diff --git a/SwarselSystems.org b/SwarselSystems.org index 242ff71..6c28513 100644 --- a/SwarselSystems.org +++ b/SwarselSystems.org @@ -1820,8 +1820,23 @@ A short overview over each input and what it does: outputs = inputs: - inputs.flake-parts.lib.mkFlake { inherit inputs; } { - imports = [ (inputs.import-tree [ ./flake ]) ]; + let + mkNixos = { name, system }: { + den.hosts.${system} = { + ${name} = { host, ... }: { + instantiate = inputs.self.outputs.instantiateNixos { minimal = false; } host.name host.system; + users.swarsel = {}; + }; + + "${name}-minimal" = { host, ... }: { + instantiate = inputs.self.outputs.instantiateNixos { minimal = true; } host.name host.system; + intoAttr = [ "nixosConfigurationsMinimal" host.name ]; + }; + }; + }; + in + inputs.flake-parts.lib.mkFlake { inherit inputs; specialArgs = { inherit mkNixos; }; } { + imports = [ (inputs.import-tree [ ./flake ./aspects ]) ]; systems = [ "x86_64-linux" "aarch64-linux" @@ -1876,7 +1891,7 @@ The builtin that is added is a simple call to the =exec= function that calls a b assert assertMsg (hasSuffix ".nix.enc" nixFile) "The content of the decrypted file must be a nix expression and should therefore end in .nix.enc"; exec [ - ./files/scripts/sops-decrypt-and-cache.sh + ../scripts/sops-decrypt-and-cache.sh nixFile ]; } @@ -2244,7 +2259,7 @@ Similar to [[#h:6ed1a641-dba8-4e85-a62e-be93264df57a][Packages (pkgs)]], we agai #+end_src -** Hosts +** Hosts (Legacy) :PROPERTIES: :CUSTOM_ID: h:5c5bf78a-9a66-436f-bd85-85871d9d402b :END: @@ -2276,86 +2291,8 @@ The rest of the functions are used to build full NixOS systems as well as halfCo #+begin_src nix-ts :tangle flake/hosts.nix { self, inputs, ... }: - let - inherit (self) outputs; - inherit (outputs) lib homeLib; - in { - den.hosts.x86_64-linux.pyramid = - let - configName = "pyramid"; - arch = "x86_64-linux"; - in - { - modules = [ - inputs.disko.nixosModules.disko - inputs.home-manager.nixosModules.home-manager - inputs.impermanence.nixosModules.impermanence - inputs.lanzaboote.nixosModules.lanzaboote - inputs.microvm.nixosModules.host - inputs.microvm.nixosModules.microvm - inputs.nix-index-database.nixosModules.nix-index - inputs.nix-minecraft.nixosModules.minecraft-servers - inputs.nix-topology.nixosModules.default - inputs.nswitch-rcm-nix.nixosModules.nswitch-rcm - inputs.simple-nixos-mailserver.nixosModules.default - inputs.sops.nixosModules.sops - inputs.stylix.nixosModules.stylix - inputs.swarsel-nix.nixosModules.default - inputs.nixos-nftables-firewall.nixosModules.default - inputs.pia.nixosModules.default - inputs.niritiling.nixosModules.default - inputs.noctoggle.nixosModules.default - (inputs.nixos-extra-modules + "/modules/guests") - (inputs.nixos-extra-modules + "/modules/interface-naming.nix") - "${self}/hosts/nixos/${arch}/${configName}" - "${self}/profiles/nixos" - "${self}/modules/nixos" - { - _module.args.dns = inputs.dns; - - microvm.guest.enable = lib.mkDefault false; - - networking.hostName = lib.swarselsystems.mkStrong configName; - - node = { - name = lib.mkForce configName; - arch = lib.mkForce arch; - type = lib.mkForce "nixos"; - secretsDir = ../hosts/nixos/${arch}/${configName}/secrets; - configDir = ../hosts/nixos/${arch}/${configName}; - lockFromBootstrapping = lib.swarselsystems.mkStrong true; - }; - - swarselprofiles = { - minimal = lib.swarselsystems.mkStrong true; - }; - - swarselmodules.server = { - ssh = lib.swarselsystems.mkStrong true; - }; - - swarselsystems = { - mainUser = lib.swarselsystems.mkStrong "swarsel"; - }; - } - ]; - users.swarsel = { }; - instantiate = (inputs.nixpkgs.lib.nixosSystem { - specialArgs = { - inherit inputs outputs self homeLib configName arch; - minimal = false; - inherit (outputs.pkgs.${arch}) lib; - inherit (outputs) nodes topologyPrivate; - globals = outputs.globals.${arch}; - type = "nixos"; - withHomeManager = true; - extraModules = [ "${self}/modules/nixos/common/globals.nix" ]; - }; - }); - }; - flake = { config, ... }: let inherit (self) outputs; @@ -3185,21 +3122,92 @@ This exposes all of my modular configuration as modules. Other people can use th }; } +#+end_src +** Instantiation Functions + +#+begin_src nix-ts :tangle flake/instantiate.nix +{ self, inputs, ... }: +{ + flake = { config, ... }: { + instantiateNixos = { minimal }: configName: arch: { modules }: + let + inherit (self) outputs; + inherit (outputs) lib homeLib; + mkStrong = lib.mkOverride 60; + in + inputs.nixpkgs.lib.nixosSystem { + specialArgs = { + inherit inputs outputs self homeLib configName arch minimal; + inherit (config.pkgs.${arch}) lib; + inherit (config) nodes topologyPrivate; + globals = config.globals.${arch}; + type = "nixos"; + withHomeManager = true; + extraModules = [ "${self}/modules-clone/nixos/common/globals.nix" ]; + }; + modules = modules ++ [ + inputs.disko.nixosModules.disko + inputs.home-manager.nixosModules.home-manager + inputs.impermanence.nixosModules.impermanence + inputs.lanzaboote.nixosModules.lanzaboote + inputs.microvm.nixosModules.host + inputs.microvm.nixosModules.microvm + inputs.nix-index-database.nixosModules.nix-index + inputs.nix-minecraft.nixosModules.minecraft-servers + inputs.nix-topology.nixosModules.default + inputs.nswitch-rcm-nix.nixosModules.nswitch-rcm + inputs.simple-nixos-mailserver.nixosModules.default + inputs.sops.nixosModules.sops + inputs.stylix.nixosModules.stylix + inputs.swarsel-nix.nixosModules.default + inputs.nixos-nftables-firewall.nixosModules.default + inputs.pia.nixosModules.default + inputs.niritiling.nixosModules.default + inputs.noctoggle.nixosModules.default + (inputs.nixos-extra-modules + "/modules/guests") + (inputs.nixos-extra-modules + "/modules/interface-naming.nix") + "${self}/profiles-clone/nixos" + "${self}/modules-clone/nixos" + { + _module.args.dns = inputs.dns; + + microvm.guest.enable = lib.mkDefault false; + + networking.hostName = mkStrong configName; + + node = { + name = lib.mkForce configName; + arch = lib.mkForce arch; + type = lib.mkForce "nixos"; + secretsDir = ../hosts/nixos/${arch}/${configName}/secrets; + configDir = ../hosts/nixos/${arch}/${configName}; + lockFromBootstrapping = lib.mkIf (!minimal) (mkStrong true); + }; + + swarselprofiles = { + minimal = lib.mkIf minimal (mkStrong true); + }; + + swarselmodules.server = { + ssh = lib.mkIf (!minimal) (mkStrong true); + }; + + swarselsystems = { + mainUser = mkStrong "swarsel"; + }; + } + ]; + }; + }; +} + #+end_src ** Den #+begin_src nix-ts :tangle flake/den.nix - { self, inputs, ... }: - let - inherit (self.outputs) lib; - in + { inputs, ... }: { imports = [ inputs.den.flakeModule ]; - - den = { - schema.user.classes = lib.mkDefault [ "homeManager" ]; - default.homeManager.home.stateVersion = "23.05"; - }; } #+end_src @@ -8662,6 +8670,1457 @@ I also set the =WLR_RENDERER_ALLOW_SOFTWARE=1= to allow this configuration to ru #+end_src +** Aspects +*** Modules +**** Defaults + +=den.default= applies settings to all hosts, users, and homes. This is the right place for =stateVersion= and other global policies. + +#+begin_src nix-ts :tangle aspects/defaults.nix + { lib, den, ... }: + { + den = { + schema.user.classes = lib.mkDefault [ "homeManager" ]; + ctx.user.includes = [ den.provides.mutual-provider ]; + default = { + nixos = { lib, minimal, ... }: { + users.mutableUsers = lib.mkIf (!minimal) (lib.mkDefault false); + system.stateVersion = lib.mkDefault "23.05"; + }; + homeManager = { + home.stateVersion = lib.mkDefault "23.05"; + }; + includes = [ + den.provides.define-user + ]; + }; + }; + } + +#+end_src +**** Shared Options + +#+begin_src nix-ts :tangle aspects/shared.nix + { + den = { + schema.conf = { lib, ... }: { + options = { + isPublic = lib.mkEnableOption "mark this as a public config (= without secrets)"; + isMicroVM = lib.mkEnableOption "mark this config as a microvm"; + mainUser = lib.mkOption { + type = lib.types.str; + default = "swarsel"; + }; + }; + }; + }; + } + +#+end_src +**** Battery: SOPS shared secrets + +This is the battery for SOPS secrets that are used in home-manager. + +#+begin_src nix-ts :tangle aspects/battery-sops.nix + { lib, den, ... }: + let + hostContext = { name, args, class }: { host }: { + nixos.sops.secrets.${name} = lib.mkIf (!host.isPublic) args // lib.optionalAttrs (class == "homeManager") { owner = host.mainUser; }; + }; + + # deadnix: skip + hostUserContext = { name, args, class }: { host, user }: { + nixos.sops.secrets.${name} = lib.mkIf (!host.isPublic) args // lib.optionalAttrs (class == "homeManager") { owner = host.mainUser; }; + }; + + homeContext = { name, args }: { home }: { + homeManager.sops.secrets.${name} = lib.mkIf (!home.isPublic) args; + }; + + in + { + den.provides.sops = { name, args, class ? "homeManager" }: den.lib.parametric.exactly { + includes = [ + (hostContext { inherit name args class; }) + (hostUserContext { inherit name args class; }) + ] ++ lib.optional (class == "homeManager") (homeContext { inherit name args; }); + }; + } + +#+end_src +**** Battery: PII + +This is the battery for PII + +#+begin_src nix-ts :tangle aspects/battery-pii.nix + { lib, ... }: + let + # If the given expression is a bare set, it will be wrapped in a function, + # so that the imported file can always be applied to the inputs, similar to + # how modules can be functions or sets. + constSet = x: if builtins.isAttrs x then (_: x) else x; + + sopsImportEncrypted = + assert lib.assertMsg (builtins ? extraBuiltins.sopsImportEncrypted) + "The extra builtin 'sopsImportEncrypted' is not available, so repo.secrets cannot be decrypted. Did you forget to add nix-plugins and point it to `/files/nix/extra-builtins.nix` ?"; + builtins.extraBuiltins.sopsImportEncrypted; + + importEncrypted = + path: + constSet ( + if builtins.pathExists path then + sopsImportEncrypted path + else + { } + ); + in + { + den = { + schema.conf = { config, inputs, lib, homeLib, nodes, globals, ... }: { + options = { + repo = { + secretFiles = lib.mkOption { + default = { }; + type = lib.types.attrsOf lib.types.path; + example = lib.literalExpression "{ local = ./pii.nix.enc; }"; + description = '' + This is for storing PII. + Each path given here must be an sops-encrypted .nix file. For each attribute ``, + the corresponding file will be decrypted, imported and exposed as {option}`repo.secrets.`. + ''; + }; + + secrets = lib.mkOption { + readOnly = true; + default = lib.mapAttrs (_: x: importEncrypted x { inherit lib homeLib nodes globals inputs config; inherit (inputs.topologyPrivate) topologyPrivate; }) config.repo.secretFiles; + type = lib.types.unspecified; + description = "Exposes the loaded repo secrets."; + }; + }; + }; + config = { + repo.secretFiles = + let + local = config.node.secretsDir + "/pii.nix.enc"; + in + (lib.optionalAttrs (lib.pathExists local) { inherit local; }) // { + common = ../secrets/repo/pii.nix.enc; + }; + }; + }; + }; + } + +#+end_src +**** Users + +#+begin_src nix-ts :tangle aspects/users.nix + { den, ... }: + let + hostContext = { host, ... }: { + nixos = { minimal, lib, config, ... }: { + users.users.swarsel = { + uid = 1000; + autoSubUidGidRange = false; + subUidRanges = [ + { + count = 65534; + startUid = 100001; + } + ]; + subGidRanges = [ + { + count = 999; + startGid = 1001; + } + ]; + description = "Leon S"; + password = lib.mkIf (minimal || host.isPublic) "setup"; + hashedPasswordFile = lib.mkIf (!minimal && !host.isPublic) config.sops.secrets.main-user-hashed-pw.path; + extraGroups = lib.optionals (!minimal && !host.isMicroVM) [ "input" "syncthing" "docker" "lp" "audio" "video" "vboxusers" "libvirtd" "scanner" ]; + }; + }; + }; + + in + { + + den = { + aspects.swarsel = { + includes = [ + hostContext + (den.provides.sops { class = "nixos"; name = "main-user-hashed-pw"; args = { neededForUsers = true; }; }) + den.provides.primary-user + (den.provides.user-shell "zsh") + ]; + }; + aspects.root = { globals, ... }: { + nixos = { + users.users.root = globals.root.hashedPassword; + }; + }; + }; + } + +#+end_src +**** Work + +#+begin_src nix-ts :tangle aspects/work.nix + { self, den, ... }: + let + sopsFile = self + /secrets/work/secrets.yaml; + certsSopsFile = self + /secrets/repo/certs.yaml; + + hostContext = { host }: common host; + homeContext = { home }: common home; + + common = from: { + nixos = { lib, pkgs, config, ... }: + let + inherit (from) mainUser; + inherit (config.swarselsystems) homeDir; + iwd = config.networking.networkmanager.wifi.backend == "iwd"; + owner = mainUser; + in + { + options.swarselsystems = { + hostName = lib.mkOption { + type = lib.types.str; + default = config.node.name; + }; + fqdn = lib.mkOption { + type = lib.types.str; + default = ""; + }; + }; + config = { + + sops = + let + secretNames = [ + "vcuser" + "vcpw" + "govcuser" + "govcpw" + "govcurl" + "govcdc" + "govcds" + "govchost" + "govcnetwork" + "govcpool" + "baseuser" + "basepw" + ]; + in + { + secrets = builtins.listToAttrs ( + map + (name: { + inherit name; + value = { inherit owner sopsFile; }; + }) + secretNames + ); + templates = { + "network-manager-work.env".content = '' + BASEUSER=${config.sops.placeholder.baseuser} + BASEPASS=${config.sops.placeholder.basepw} + ''; + }; + }; + + boot.initrd = { + systemd.enable = lib.mkForce true; # make sure we are using initrd systemd even when not using Impermanence + luks = { + # disable "support" since we use systemd-cryptenroll + # make sure yubikeys are enrolled using + # sudo systemd-cryptenroll --fido2-device=auto --fido2-with-user-verification=no --fido2-with-user-presence=true --fido2-with-client-pin=no /dev/nvme0n1p2 + yubikeySupport = false; + fido2Support = false; + }; + }; + + programs = { + + browserpass.enable = true; + _1password.enable = true; + _1password-gui = { + enable = true; + package = pkgs._1password-gui-beta; + polkitPolicyOwners = [ "${mainUser}" ]; + }; + }; + + networking = { + inherit (config.swarselsystems) hostName fqdn; + + networkmanager = { + wifi.scanRandMacAddress = false; + ensureProfiles = { + environmentFiles = [ + "${config.sops.templates."network-manager-work.env".path}" + ]; + profiles = { + VBC = { + "802-1x" = { + eap = if (!iwd) then "ttls;" else "peap;"; + identity = "$BASEUSER"; + password = "$BASEPASS"; + phase2-auth = "mschapv2"; + }; + connection = { + id = "VBC"; + type = "wifi"; + autoconnect-priority = "500"; + uuid = "3988f10e-6451-381f-9330-a12e66f45051"; + secondaries = "48d09de4-0521-47d7-9bd5-43f97e23ff82"; # vpn uuid + }; + ipv4 = { method = "auto"; }; + ipv6 = { + # addr-gen-mode = "default"; + addr-gen-mode = "stable-privacy"; + method = "auto"; + }; + proxy = { }; + wifi = { + cloned-mac-address = "permanent"; + mac-address = "E8:65:38:52:63:FF"; + mac-address-randomization = "1"; + mode = "infrastructure"; + band = "a"; + ssid = "VBC"; + }; + wifi-security = { + # auth-alg = "open"; + key-mgmt = "wpa-eap"; + }; + }; + }; + }; + }; + + + nftables = { + firewall = { + zones = { + virbr = { + interfaces = [ "virbr*" ]; + }; + }; + rules = { + virbr-dns-dhcp = { + from = [ "virbr" ]; + to = [ "local" ]; + allowedTCPPorts = [ 53 ]; + allowedUDPPorts = [ 53 67 547 ]; + }; + virbr-forward = { + from = [ "virbr" ]; + to = [ "untrusted" ]; + verdict = "accept"; + }; + virbr-forward-return = { + from = [ "untrusted" ]; + to = [ "virbr" ]; + extraLines = [ + "ct state { established, related } accept" + ]; + }; + }; + }; + chains.postrouting.libvirt-masq = { + after = [ "dnat" ]; + rules = [ + "iifname \"virbr*\" masquerade" + ]; + }; + }; + + search = [ + "vbc.ac.at" + "clip.vbc.ac.at" + "imp.univie.ac.at" + ]; + }; + + systemd.services = { + virtqemud.path = with pkgs; [ + qemu_kvm + libvirt + ]; + + virtstoraged.path = with pkgs; [ + qemu_kvm + libvirt + ]; + + virtnetworkd.path = with pkgs; [ + dnsmasq + iproute2 + nftables + ]; + }; + + virtualisation = { + docker.enable = lib.mkIf (!config.virtualisation.podman.dockerCompat) true; + spiceUSBRedirection.enable = true; + libvirtd = { + enable = true; + qemu = { + package = pkgs.qemu_kvm; + runAsRoot = true; + swtpm.enable = true; + vhostUserPackages = with pkgs; [ virtiofsd ]; + }; + }; + }; + + environment.systemPackages = with pkgs; [ + remmina + python39 + qemu + packer + gnumake + libisoburn + govc + terraform + opentofu + terragrunt + graphviz + azure-cli + + # vm + virt-manager + virt-viewer + virtiofsd + spice + spice-gtk + spice-protocol + virtio-win + win-spice + + powershell + gh + ]; + + services = { + spice-vdagentd.enable = true; + openssh = { + enable = true; + extraConfig = '' + ''; + }; + + syncthing = { + settings = { + "winters" = { + id = "O7RWDMD-AEAHPP7-7TAVLKZ-BSWNBTU-2VA44MS-EYGUNBB-SLHKB3C-ZSLMOAA"; + }; + "moonside@oracle" = { + id = "VPCDZB6-MGVGQZD-Q6DIZW3-IZJRJTO-TCC3QUQ-2BNTL7P-AKE7FBO-N55UNQE"; + }; + folders = { + "Documents" = { + path = "${homeDir}/Documents"; + devices = [ "moonside@oracle" ]; + id = "hgr3d-pfu3w"; + }; + }; + }; + }; + + # udev.extraRules = '' + # # lock screen when yubikey removed + # ACTION=="remove", ENV{PRODUCT}=="3/1050/407/110", RUN+="${pkgs.systemd}/bin/systemctl suspend" + # ''; + + }; + + # cgroups v1 is required for centos7 dockers + # specialisation = { + # cgroup_v1.configuration = { + # boot.kernelParams = [ + # "SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1" + # "systemd.unified_cgroup_hierarchy=0" + # ]; + # }; + # }; + }; + }; + homeManager = { self, config, pkgs, lib, vars, ... }: + let + source = if (config ? home) then "home" else "host"; + inherit (config.swarselsystems) homeDir; + inherit (config.repo.secrets.local.mail) allMailAddresses; + inherit (config.repo.secrets.local.work) mailAddress; + + in + { + config = { + home = { + packages = with pkgs; [ + teams-for-linux + shellcheck + dig + docker + postman + # rclone + libguestfs-with-appliance + prometheus.cli + tigervnc + # openstackclient + step-cli + + vscode-fhs + copilot-cli + antigravity + + + rustdesk-vbc + ]; + sessionVariables = { + AWS_CA_BUNDLE = source.sops.secrets.harica-root-ca.path; + }; + }; + systemd.user.sessionVariables = { + DOCUMENT_DIR_WORK = lib.mkForce "${homeDir}/Documents/Work"; + } // lib.optionalAttrs (!config.swarselsystems.isPublic) { + SWARSEL_MAIL_ALL = lib.mkForce allMailAddresses; + SWARSEL_MAIL_WORK = lib.mkForce mailAddress; + }; + + accounts.email.accounts.work = + let + inherit (from.repo.secrets.local.work) mailName; + in + { + primary = false; + address = mailAddress; + userName = mailAddress; + realName = mailName; + passwordCommand = "pizauth show work"; + imap = { + host = "outlook.office365.com"; + port = 993; + tls.enable = true; # SSL/TLS + }; + smtp = { + host = "outlook.office365.com"; + port = 587; + tls = { + enable = true; # SSL/TLS + useStartTls = true; + }; + }; + thunderbird = { + enable = true; + profiles = [ "default" ]; + settings = id: { + "mail.smtpserver.smtp_${id}.authMethod" = 10; # oauth + "mail.server.server_${id}.authMethod" = 10; # oauth + # "toolkit.telemetry.enabled" = false; + # "toolkit.telemetry.rejected" = true; + # "toolkit.telemetry.prompted" = 2; + }; + }; + msmtp = { + enable = true; + extraConfig = { + auth = "xoauth2"; + host = "outlook.office365.com"; + protocol = "smtp"; + port = "587"; + tls = "on"; + tls_starttls = "on"; + from = "${mailAddress}"; + user = "${mailAddress}"; + passwordeval = "pizauth show work"; + }; + }; + mu.enable = true; + mbsync = { + enable = true; + expunge = "both"; + patterns = [ "INBOX" ]; + extraConfig = { + account = { + AuthMechs = "XOAUTH2"; + }; + }; + }; + }; + + wayland.windowManager.sway = + let + inherit (from.repo.secrets.local.work) user1 user1Long domain1 mailAddress; + in + { + config = { + keybindings = + let + inherit (config.wayland.windowManager.sway.config) modifier; + in + { + "${modifier}+Shift+d" = "exec ${pkgs.quickpass}/bin/quickpass work/adm/${user1}/${user1Long}@${domain1}"; + "${modifier}+Shift+i" = "exec ${pkgs.quickpass}/bin/quickpass work/${mailAddress}"; + }; + }; + }; + + stylix = { + targets.firefox.profileNames = + let + inherit (from.repo.secrets.local.work) user1 user2 user3; + in + [ + "${user1}" + "${user2}" + "${user3}" + "work" + ]; + }; + + programs = + let + inherit (from.repo.secrets.local.work) user1 user1Long user2 user2Long user3 user3Long path1 site1 site2 site3 site4 site5 site6 site7 clouds; + in + { + openstackclient = { + enable = true; + inherit clouds; + }; + awscli = { + enable = true; + package = pkgs.awscli2; + }; + + zsh = { + shellAliases = { + dssh = "ssh -l ${user1Long}"; + cssh = "ssh -l ${user2Long}"; + wssh = "ssh -l ${user3Long}"; + }; + cdpath = [ + "~/Documents/Work" + ]; + dirHashes = { + d = "$HOME/.dotfiles"; + w = "$HOME/Documents/Work"; + s = "$HOME/.dotfiles/secrets"; + pr = "$HOME/Documents/Private"; + ac = path1; + }; + + sessionVariables = { + VSPHERE_USER = "$(cat ${source.sops.secrets.vcuser.path})"; + VSPHERE_PW = "$(cat ${source.sops.secrets.vcpw.path})"; + GOVC_USERNAME = "$(cat ${source.sops.secrets.govcuser.path})"; + GOVC_PASSWORD = "$(cat ${source.sops.secrets.govcpw.path})"; + GOVC_URL = "$(cat ${source.sops.secrets.govcurl.path})"; + GOVC_DATACENTER = "$(cat ${source.sops.secrets.govcdc.path})"; + GOVC_DATASTORE = "$(cat ${source.sops.secrets.govcds.path})"; + GOVC_HOST = "$(cat ${source.sops.secrets.govchost.path})"; + GOVC_RESOURCE_POOL = "$(cat ${source.sops.secrets.govcpool.path})"; + GOVC_NETWORK = "$(cat ${source.sops.secrets.govcnetwork.path})"; + }; + }; + + ssh.matchBlocks = from.repo.secrets.local.work.sshConfig; + + firefox = { + profiles = + let + isDefault = false; + in + { + "${user1}" = lib.recursiveUpdate + { + inherit isDefault; + id = 1; + settings = { + "browser.startup.homepage" = "${site1}|${site2}"; + }; + } + vars.firefox; + "${user2}" = lib.recursiveUpdate + { + inherit isDefault; + id = 2; + settings = { + "browser.startup.homepage" = "${site3}"; + }; + } + vars.firefox; + "${user3}" = lib.recursiveUpdate + { + inherit isDefault; + id = 3; + } + vars.firefox; + work = lib.recursiveUpdate + { + inherit isDefault; + id = 4; + settings = { + "browser.startup.homepage" = "${site4}|${site5}|${site6}|${site7}"; + }; + } + vars.firefox; + }; + }; + + chromium = { + enable = true; + package = pkgs.chromium; + + extensions = [ + # 1password + "gejiddohjgogedgjnonbofjigllpkmbf" + # dark reader + "eimadpbcbfnmbkopoojfekhnkhdbieeh" + # ublock origin + "cjpalhdlnbpafiamejdnhcphjbkeiagm" + # i still dont care about cookies + "edibdbjcniadpccecjdfdjjppcpchdlm" + # browserpass + "naepdomgkenhinolocfifgehidddafch" + ]; + }; + }; + + services = { + + shikane = { + settings = + let + workRight = [ + "m=HP Z32" + "s=CN41212T55" + "v=HP Inc." + ]; + workLeft = [ + "m=HP 732pk" + "s=CNC4080YL5" + "v=HP Inc." + ]; + exec = [ "notify-send shikane \"Profile $SHIKANE_PROFILE_NAME has been applied\"" ]; + in + { + profile = [ + + { + name = "work-internal-on"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = true; + scale = 1.7; + position = "2560,0"; + } + { + match = workRight; + enable = true; + scale = 1.0; + mode = "3840x2160@60Hz"; + position = "-1280,0"; + } + { + match = workLeft; + enable = true; + scale = 1.0; + transform = "270"; + mode = "3840x2160@60Hz"; + position = "-3440,-1050"; + } + ]; + } + { + name = "work-internal-off"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = false; + scale = 1.7; + position = "2560,0"; + } + { + match = workRight; + enable = true; + scale = 1.0; + mode = "3840x2160@60Hz"; + position = "-1280,0"; + } + { + match = workLeft; + enable = true; + scale = 1.0; + transform = "270"; + mode = "3840x2160@60Hz"; + position = "-3440,-1050"; + } + ]; + } + + + ]; + }; + }; + kanshi = { + settings = [ + { + # seminary room + output = { + criteria = "Applied Creative Technology Transmitter QUATTRO201811"; + scale = 1.0; + mode = "1280x720"; + }; + } + { + # work side screen + output = { + criteria = "HP Inc. HP 732pk CNC4080YL5"; + scale = 1.0; + mode = "3840x2160"; + transform = "270"; + }; + } + # { + # # work side screen + # output = { + # criteria = "Hewlett Packard HP Z24i CN44250RDT"; + # scale = 1.0; + # mode = "1920x1200"; + # transform = "270"; + # }; + # } + { + # work main screen + output = { + criteria = "HP Inc. HP Z32 CN41212T55"; + scale = 1.0; + mode = "3840x2160"; + }; + } + { + profile = { + name = "lidopen"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${config.swarselsystems.sharescreen}' --image ${config.swarselsystems.wallpaper} --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP Z32 CN41212T55' --image ${self}/files/wallpaper/landscape/botanicswp.png --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP 732pk CNC4080YL5' --image ${self}/files/wallpaper/portrait/op6wp.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "enable"; + scale = 1.5; + position = "2560,0"; + } + { + criteria = "HP Inc. HP 732pk CNC4080YL5"; + scale = 1.0; + mode = "3840x2160"; + position = "-3440,-1050"; + transform = "270"; + } + { + criteria = "HP Inc. HP Z32 CN41212T55"; + scale = 1.0; + mode = "3840x2160"; + position = "-1280,0"; + } + ]; + }; + } + { + profile = + let + monitor = "Applied Creative Technology Transmitter QUATTRO201811"; + in + { + name = "lidopen"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${config.swarselsystems.sharescreen}' --image ${config.swarselsystems.wallpaper} --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output '${monitor}' --image ${self}/files/wallpaper/services/navidrome.png --mode ${config.stylix.imageScalingMode}" + "${pkgs.kanshare}/bin/kanshare ${config.swarselsystems.sharescreen} '${monitor}'" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "enable"; + scale = 1.7; + position = "2560,0"; + } + { + criteria = "Applied Creative Technology Transmitter QUATTRO201811"; + scale = 1.0; + mode = "1280x720"; + position = "10000,10000"; + } + ]; + }; + } + { + profile = { + name = "lidclosed"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP Z32 CN41212T55' --image ${self}/files/wallpaper/landscape/botanicswp.png --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP 732pk CNC4080YL5' --image ${self}/files/wallpaper/portrait/op6wp.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "disable"; + } + { + criteria = "HP Inc. HP 732pk CNC4080YL5"; + scale = 1.0; + mode = "3840x2160"; + position = "-3440,-1050"; + transform = "270"; + } + { + criteria = "HP Inc. HP Z32 CN41212T55"; + scale = 1.0; + mode = "3840x2160"; + position = "-1280,0"; + } + ]; + }; + } + { + profile = + let + monitor = "Applied Creative Technology Transmitter QUATTRO201811"; + in + { + name = "lidclosed"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${monitor}' --image ${self}/files/wallpaper/services/navidrome.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "disable"; + } + { + criteria = "Applied Creative Technology Transmitter QUATTRO201811"; + scale = 1.0; + mode = "1280x720"; + position = "10000,10000"; + } + ]; + }; + } + ]; + }; + }; + + systemd.user.services = { + pizauth.Service = { + ExecStartPost = [ + "${pkgs.toybox}/bin/sleep 1" + "//bin/sh -c '${lib.getExe pkgs.pizauth} restore < ${homeDir}/.pizauth.state'" + ]; + }; + + teams-applet = { + Unit = { + Description = "teams applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs.teams-for-linux}/bin/teams-for-linux --disableGpu=true --minimized=true --trayIconEnabled=true"; + }; + }; + + onepassword-applet = { + Unit = { + Description = "1password applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs._1password-gui-beta}/bin/1password"; + }; + }; + + }; + + services.pizauth = { + enable = true; + extraConfig = '' + auth_notify_cmd = "if [[ \"$(notify-send -A \"Open $PIZAUTH_ACCOUNT\" -t 30000 'pizauth authorisation')\" == \"0\" ]]; then open \"$PIZAUTH_URL\"; fi"; + error_notify_cmd = "notify-send -t 90000 \"pizauth error for $PIZAUTH_ACCOUNT\" \"$PIZAUTH_MSG\""; + token_event_cmd = "pizauth dump > ${homeDir}/.pizauth.state"; + ''; + accounts = { + work = { + authUri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; + tokenUri = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + clientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584"; + clientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82"; + scopes = [ + "https://outlook.office365.com/IMAP.AccessAsUser.All" + "https://outlook.office365.com/SMTP.Send" + "offline_access" + ]; + loginHint = "${from.repo.secrets.local.work.mailAddress}"; + }; + }; + + }; + + xdg = + let + inherit (from.repo.secrets.local.work) user1 user2 user3; + in + { + mimeApps = { + defaultApplications = { + "x-scheme-handler/msteams" = [ "teams-for-linux.desktop" ]; + }; + }; + desktopEntries = + let + terminal = false; + categories = [ "Application" ]; + icon = "firefox"; + in + { + firefox_work = { + name = "Firefox (work)"; + genericName = "Firefox work"; + exec = "firefox -p work"; + inherit terminal categories icon; + }; + "firefox_${user1}" = { + name = "Firefox (${user1})"; + genericName = "Firefox ${user1}"; + exec = "firefox -p ${user1}"; + inherit terminal categories icon; + }; + + "firefox_${user2}" = { + name = "Firefox (${user2})"; + genericName = "Firefox ${user2}"; + exec = "firefox -p ${user2}"; + inherit terminal categories icon; + }; + + "firefox_${user3}" = { + name = "Firefox (${user3})"; + genericName = "Firefox ${user3}"; + exec = "firefox -p ${user3}"; + inherit terminal categories icon; + }; + + + }; + }; + swarselsystems = { + startup = [ + # { command = "nextcloud --background"; } + # { command = "vesktop --start-minimized --enable-speech-dispatcher --ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime"; } + # { command = "element-desktop --hidden --enable-features=UseOzonePlatform --ozone-platform=wayland --disable-gpu-driver-bug-workarounds"; } + # { command = "anki"; } + # { command = "obsidian"; } + # { command = "nm-applet"; } + # { command = "feishin"; } + # { command = "teams-for-linux --disableGpu=true --minimized=true --trayIconEnabled=true"; } + # { command = "1password"; } + ]; + monitors = { + work_back_middle = rec { + name = "LG Electronics LG Ultra HD 0x000305A6"; + mode = "2560x1440"; + scale = "1"; + position = "5120,0"; + workspace = "1:一"; + # output = "DP-10"; + output = name; + }; + work_front_left = rec { + name = "LG Electronics LG Ultra HD 0x0007AB45"; + mode = "3840x2160"; + scale = "1"; + position = "5120,0"; + workspace = "1:一"; + # output = "DP-7"; + output = name; + }; + work_middle_middle_main = rec { + name = "HP Inc. HP Z32 CN41212T55"; + mode = "3840x2160"; + scale = "1"; + position = "-1280,0"; + workspace = "1:一"; + # output = "DP-3"; + output = name; + }; + # work_middle_middle_main = rec { + # name = "HP Inc. HP 732pk CNC4080YL5"; + # mode = "3840x2160"; + # scale = "1"; + # position = "-1280,0"; + # workspace = "11:M"; + # # output = "DP-8"; + # output = name; + # }; + work_middle_middle_side = rec { + name = "HP Inc. HP 732pk CNC4080YL5"; + mode = "3840x2160"; + transform = "270"; + scale = "1"; + position = "-3440,-1050"; + workspace = "12:S"; + # output = "DP-8"; + output = name; + }; + work_middle_middle_old = rec { + name = "Hewlett Packard HP Z24i CN44250RDT"; + mode = "1920x1200"; + transform = "270"; + scale = "1"; + position = "-2480,0"; + workspace = "12:S"; + # output = "DP-9"; + output = name; + }; + work_seminary = rec { + name = "Applied Creative Technology Transmitter QUATTRO201811"; + mode = "1280x720"; + scale = "1"; + position = "10000,10000"; # i.e. this screen is inaccessible by moving the mouse + workspace = "14:T"; + # output = "DP-4"; + output = name; + }; + }; + inputs = { + "1133:45081:MX_Master_2S_Keyboard" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + # "2362:628:PIXA3854:00_093A:0274_Touchpad" = { + # dwt = "enabled"; + # tap = "enabled"; + # natural_scroll = "enabled"; + # middle_emulation = "enabled"; + # drag_lock = "disabled"; + # }; + "1133:50504:Logitech_USB_Receiver" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + "1133:45944:MX_KEYS_S" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + }; + + }; + }; + }; + }; + in + { + den = { + aspects.work = { + includes = [ + hostContext + homeContext + (den.provides.sops { name = "harica-root-ca"; args = { sopsFile = certsSopsFile; path = "/home/swarsel/.aws/certs/harica-root.pem"; }; }) + (den.provides.sops { name = "yubikey-1"; args = { inherit sopsFile; }; }) + (den.provides.sops { name = "ucKey"; args = { inherit sopsFile; }; }) + ]; + }; + }; + } + +#+end_src +**** Shell + +#+begin_src nix-ts :tangle aspects/shell.nix + { den, ... }: + { + den.aspects.shell = { + provides.zsh = { + includes = [ + (den.provides.sops { name = "croc-password"; args = { }; }) + (den.provides.sops { name = "github-nixpkgs-review-token"; args = { }; }) + ]; + nixos = { pkgs, ... }: { + programs.zsh = { + enable = true; + enableCompletion = false; + }; + users.defaultUserShell = pkgs.zsh; + environment = { + shells = with pkgs; [ zsh ]; + pathsToLink = [ "/share/zsh" ]; + }; + }; + homeManager = { self, config, pkgs, lib, minimal, globals, confLib, arch, ... }: + let + inherit (config.swarselsystems) flakePath isNixos homeDir; + crocDomain = globals.services.croc.domain; + in + { + programs.zsh = { + enable = true; + } // lib.optionalAttrs (!minimal) { + shellAliases = + { + nb = "nix build"; + nbl = "nix build --builders \"\""; + nbo = "nix build --offline --builders \"\""; + nd = "nix develop"; + ns = "nix shell"; + hmswitch = lib.mkIf (!isNixos) "${lib.getExe pkgs.home-manager} --flake ${flakePath}#$(hostname) switch |& nom"; + nswitch = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) switch; cd -;"; + ntest = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) test; cd -;"; + nboot = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) boot; cd -;"; + ndry = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) dry-activate; cd -;"; + magit = "emacsclient -nc -e \"(magit-status)\""; + config = "git --git-dir=$HOME/.cfg/ --work-tree=$HOME"; + g = "git"; + c = "git --git-dir=$FLAKE/.git --work-tree=$FLAKE/"; + passpush = "cd ~/.local/share/password-store; git add .; git commit -m 'pass file changes'; git push; cd -;"; + passpull = "cd ~/.local/share/password-store; git pull; cd -;"; + hotspot = "nmcli connection up local; nmcli device wifi hotspot;"; + youtube-dl = "yt-dlp"; + cat-orig = "cat"; + # cdr = "cd \"$( (find $DOCUMENT_DIR_WORK $DOCUMENT_DIR_PRIV -maxdepth 1 && echo $FLAKE) | fzf )\""; + cdr = "source cdr"; + nix-ldd-ldd = "LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH ldd"; + nix-ldd = "LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH ldd"; + nix-ldd-locate = "nix-locate --minimal --top-level -w "; + nix-store-search = "ls /nix/store | grep"; + fs-diff = "sudo mount -o subvol=/ /dev/mapper/cryptroot /mnt ; fs-diff"; + lt = "eza -las modified --total-size"; + boot-diff = "nix store diff-closures /run/*-system"; + gen-diff = "nix profile diff-closures --profile /nix/var/nix/profiles/system"; + cc = "wl-copy"; + build-topology = "nix build --override-input topologyPrivate ${self}/files/topology/private ${flakePath}#topology.${arch}.config.output"; + build-topology-dev = "nix build --show-trace --override-input nix-topology ${homeDir}/Documents/Private/nix-topology --override-input topologyPrivate ${self}/files/topology/private ${flakePath}#topology.${arch}.config.output"; + build-iso = "nix build --print-out-paths .#live-iso"; + nix-review-local = "nix run nixpkgs#nixpkgs-review -- rev HEAD"; + nix-review-post = "nix run nixpkgs#nixpkgs-review -- pr --post-result --systems linux"; + }; + autosuggestion.enable = true; + enableCompletion = true; + syntaxHighlighting.enable = true; + autocd = false; + cdpath = [ + "~/.dotfiles" + ]; + defaultKeymap = "emacs"; + dirHashes = { + dl = "$HOME/Downloads"; + gh = "$HOME/Documents/GitHub"; + }; + history = { + expireDuplicatesFirst = true; + append = true; + ignoreSpace = true; + ignoreDups = true; + path = "${config.home.homeDirectory}/.histfile"; + save = 100000; + size = 100000; + }; + historySubstringSearch = { + enable = true; + searchDownKey = "^[OB"; + searchUpKey = "^[OA"; + }; + initContent = '' + my-forward-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle forward-word + } + zle -N my-forward-word + # ctrl + right + bindkey "^[[1;5C" my-forward-word + + # shift + right + bindkey "^[[1;2C" forward-word + + my-backward-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle backward-word + } + zle -N my-backward-word + # ctrl + left + bindkey "^[[1;5D" my-backward-word + + # shift + left + bindkey "^[[1;2D" backward-word + + my-backward-delete-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle backward-delete-word + } + zle -N my-backward-delete-word + # ctrl + del + bindkey '^H' my-backward-delete-word + ''; + sessionVariables = lib.mkIf (!config.swarselsystems.isPublic) { + CROC_RELAY = crocDomain; + CROC_PASS = "$(cat ${confLib.getConfig.sops.secrets.croc-password.path or ""})"; + GITHUB_TOKEN = "$(cat ${confLib.getConfig.sops.secrets.github-nixpkgs-review-token.path or ""})"; + QT_QPA_PLATFORM_PLUGIN_PATH = "${pkgs.libsForQt5.qt5.qtbase.bin}/lib/qt-${pkgs.libsForQt5.qt5.qtbase.version}/plugins"; + }; + }; + }; + }; + }; + } + + + +#+end_src +*** Hosts +**** Pyramid + +#+begin_src nix-ts :tangle aspects/hosts/pyramid.nix + { mkNixos, lib, den, ... }: + let + hostContext = { host }: + let + inherit (host) mainUser; + in + { + nixos = { self, inputs, lib, ... }: { + + imports = [ + inputs.nixos-hardware.nixosModules.framework-16-7040-amd + + "${self}/hosts/nixos/x86_64-linux/pyramid/disk-config.nix" + "${self}/hosts/nixos/x86_64-linux/pyramid/hardware-configuration.nix" + + "${self}/modules/nixos/optional/amdcpu.nix" + "${self}/modules/nixos/optional/amdgpu.nix" + "${self}/modules/nixos/optional/framework.nix" + "${self}/modules/nixos/optional/gaming.nix" + "${self}/modules/nixos/optional/hibernation.nix" + "${self}/modules/nixos/optional/nswitch-rcm.nix" + "${self}/modules/nixos/optional/virtualbox.nix" + "${self}/modules/nixos/optional/work.nix" + "${self}/modules/nixos/optional/niri.nix" + "${self}/modules/nixos/optional/noctalia.nix" + ]; + + topology.self = { + interfaces = { + eth1.network = lib.mkForce "home"; + wifi = { }; + fritz-wg.network = "fritz-wg"; + }; + }; + + swarselsystems = { + lowResolution = "1280x800"; + highResolution = "2560x1600"; + isLaptop = true; + isNixos = true; + isBtrfs = true; + isLinux = true; + sharescreen = "eDP-2"; + info = "Framework Laptop 16, 7940HS, RX7700S, 64GB RAM"; + firewall = lib.mkForce true; + wallpaper = self + /files/wallpaper/landscape/lenovowp.png; + hasBluetooth = true; + hasFingerprint = true; + isImpermanence = false; + isSecureBoot = true; + isCrypted = true; + inherit (host.repo.secrets.local) hostName; + inherit (host.repo.secrets.local) fqdn; + hibernation.offset = 533760; + }; + }; + + home-manager = { lib, minimal, ... }: { + users."${mainUser}" = { + swarselsystems = { + isSecondaryGpu = true; + SecondaryGpuCard = "pci-0000_03_00_0"; + cpuCount = 16; + temperatureHwmon = { + isAbsolutePath = true; + path = "/sys/devices/virtual/thermal/thermal_zone0/"; + input-filename = "temp4_input"; + }; + monitors = { + main = { + # name = "BOE 0x0BC9 Unknown"; + name = "BOE 0x0BC9"; + mode = "2560x1600"; + scale = "1"; + position = "2560,0"; + workspace = "15:L"; + output = "eDP-2"; + }; + }; + }; + }; + } // lib.optionalAttrs (!minimal) { + swarselprofiles = { + personal = true; + }; + + networking.nftables.firewall.zones.untrusted.interfaces = [ "wlan*" "enp*" ]; + }; + }; + in + lib.recursiveUpdate (mkNixos + { + name = "pyramid"; + system = "x86_64-linux"; + }) { + den.aspects.pyramid = { + includes = [ + hostContext + den.aspects.work + ]; + }; + } +#+end_src ** NixOS :PROPERTIES: :CUSTOM_ID: h:6da812f5-358c-49cb-aff2-0a94f20d70b3 @@ -9680,6 +11139,7 @@ Here I set some general boot options, mostly enabling an emergency shell and som { options.swarselmodules.boot = lib.mkEnableOption "boot config"; config = lib.mkIf config.swarselmodules.boot { + boot = { initrd.systemd = { enable = true; @@ -12457,8 +13917,7 @@ I also take some precautions in how I get networking information during stage 1. ''; deps = [ "users" - "createPersistentStorageDirs" - ]; + ] ++ lib.optional config.swarselsystems.isImpermanence "createPersistentStorageDirs"; }; environment.persistence."/persist" = lib.mkIf (config.swarselsystems.isImpermanence && (config.swarselprofiles.server || minimal)) { diff --git a/aspects/battery-pii.nix b/aspects/battery-pii.nix new file mode 100644 index 0000000..8f0e69e --- /dev/null +++ b/aspects/battery-pii.nix @@ -0,0 +1,57 @@ +{ lib, ... }: +let + # If the given expression is a bare set, it will be wrapped in a function, + # so that the imported file can always be applied to the inputs, similar to + # how modules can be functions or sets. + constSet = x: if builtins.isAttrs x then (_: x) else x; + + sopsImportEncrypted = + assert lib.assertMsg (builtins ? extraBuiltins.sopsImportEncrypted) + "The extra builtin 'sopsImportEncrypted' is not available, so repo.secrets cannot be decrypted. Did you forget to add nix-plugins and point it to `/files/nix/extra-builtins.nix` ?"; + builtins.extraBuiltins.sopsImportEncrypted; + + importEncrypted = + path: + constSet ( + if builtins.pathExists path then + sopsImportEncrypted path + else + { } + ); +in +{ + den = { + schema.conf = { config, inputs, lib, homeLib, nodes, globals, ... }: { + options = { + repo = { + secretFiles = lib.mkOption { + default = { }; + type = lib.types.attrsOf lib.types.path; + example = lib.literalExpression "{ local = ./pii.nix.enc; }"; + description = '' + This is for storing PII. + Each path given here must be an sops-encrypted .nix file. For each attribute ``, + the corresponding file will be decrypted, imported and exposed as {option}`repo.secrets.`. + ''; + }; + + secrets = lib.mkOption { + readOnly = true; + default = lib.mapAttrs (_: x: importEncrypted x { inherit lib homeLib nodes globals inputs config; inherit (inputs.topologyPrivate) topologyPrivate; }) config.repo.secretFiles; + type = lib.types.unspecified; + description = "Exposes the loaded repo secrets."; + }; + }; + }; + config = { + repo.secretFiles = + let + local = config.node.secretsDir + "/pii.nix.enc"; + in + (lib.optionalAttrs (lib.pathExists local) { inherit local; }) // { + common = ../secrets/repo/pii.nix.enc; + }; + }; + }; + }; +} diff --git a/aspects/battery-sops.nix b/aspects/battery-sops.nix new file mode 100644 index 0000000..fd8eba6 --- /dev/null +++ b/aspects/battery-sops.nix @@ -0,0 +1,24 @@ +{ lib, den, ... }: +let + hostContext = { name, args, class }: { host }: { + nixos.sops.secrets.${name} = lib.mkIf (!host.isPublic) args // lib.optionalAttrs (class == "homeManager") { owner = host.mainUser; }; + }; + + # deadnix: skip + hostUserContext = { name, args, class }: { host, user }: { + nixos.sops.secrets.${name} = lib.mkIf (!host.isPublic) args // lib.optionalAttrs (class == "homeManager") { owner = host.mainUser; }; + }; + + homeContext = { name, args }: { home }: { + homeManager.sops.secrets.${name} = lib.mkIf (!home.isPublic) args; + }; + +in +{ + den.provides.sops = { name, args, class ? "homeManager" }: den.lib.parametric.exactly { + includes = [ + (hostContext { inherit name args class; }) + (hostUserContext { inherit name args class; }) + ] ++ lib.optional (class == "homeManager") (homeContext { inherit name args; }); + }; +} diff --git a/aspects/defaults.nix b/aspects/defaults.nix new file mode 100644 index 0000000..21de37d --- /dev/null +++ b/aspects/defaults.nix @@ -0,0 +1,19 @@ +{ lib, den, ... }: +{ + den = { + schema.user.classes = lib.mkDefault [ "homeManager" ]; + ctx.user.includes = [ den.provides.mutual-provider ]; + default = { + nixos = { lib, minimal, ... }: { + users.mutableUsers = lib.mkIf (!minimal) (lib.mkDefault false); + system.stateVersion = lib.mkDefault "23.05"; + }; + homeManager = { + home.stateVersion = lib.mkDefault "23.05"; + }; + includes = [ + den.provides.define-user + ]; + }; + }; +} diff --git a/aspects/hosts/pyramid.nix b/aspects/hosts/pyramid.nix new file mode 100644 index 0000000..b1dafe0 --- /dev/null +++ b/aspects/hosts/pyramid.nix @@ -0,0 +1,104 @@ +{ mkNixos, lib, den, ... }: +let + hostContext = { host }: + let + inherit (host) mainUser; + in + { + nixos = { self, inputs, lib, ... }: { + + imports = [ + inputs.nixos-hardware.nixosModules.framework-16-7040-amd + + "${self}/hosts/nixos/x86_64-linux/pyramid/disk-config.nix" + "${self}/hosts/nixos/x86_64-linux/pyramid/hardware-configuration.nix" + + "${self}/modules/nixos/optional/amdcpu.nix" + "${self}/modules/nixos/optional/amdgpu.nix" + "${self}/modules/nixos/optional/framework.nix" + "${self}/modules/nixos/optional/gaming.nix" + "${self}/modules/nixos/optional/hibernation.nix" + "${self}/modules/nixos/optional/nswitch-rcm.nix" + "${self}/modules/nixos/optional/virtualbox.nix" + "${self}/modules/nixos/optional/work.nix" + "${self}/modules/nixos/optional/niri.nix" + "${self}/modules/nixos/optional/noctalia.nix" + ]; + + topology.self = { + interfaces = { + eth1.network = lib.mkForce "home"; + wifi = { }; + fritz-wg.network = "fritz-wg"; + }; + }; + + swarselsystems = { + lowResolution = "1280x800"; + highResolution = "2560x1600"; + isLaptop = true; + isNixos = true; + isBtrfs = true; + isLinux = true; + sharescreen = "eDP-2"; + info = "Framework Laptop 16, 7940HS, RX7700S, 64GB RAM"; + firewall = lib.mkForce true; + wallpaper = self + /files/wallpaper/landscape/lenovowp.png; + hasBluetooth = true; + hasFingerprint = true; + isImpermanence = false; + isSecureBoot = true; + isCrypted = true; + inherit (host.repo.secrets.local) hostName; + inherit (host.repo.secrets.local) fqdn; + hibernation.offset = 533760; + }; + }; + + home-manager = { lib, minimal, ... }: { + users."${mainUser}" = { + swarselsystems = { + isSecondaryGpu = true; + SecondaryGpuCard = "pci-0000_03_00_0"; + cpuCount = 16; + temperatureHwmon = { + isAbsolutePath = true; + path = "/sys/devices/virtual/thermal/thermal_zone0/"; + input-filename = "temp4_input"; + }; + monitors = { + main = { + # name = "BOE 0x0BC9 Unknown"; + name = "BOE 0x0BC9"; + mode = "2560x1600"; + scale = "1"; + position = "2560,0"; + workspace = "15:L"; + output = "eDP-2"; + }; + }; + }; + }; + } // lib.optionalAttrs (!minimal) { + swarselprofiles = { + personal = true; + }; + + networking.nftables.firewall.zones.untrusted.interfaces = [ "wlan*" "enp*" ]; + }; + }; +in +lib.recursiveUpdate + (mkNixos + { + name = "pyramid"; + system = "x86_64-linux"; + }) +{ + den.aspects.pyramid = { + includes = [ + hostContext + den.aspects.work + ]; + }; +} diff --git a/aspects/shared.nix b/aspects/shared.nix new file mode 100644 index 0000000..831349f --- /dev/null +++ b/aspects/shared.nix @@ -0,0 +1,14 @@ +{ + den = { + schema.conf = { lib, ... }: { + options = { + isPublic = lib.mkEnableOption "mark this as a public config (= without secrets)"; + isMicroVM = lib.mkEnableOption "mark this config as a microvm"; + mainUser = lib.mkOption { + type = lib.types.str; + default = "swarsel"; + }; + }; + }; + }; +} diff --git a/aspects/shell.nix b/aspects/shell.nix new file mode 100644 index 0000000..6b22447 --- /dev/null +++ b/aspects/shell.nix @@ -0,0 +1,143 @@ +{ den, ... }: +{ + den.aspects.shell = { + provides.zsh = { + includes = [ + (den.provides.sops { name = "croc-password"; args = { }; }) + (den.provides.sops { name = "github-nixpkgs-review-token"; args = { }; }) + ]; + nixos = { pkgs, ... }: { + programs.zsh = { + enable = true; + enableCompletion = false; + }; + users.defaultUserShell = pkgs.zsh; + environment = { + shells = with pkgs; [ zsh ]; + pathsToLink = [ "/share/zsh" ]; + }; + }; + homeManager = { self, config, pkgs, lib, minimal, globals, confLib, arch, ... }: + let + inherit (config.swarselsystems) flakePath isNixos homeDir; + crocDomain = globals.services.croc.domain; + in + { + programs.zsh = { + enable = true; + } // lib.optionalAttrs (!minimal) { + shellAliases = + { + nb = "nix build"; + nbl = "nix build --builders \"\""; + nbo = "nix build --offline --builders \"\""; + nd = "nix develop"; + ns = "nix shell"; + hmswitch = lib.mkIf (!isNixos) "${lib.getExe pkgs.home-manager} --flake ${flakePath}#$(hostname) switch |& nom"; + nswitch = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) switch; cd -;"; + ntest = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) test; cd -;"; + nboot = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) boot; cd -;"; + ndry = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) dry-activate; cd -;"; + magit = "emacsclient -nc -e \"(magit-status)\""; + config = "git --git-dir=$HOME/.cfg/ --work-tree=$HOME"; + g = "git"; + c = "git --git-dir=$FLAKE/.git --work-tree=$FLAKE/"; + passpush = "cd ~/.local/share/password-store; git add .; git commit -m 'pass file changes'; git push; cd -;"; + passpull = "cd ~/.local/share/password-store; git pull; cd -;"; + hotspot = "nmcli connection up local; nmcli device wifi hotspot;"; + youtube-dl = "yt-dlp"; + cat-orig = "cat"; + # cdr = "cd \"$( (find $DOCUMENT_DIR_WORK $DOCUMENT_DIR_PRIV -maxdepth 1 && echo $FLAKE) | fzf )\""; + cdr = "source cdr"; + nix-ldd-ldd = "LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH ldd"; + nix-ldd = "LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH ldd"; + nix-ldd-locate = "nix-locate --minimal --top-level -w "; + nix-store-search = "ls /nix/store | grep"; + fs-diff = "sudo mount -o subvol=/ /dev/mapper/cryptroot /mnt ; fs-diff"; + lt = "eza -las modified --total-size"; + boot-diff = "nix store diff-closures /run/*-system"; + gen-diff = "nix profile diff-closures --profile /nix/var/nix/profiles/system"; + cc = "wl-copy"; + build-topology = "nix build --override-input topologyPrivate ${self}/files/topology/private ${flakePath}#topology.${arch}.config.output"; + build-topology-dev = "nix build --show-trace --override-input nix-topology ${homeDir}/Documents/Private/nix-topology --override-input topologyPrivate ${self}/files/topology/private ${flakePath}#topology.${arch}.config.output"; + build-iso = "nix build --print-out-paths .#live-iso"; + nix-review-local = "nix run nixpkgs#nixpkgs-review -- rev HEAD"; + nix-review-post = "nix run nixpkgs#nixpkgs-review -- pr --post-result --systems linux"; + }; + autosuggestion.enable = true; + enableCompletion = true; + syntaxHighlighting.enable = true; + autocd = false; + cdpath = [ + "~/.dotfiles" + ]; + defaultKeymap = "emacs"; + dirHashes = { + dl = "$HOME/Downloads"; + gh = "$HOME/Documents/GitHub"; + }; + history = { + expireDuplicatesFirst = true; + append = true; + ignoreSpace = true; + ignoreDups = true; + path = "${config.home.homeDirectory}/.histfile"; + save = 100000; + size = 100000; + }; + historySubstringSearch = { + enable = true; + searchDownKey = "^[OB"; + searchUpKey = "^[OA"; + }; + initContent = '' + my-forward-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle forward-word + } + zle -N my-forward-word + # ctrl + right + bindkey "^[[1;5C" my-forward-word + + # shift + right + bindkey "^[[1;2C" forward-word + + my-backward-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle backward-word + } + zle -N my-backward-word + # ctrl + left + bindkey "^[[1;5D" my-backward-word + + # shift + left + bindkey "^[[1;2D" backward-word + + my-backward-delete-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle backward-delete-word + } + zle -N my-backward-delete-word + # ctrl + del + bindkey '^H' my-backward-delete-word + ''; + sessionVariables = lib.mkIf (!config.swarselsystems.isPublic) { + CROC_RELAY = crocDomain; + CROC_PASS = "$(cat ${confLib.getConfig.sops.secrets.croc-password.path or ""})"; + GITHUB_TOKEN = "$(cat ${confLib.getConfig.sops.secrets.github-nixpkgs-review-token.path or ""})"; + QT_QPA_PLATFORM_PLUGIN_PATH = "${pkgs.libsForQt5.qt5.qtbase.bin}/lib/qt-${pkgs.libsForQt5.qt5.qtbase.version}/plugins"; + }; + }; + }; + }; + }; +} diff --git a/aspects/users.nix b/aspects/users.nix new file mode 100644 index 0000000..ff3e665 --- /dev/null +++ b/aspects/users.nix @@ -0,0 +1,46 @@ +{ den, ... }: +let + hostContext = { host, ... }: { + nixos = { minimal, lib, config, ... }: { + users.users.swarsel = { + uid = 1000; + autoSubUidGidRange = false; + subUidRanges = [ + { + count = 65534; + startUid = 100001; + } + ]; + subGidRanges = [ + { + count = 999; + startGid = 1001; + } + ]; + description = "Leon S"; + password = lib.mkIf (minimal || host.isPublic) "setup"; + hashedPasswordFile = lib.mkIf (!minimal && !host.isPublic) config.sops.secrets.main-user-hashed-pw.path; + extraGroups = lib.optionals (!minimal && !host.isMicroVM) [ "input" "syncthing" "docker" "lp" "audio" "video" "vboxusers" "libvirtd" "scanner" ]; + }; + }; + }; + +in +{ + + den = { + aspects.swarsel = { + includes = [ + hostContext + (den.provides.sops { class = "nixos"; name = "main-user-hashed-pw"; args = { neededForUsers = true; }; }) + den.provides.primary-user + (den.provides.user-shell "zsh") + ]; + }; + aspects.root = { globals, ... }: { + nixos = { + users.users.root = globals.root.hashedPassword; + }; + }; + }; +} diff --git a/aspects/work.nix b/aspects/work.nix new file mode 100644 index 0000000..b933a22 --- /dev/null +++ b/aspects/work.nix @@ -0,0 +1,996 @@ +{ self, den, ... }: +let + sopsFile = self + /secrets/work/secrets.yaml; + certsSopsFile = self + /secrets/repo/certs.yaml; + + hostContext = { host }: common host; + homeContext = { home }: common home; + + common = from: { + nixos = { lib, pkgs, config, ... }: + let + inherit (from) mainUser; + inherit (config.swarselsystems) homeDir; + iwd = config.networking.networkmanager.wifi.backend == "iwd"; + owner = mainUser; + in + { + options.swarselsystems = { + hostName = lib.mkOption { + type = lib.types.str; + default = config.node.name; + }; + fqdn = lib.mkOption { + type = lib.types.str; + default = ""; + }; + }; + config = { + + sops = + let + secretNames = [ + "vcuser" + "vcpw" + "govcuser" + "govcpw" + "govcurl" + "govcdc" + "govcds" + "govchost" + "govcnetwork" + "govcpool" + "baseuser" + "basepw" + ]; + in + { + secrets = builtins.listToAttrs ( + map + (name: { + inherit name; + value = { inherit owner sopsFile; }; + }) + secretNames + ); + templates = { + "network-manager-work.env".content = '' + BASEUSER=${config.sops.placeholder.baseuser} + BASEPASS=${config.sops.placeholder.basepw} + ''; + }; + }; + + boot.initrd = { + systemd.enable = lib.mkForce true; # make sure we are using initrd systemd even when not using Impermanence + luks = { + # disable "support" since we use systemd-cryptenroll + # make sure yubikeys are enrolled using + # sudo systemd-cryptenroll --fido2-device=auto --fido2-with-user-verification=no --fido2-with-user-presence=true --fido2-with-client-pin=no /dev/nvme0n1p2 + yubikeySupport = false; + fido2Support = false; + }; + }; + + programs = { + + browserpass.enable = true; + _1password.enable = true; + _1password-gui = { + enable = true; + package = pkgs._1password-gui-beta; + polkitPolicyOwners = [ "${mainUser}" ]; + }; + }; + + networking = { + inherit (config.swarselsystems) hostName fqdn; + + networkmanager = { + wifi.scanRandMacAddress = false; + ensureProfiles = { + environmentFiles = [ + "${config.sops.templates."network-manager-work.env".path}" + ]; + profiles = { + VBC = { + "802-1x" = { + eap = if (!iwd) then "ttls;" else "peap;"; + identity = "$BASEUSER"; + password = "$BASEPASS"; + phase2-auth = "mschapv2"; + }; + connection = { + id = "VBC"; + type = "wifi"; + autoconnect-priority = "500"; + uuid = "3988f10e-6451-381f-9330-a12e66f45051"; + secondaries = "48d09de4-0521-47d7-9bd5-43f97e23ff82"; # vpn uuid + }; + ipv4 = { method = "auto"; }; + ipv6 = { + # addr-gen-mode = "default"; + addr-gen-mode = "stable-privacy"; + method = "auto"; + }; + proxy = { }; + wifi = { + cloned-mac-address = "permanent"; + mac-address = "E8:65:38:52:63:FF"; + mac-address-randomization = "1"; + mode = "infrastructure"; + band = "a"; + ssid = "VBC"; + }; + wifi-security = { + # auth-alg = "open"; + key-mgmt = "wpa-eap"; + }; + }; + }; + }; + }; + + + nftables = { + firewall = { + zones = { + virbr = { + interfaces = [ "virbr*" ]; + }; + }; + rules = { + virbr-dns-dhcp = { + from = [ "virbr" ]; + to = [ "local" ]; + allowedTCPPorts = [ 53 ]; + allowedUDPPorts = [ 53 67 547 ]; + }; + virbr-forward = { + from = [ "virbr" ]; + to = [ "untrusted" ]; + verdict = "accept"; + }; + virbr-forward-return = { + from = [ "untrusted" ]; + to = [ "virbr" ]; + extraLines = [ + "ct state { established, related } accept" + ]; + }; + }; + }; + chains.postrouting.libvirt-masq = { + after = [ "dnat" ]; + rules = [ + "iifname \"virbr*\" masquerade" + ]; + }; + }; + + search = [ + "vbc.ac.at" + "clip.vbc.ac.at" + "imp.univie.ac.at" + ]; + }; + + systemd.services = { + virtqemud.path = with pkgs; [ + qemu_kvm + libvirt + ]; + + virtstoraged.path = with pkgs; [ + qemu_kvm + libvirt + ]; + + virtnetworkd.path = with pkgs; [ + dnsmasq + iproute2 + nftables + ]; + }; + + virtualisation = { + docker.enable = lib.mkIf (!config.virtualisation.podman.dockerCompat) true; + spiceUSBRedirection.enable = true; + libvirtd = { + enable = true; + qemu = { + package = pkgs.qemu_kvm; + runAsRoot = true; + swtpm.enable = true; + vhostUserPackages = with pkgs; [ virtiofsd ]; + }; + }; + }; + + environment.systemPackages = with pkgs; [ + remmina + python39 + qemu + packer + gnumake + libisoburn + govc + terraform + opentofu + terragrunt + graphviz + azure-cli + + # vm + virt-manager + virt-viewer + virtiofsd + spice + spice-gtk + spice-protocol + virtio-win + win-spice + + powershell + gh + ]; + + services = { + spice-vdagentd.enable = true; + openssh = { + enable = true; + extraConfig = '' + ''; + }; + + syncthing = { + settings = { + "winters" = { + id = "O7RWDMD-AEAHPP7-7TAVLKZ-BSWNBTU-2VA44MS-EYGUNBB-SLHKB3C-ZSLMOAA"; + }; + "moonside@oracle" = { + id = "VPCDZB6-MGVGQZD-Q6DIZW3-IZJRJTO-TCC3QUQ-2BNTL7P-AKE7FBO-N55UNQE"; + }; + folders = { + "Documents" = { + path = "${homeDir}/Documents"; + devices = [ "moonside@oracle" ]; + id = "hgr3d-pfu3w"; + }; + }; + }; + }; + + # udev.extraRules = '' + # # lock screen when yubikey removed + # ACTION=="remove", ENV{PRODUCT}=="3/1050/407/110", RUN+="${pkgs.systemd}/bin/systemctl suspend" + # ''; + + }; + + # cgroups v1 is required for centos7 dockers + # specialisation = { + # cgroup_v1.configuration = { + # boot.kernelParams = [ + # "SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1" + # "systemd.unified_cgroup_hierarchy=0" + # ]; + # }; + # }; + }; + }; + homeManager = { self, config, pkgs, lib, vars, ... }: + let + source = if (config ? home) then "home" else "host"; + inherit (config.swarselsystems) homeDir; + inherit (config.repo.secrets.local.mail) allMailAddresses; + inherit (config.repo.secrets.local.work) mailAddress; + + in + { + config = { + home = { + packages = with pkgs; [ + teams-for-linux + shellcheck + dig + docker + postman + # rclone + libguestfs-with-appliance + prometheus.cli + tigervnc + # openstackclient + step-cli + + vscode-fhs + copilot-cli + antigravity + + + rustdesk-vbc + ]; + sessionVariables = { + AWS_CA_BUNDLE = source.sops.secrets.harica-root-ca.path; + }; + }; + systemd.user.sessionVariables = { + DOCUMENT_DIR_WORK = lib.mkForce "${homeDir}/Documents/Work"; + } // lib.optionalAttrs (!config.swarselsystems.isPublic) { + SWARSEL_MAIL_ALL = lib.mkForce allMailAddresses; + SWARSEL_MAIL_WORK = lib.mkForce mailAddress; + }; + + accounts.email.accounts.work = + let + inherit (from.repo.secrets.local.work) mailName; + in + { + primary = false; + address = mailAddress; + userName = mailAddress; + realName = mailName; + passwordCommand = "pizauth show work"; + imap = { + host = "outlook.office365.com"; + port = 993; + tls.enable = true; # SSL/TLS + }; + smtp = { + host = "outlook.office365.com"; + port = 587; + tls = { + enable = true; # SSL/TLS + useStartTls = true; + }; + }; + thunderbird = { + enable = true; + profiles = [ "default" ]; + settings = id: { + "mail.smtpserver.smtp_${id}.authMethod" = 10; # oauth + "mail.server.server_${id}.authMethod" = 10; # oauth + # "toolkit.telemetry.enabled" = false; + # "toolkit.telemetry.rejected" = true; + # "toolkit.telemetry.prompted" = 2; + }; + }; + msmtp = { + enable = true; + extraConfig = { + auth = "xoauth2"; + host = "outlook.office365.com"; + protocol = "smtp"; + port = "587"; + tls = "on"; + tls_starttls = "on"; + from = "${mailAddress}"; + user = "${mailAddress}"; + passwordeval = "pizauth show work"; + }; + }; + mu.enable = true; + mbsync = { + enable = true; + expunge = "both"; + patterns = [ "INBOX" ]; + extraConfig = { + account = { + AuthMechs = "XOAUTH2"; + }; + }; + }; + }; + + wayland.windowManager.sway = + let + inherit (from.repo.secrets.local.work) user1 user1Long domain1 mailAddress; + in + { + config = { + keybindings = + let + inherit (config.wayland.windowManager.sway.config) modifier; + in + { + "${modifier}+Shift+d" = "exec ${pkgs.quickpass}/bin/quickpass work/adm/${user1}/${user1Long}@${domain1}"; + "${modifier}+Shift+i" = "exec ${pkgs.quickpass}/bin/quickpass work/${mailAddress}"; + }; + }; + }; + + stylix = { + targets.firefox.profileNames = + let + inherit (from.repo.secrets.local.work) user1 user2 user3; + in + [ + "${user1}" + "${user2}" + "${user3}" + "work" + ]; + }; + + programs = + let + inherit (from.repo.secrets.local.work) user1 user1Long user2 user2Long user3 user3Long path1 site1 site2 site3 site4 site5 site6 site7 clouds; + in + { + openstackclient = { + enable = true; + inherit clouds; + }; + awscli = { + enable = true; + package = pkgs.awscli2; + }; + + zsh = { + shellAliases = { + dssh = "ssh -l ${user1Long}"; + cssh = "ssh -l ${user2Long}"; + wssh = "ssh -l ${user3Long}"; + }; + cdpath = [ + "~/Documents/Work" + ]; + dirHashes = { + d = "$HOME/.dotfiles"; + w = "$HOME/Documents/Work"; + s = "$HOME/.dotfiles/secrets"; + pr = "$HOME/Documents/Private"; + ac = path1; + }; + + sessionVariables = { + VSPHERE_USER = "$(cat ${source.sops.secrets.vcuser.path})"; + VSPHERE_PW = "$(cat ${source.sops.secrets.vcpw.path})"; + GOVC_USERNAME = "$(cat ${source.sops.secrets.govcuser.path})"; + GOVC_PASSWORD = "$(cat ${source.sops.secrets.govcpw.path})"; + GOVC_URL = "$(cat ${source.sops.secrets.govcurl.path})"; + GOVC_DATACENTER = "$(cat ${source.sops.secrets.govcdc.path})"; + GOVC_DATASTORE = "$(cat ${source.sops.secrets.govcds.path})"; + GOVC_HOST = "$(cat ${source.sops.secrets.govchost.path})"; + GOVC_RESOURCE_POOL = "$(cat ${source.sops.secrets.govcpool.path})"; + GOVC_NETWORK = "$(cat ${source.sops.secrets.govcnetwork.path})"; + }; + }; + + ssh.matchBlocks = from.repo.secrets.local.work.sshConfig; + + firefox = { + profiles = + let + isDefault = false; + in + { + "${user1}" = lib.recursiveUpdate + { + inherit isDefault; + id = 1; + settings = { + "browser.startup.homepage" = "${site1}|${site2}"; + }; + } + vars.firefox; + "${user2}" = lib.recursiveUpdate + { + inherit isDefault; + id = 2; + settings = { + "browser.startup.homepage" = "${site3}"; + }; + } + vars.firefox; + "${user3}" = lib.recursiveUpdate + { + inherit isDefault; + id = 3; + } + vars.firefox; + work = lib.recursiveUpdate + { + inherit isDefault; + id = 4; + settings = { + "browser.startup.homepage" = "${site4}|${site5}|${site6}|${site7}"; + }; + } + vars.firefox; + }; + }; + + chromium = { + enable = true; + package = pkgs.chromium; + + extensions = [ + # 1password + "gejiddohjgogedgjnonbofjigllpkmbf" + # dark reader + "eimadpbcbfnmbkopoojfekhnkhdbieeh" + # ublock origin + "cjpalhdlnbpafiamejdnhcphjbkeiagm" + # i still dont care about cookies + "edibdbjcniadpccecjdfdjjppcpchdlm" + # browserpass + "naepdomgkenhinolocfifgehidddafch" + ]; + }; + }; + + services = { + + shikane = { + settings = + let + workRight = [ + "m=HP Z32" + "s=CN41212T55" + "v=HP Inc." + ]; + workLeft = [ + "m=HP 732pk" + "s=CNC4080YL5" + "v=HP Inc." + ]; + exec = [ "notify-send shikane \"Profile $SHIKANE_PROFILE_NAME has been applied\"" ]; + in + { + profile = [ + + { + name = "work-internal-on"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = true; + scale = 1.7; + position = "2560,0"; + } + { + match = workRight; + enable = true; + scale = 1.0; + mode = "3840x2160@60Hz"; + position = "-1280,0"; + } + { + match = workLeft; + enable = true; + scale = 1.0; + transform = "270"; + mode = "3840x2160@60Hz"; + position = "-3440,-1050"; + } + ]; + } + { + name = "work-internal-off"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = false; + scale = 1.7; + position = "2560,0"; + } + { + match = workRight; + enable = true; + scale = 1.0; + mode = "3840x2160@60Hz"; + position = "-1280,0"; + } + { + match = workLeft; + enable = true; + scale = 1.0; + transform = "270"; + mode = "3840x2160@60Hz"; + position = "-3440,-1050"; + } + ]; + } + + + ]; + }; + }; + kanshi = { + settings = [ + { + # seminary room + output = { + criteria = "Applied Creative Technology Transmitter QUATTRO201811"; + scale = 1.0; + mode = "1280x720"; + }; + } + { + # work side screen + output = { + criteria = "HP Inc. HP 732pk CNC4080YL5"; + scale = 1.0; + mode = "3840x2160"; + transform = "270"; + }; + } + # { + # # work side screen + # output = { + # criteria = "Hewlett Packard HP Z24i CN44250RDT"; + # scale = 1.0; + # mode = "1920x1200"; + # transform = "270"; + # }; + # } + { + # work main screen + output = { + criteria = "HP Inc. HP Z32 CN41212T55"; + scale = 1.0; + mode = "3840x2160"; + }; + } + { + profile = { + name = "lidopen"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${config.swarselsystems.sharescreen}' --image ${config.swarselsystems.wallpaper} --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP Z32 CN41212T55' --image ${self}/files/wallpaper/landscape/botanicswp.png --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP 732pk CNC4080YL5' --image ${self}/files/wallpaper/portrait/op6wp.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "enable"; + scale = 1.5; + position = "2560,0"; + } + { + criteria = "HP Inc. HP 732pk CNC4080YL5"; + scale = 1.0; + mode = "3840x2160"; + position = "-3440,-1050"; + transform = "270"; + } + { + criteria = "HP Inc. HP Z32 CN41212T55"; + scale = 1.0; + mode = "3840x2160"; + position = "-1280,0"; + } + ]; + }; + } + { + profile = + let + monitor = "Applied Creative Technology Transmitter QUATTRO201811"; + in + { + name = "lidopen"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${config.swarselsystems.sharescreen}' --image ${config.swarselsystems.wallpaper} --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output '${monitor}' --image ${self}/files/wallpaper/services/navidrome.png --mode ${config.stylix.imageScalingMode}" + "${pkgs.kanshare}/bin/kanshare ${config.swarselsystems.sharescreen} '${monitor}'" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "enable"; + scale = 1.7; + position = "2560,0"; + } + { + criteria = "Applied Creative Technology Transmitter QUATTRO201811"; + scale = 1.0; + mode = "1280x720"; + position = "10000,10000"; + } + ]; + }; + } + { + profile = { + name = "lidclosed"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP Z32 CN41212T55' --image ${self}/files/wallpaper/landscape/botanicswp.png --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output 'HP Inc. HP 732pk CNC4080YL5' --image ${self}/files/wallpaper/portrait/op6wp.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "disable"; + } + { + criteria = "HP Inc. HP 732pk CNC4080YL5"; + scale = 1.0; + mode = "3840x2160"; + position = "-3440,-1050"; + transform = "270"; + } + { + criteria = "HP Inc. HP Z32 CN41212T55"; + scale = 1.0; + mode = "3840x2160"; + position = "-1280,0"; + } + ]; + }; + } + { + profile = + let + monitor = "Applied Creative Technology Transmitter QUATTRO201811"; + in + { + name = "lidclosed"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${monitor}' --image ${self}/files/wallpaper/services/navidrome.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "disable"; + } + { + criteria = "Applied Creative Technology Transmitter QUATTRO201811"; + scale = 1.0; + mode = "1280x720"; + position = "10000,10000"; + } + ]; + }; + } + ]; + }; + }; + + systemd.user.services = { + pizauth.Service = { + ExecStartPost = [ + "${pkgs.toybox}/bin/sleep 1" + "//bin/sh -c '${lib.getExe pkgs.pizauth} restore < ${homeDir}/.pizauth.state'" + ]; + }; + + teams-applet = { + Unit = { + Description = "teams applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs.teams-for-linux}/bin/teams-for-linux --disableGpu=true --minimized=true --trayIconEnabled=true"; + }; + }; + + onepassword-applet = { + Unit = { + Description = "1password applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs._1password-gui-beta}/bin/1password"; + }; + }; + + }; + + services.pizauth = { + enable = true; + extraConfig = '' + auth_notify_cmd = "if [[ \"$(notify-send -A \"Open $PIZAUTH_ACCOUNT\" -t 30000 'pizauth authorisation')\" == \"0\" ]]; then open \"$PIZAUTH_URL\"; fi"; + error_notify_cmd = "notify-send -t 90000 \"pizauth error for $PIZAUTH_ACCOUNT\" \"$PIZAUTH_MSG\""; + token_event_cmd = "pizauth dump > ${homeDir}/.pizauth.state"; + ''; + accounts = { + work = { + authUri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; + tokenUri = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + clientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584"; + clientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82"; + scopes = [ + "https://outlook.office365.com/IMAP.AccessAsUser.All" + "https://outlook.office365.com/SMTP.Send" + "offline_access" + ]; + loginHint = "${from.repo.secrets.local.work.mailAddress}"; + }; + }; + + }; + + xdg = + let + inherit (from.repo.secrets.local.work) user1 user2 user3; + in + { + mimeApps = { + defaultApplications = { + "x-scheme-handler/msteams" = [ "teams-for-linux.desktop" ]; + }; + }; + desktopEntries = + let + terminal = false; + categories = [ "Application" ]; + icon = "firefox"; + in + { + firefox_work = { + name = "Firefox (work)"; + genericName = "Firefox work"; + exec = "firefox -p work"; + inherit terminal categories icon; + }; + "firefox_${user1}" = { + name = "Firefox (${user1})"; + genericName = "Firefox ${user1}"; + exec = "firefox -p ${user1}"; + inherit terminal categories icon; + }; + + "firefox_${user2}" = { + name = "Firefox (${user2})"; + genericName = "Firefox ${user2}"; + exec = "firefox -p ${user2}"; + inherit terminal categories icon; + }; + + "firefox_${user3}" = { + name = "Firefox (${user3})"; + genericName = "Firefox ${user3}"; + exec = "firefox -p ${user3}"; + inherit terminal categories icon; + }; + + + }; + }; + swarselsystems = { + startup = [ + # { command = "nextcloud --background"; } + # { command = "vesktop --start-minimized --enable-speech-dispatcher --ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime"; } + # { command = "element-desktop --hidden --enable-features=UseOzonePlatform --ozone-platform=wayland --disable-gpu-driver-bug-workarounds"; } + # { command = "anki"; } + # { command = "obsidian"; } + # { command = "nm-applet"; } + # { command = "feishin"; } + # { command = "teams-for-linux --disableGpu=true --minimized=true --trayIconEnabled=true"; } + # { command = "1password"; } + ]; + monitors = { + work_back_middle = rec { + name = "LG Electronics LG Ultra HD 0x000305A6"; + mode = "2560x1440"; + scale = "1"; + position = "5120,0"; + workspace = "1:一"; + # output = "DP-10"; + output = name; + }; + work_front_left = rec { + name = "LG Electronics LG Ultra HD 0x0007AB45"; + mode = "3840x2160"; + scale = "1"; + position = "5120,0"; + workspace = "1:一"; + # output = "DP-7"; + output = name; + }; + work_middle_middle_main = rec { + name = "HP Inc. HP Z32 CN41212T55"; + mode = "3840x2160"; + scale = "1"; + position = "-1280,0"; + workspace = "1:一"; + # output = "DP-3"; + output = name; + }; + # work_middle_middle_main = rec { + # name = "HP Inc. HP 732pk CNC4080YL5"; + # mode = "3840x2160"; + # scale = "1"; + # position = "-1280,0"; + # workspace = "11:M"; + # # output = "DP-8"; + # output = name; + # }; + work_middle_middle_side = rec { + name = "HP Inc. HP 732pk CNC4080YL5"; + mode = "3840x2160"; + transform = "270"; + scale = "1"; + position = "-3440,-1050"; + workspace = "12:S"; + # output = "DP-8"; + output = name; + }; + work_middle_middle_old = rec { + name = "Hewlett Packard HP Z24i CN44250RDT"; + mode = "1920x1200"; + transform = "270"; + scale = "1"; + position = "-2480,0"; + workspace = "12:S"; + # output = "DP-9"; + output = name; + }; + work_seminary = rec { + name = "Applied Creative Technology Transmitter QUATTRO201811"; + mode = "1280x720"; + scale = "1"; + position = "10000,10000"; # i.e. this screen is inaccessible by moving the mouse + workspace = "14:T"; + # output = "DP-4"; + output = name; + }; + }; + inputs = { + "1133:45081:MX_Master_2S_Keyboard" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + # "2362:628:PIXA3854:00_093A:0274_Touchpad" = { + # dwt = "enabled"; + # tap = "enabled"; + # natural_scroll = "enabled"; + # middle_emulation = "enabled"; + # drag_lock = "disabled"; + # }; + "1133:50504:Logitech_USB_Receiver" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + "1133:45944:MX_KEYS_S" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + }; + + }; + }; + }; + }; +in +{ + den = { + aspects.work = { + includes = [ + hostContext + homeContext + (den.provides.sops { name = "harica-root-ca"; args = { sopsFile = certsSopsFile; path = "/home/swarsel/.aws/certs/harica-root.pem"; }; }) + (den.provides.sops { name = "yubikey-1"; args = { inherit sopsFile; }; }) + (den.provides.sops { name = "ucKey"; args = { inherit sopsFile; }; }) + ]; + }; + }; +} diff --git a/files/nix/extra-builtins.nix b/files/nix/extra-builtins.nix index 0b433d9..a40bfcc 100644 --- a/files/nix/extra-builtins.nix +++ b/files/nix/extra-builtins.nix @@ -21,7 +21,7 @@ in assert assertMsg (hasSuffix ".nix.enc" nixFile) "The content of the decrypted file must be a nix expression and should therefore end in .nix.enc"; exec [ - ./files/scripts/sops-decrypt-and-cache.sh + ../scripts/sops-decrypt-and-cache.sh nixFile ]; } diff --git a/flake.lock b/flake.lock index dbcaf96..164168f 100644 --- a/flake.lock +++ b/flake.lock @@ -158,11 +158,11 @@ }, "den": { "locked": { - "lastModified": 1774890137, - "narHash": "sha256-ud23tRiZy+DONcw3a3WDIl+bYa+wY4ZrB8pHbRCLR+w=", + "lastModified": 1775073310, + "narHash": "sha256-wGlW57lzZy6dyA62zf3HC/w5fL6kS+prUdP2q4afiNQ=", "owner": "vic", "repo": "den", - "rev": "26a5d222f770069180ae147c6907d3875fa0056e", + "rev": "2b72a0d40861088caa7f203f9a1cf837d0ccc3bc", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 79f042f..249ac34 100644 --- a/flake.nix +++ b/flake.nix @@ -107,8 +107,23 @@ outputs = inputs: - inputs.flake-parts.lib.mkFlake { inherit inputs; } { - imports = [ (inputs.import-tree [ ./flake ]) ]; + let + mkNixos = { name, system }: { + den.hosts.${system} = { + ${name} = { host, ... }: { + instantiate = inputs.self.outputs.instantiateNixos { minimal = false; } host.name host.system; + users.swarsel = { }; + }; + + "${name}-minimal" = { host, ... }: { + instantiate = inputs.self.outputs.instantiateNixos { minimal = true; } host.name host.system; + intoAttr = [ "nixosConfigurationsMinimal" host.name ]; + }; + }; + }; + in + inputs.flake-parts.lib.mkFlake { inherit inputs; specialArgs = { inherit mkNixos; }; } { + imports = [ (inputs.import-tree [ ./flake ./aspects ]) ]; systems = [ "x86_64-linux" "aarch64-linux" diff --git a/flake/den.nix b/flake/den.nix index ebb69cc..3accf5d 100644 --- a/flake/den.nix +++ b/flake/den.nix @@ -1,12 +1,4 @@ -{ self, inputs, ... }: -let - inherit (self.outputs) lib; -in +{ inputs, ... }: { imports = [ inputs.den.flakeModule ]; - - den = { - schema.user.classes = lib.mkDefault [ "homeManager" ]; - default.homeManager.home.stateVersion = "23.05"; - }; } diff --git a/flake/hosts.nix b/flake/hosts.nix index 261e776..36f698c 100644 --- a/flake/hosts.nix +++ b/flake/hosts.nix @@ -1,84 +1,6 @@ { self, inputs, ... }: -let - inherit (self) outputs; - inherit (outputs) lib homeLib; -in { - den.hosts.x86_64-linux.pyramid = - let - configName = "pyramid"; - arch = "x86_64-linux"; - in - { - modules = [ - inputs.disko.nixosModules.disko - inputs.home-manager.nixosModules.home-manager - inputs.impermanence.nixosModules.impermanence - inputs.lanzaboote.nixosModules.lanzaboote - inputs.microvm.nixosModules.host - inputs.microvm.nixosModules.microvm - inputs.nix-index-database.nixosModules.nix-index - inputs.nix-minecraft.nixosModules.minecraft-servers - inputs.nix-topology.nixosModules.default - inputs.nswitch-rcm-nix.nixosModules.nswitch-rcm - inputs.simple-nixos-mailserver.nixosModules.default - inputs.sops.nixosModules.sops - inputs.stylix.nixosModules.stylix - inputs.swarsel-nix.nixosModules.default - inputs.nixos-nftables-firewall.nixosModules.default - inputs.pia.nixosModules.default - inputs.niritiling.nixosModules.default - inputs.noctoggle.nixosModules.default - (inputs.nixos-extra-modules + "/modules/guests") - (inputs.nixos-extra-modules + "/modules/interface-naming.nix") - "${self}/hosts/nixos/${arch}/${configName}" - "${self}/profiles/nixos" - "${self}/modules/nixos" - { - _module.args.dns = inputs.dns; - - microvm.guest.enable = lib.mkDefault false; - - networking.hostName = lib.swarselsystems.mkStrong configName; - - node = { - name = lib.mkForce configName; - arch = lib.mkForce arch; - type = lib.mkForce "nixos"; - secretsDir = ../hosts/nixos/${arch}/${configName}/secrets; - configDir = ../hosts/nixos/${arch}/${configName}; - lockFromBootstrapping = lib.swarselsystems.mkStrong true; - }; - - swarselprofiles = { - minimal = lib.swarselsystems.mkStrong true; - }; - - swarselmodules.server = { - ssh = lib.swarselsystems.mkStrong true; - }; - - swarselsystems = { - mainUser = lib.swarselsystems.mkStrong "swarsel"; - }; - } - ]; - users.swarsel = { }; - instantiate = inputs.nixpkgs.lib.nixosSystem { - specialArgs = { - inherit inputs outputs self homeLib configName arch; - minimal = false; - inherit (outputs.pkgs.${arch}) lib; - inherit (outputs) nodes topologyPrivate; - globals = outputs.globals.${arch}; - type = "nixos"; - withHomeManager = true; - extraModules = [ "${self}/modules/nixos/common/globals.nix" ]; - }; - }; - }; - flake = { config, ... }: let inherit (self) outputs; diff --git a/flake/instantiate.nix b/flake/instantiate.nix new file mode 100644 index 0000000..d7fe674 --- /dev/null +++ b/flake/instantiate.nix @@ -0,0 +1,74 @@ +{ self, inputs, ... }: +{ + flake = { config, ... }: { + instantiateNixos = { minimal }: configName: arch: { modules }: + let + inherit (self) outputs; + inherit (outputs) lib homeLib; + mkStrong = lib.mkOverride 60; + in + inputs.nixpkgs.lib.nixosSystem { + specialArgs = { + inherit inputs outputs self homeLib configName arch minimal; + inherit (config.pkgs.${arch}) lib; + inherit (config) nodes topologyPrivate; + globals = config.globals.${arch}; + type = "nixos"; + withHomeManager = true; + extraModules = [ "${self}/modules-clone/nixos/common/globals.nix" ]; + }; + modules = modules ++ [ + inputs.disko.nixosModules.disko + inputs.home-manager.nixosModules.home-manager + inputs.impermanence.nixosModules.impermanence + inputs.lanzaboote.nixosModules.lanzaboote + inputs.microvm.nixosModules.host + inputs.microvm.nixosModules.microvm + inputs.nix-index-database.nixosModules.nix-index + inputs.nix-minecraft.nixosModules.minecraft-servers + inputs.nix-topology.nixosModules.default + inputs.nswitch-rcm-nix.nixosModules.nswitch-rcm + inputs.simple-nixos-mailserver.nixosModules.default + inputs.sops.nixosModules.sops + inputs.stylix.nixosModules.stylix + inputs.swarsel-nix.nixosModules.default + inputs.nixos-nftables-firewall.nixosModules.default + inputs.pia.nixosModules.default + inputs.niritiling.nixosModules.default + inputs.noctoggle.nixosModules.default + (inputs.nixos-extra-modules + "/modules/guests") + (inputs.nixos-extra-modules + "/modules/interface-naming.nix") + "${self}/profiles-clone/nixos" + "${self}/modules-clone/nixos" + { + _module.args.dns = inputs.dns; + + microvm.guest.enable = lib.mkDefault false; + + networking.hostName = mkStrong configName; + + node = { + name = lib.mkForce configName; + arch = lib.mkForce arch; + type = lib.mkForce "nixos"; + secretsDir = ../hosts/nixos/${arch}/${configName}/secrets; + configDir = ../hosts/nixos/${arch}/${configName}; + lockFromBootstrapping = lib.mkIf (!minimal) (mkStrong true); + }; + + swarselprofiles = { + minimal = lib.mkIf minimal (mkStrong true); + }; + + swarselmodules.server = { + ssh = lib.mkIf (!minimal) (mkStrong true); + }; + + swarselsystems = { + mainUser = mkStrong "swarsel"; + }; + } + ]; + }; + }; +} diff --git a/modules-clone/home/common/anki-tray.nix b/modules-clone/home/common/anki-tray.nix new file mode 100644 index 0000000..60ae9a4 --- /dev/null +++ b/modules-clone/home/common/anki-tray.nix @@ -0,0 +1,38 @@ +{ lib, config, ... }: +{ + options.swarselmodules.anki-tray = lib.mkEnableOption "enable anki applet for tray"; + config = lib.mkIf config.swarselmodules.anki-tray { + + systemd.user.services.anki-applet = { + Unit = { + Description = "Anki applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + # ExecStart = "${lib.getExe config.programs.anki.package}"; + Type = "simple"; + ExecStart = "/etc/profiles/per-user/${config.swarselsystems.mainUser}/bin/anki"; + Environment = [ + "QT_QPA_PLATFORM=xcb" + ]; + TimeoutStopSec = "2s"; + KillMode = "mixed"; + KillSignal = "SIGTERM"; + SendSIGKILL = "yes"; + }; + }; + + }; +} diff --git a/modules-clone/home/common/anki.nix b/modules-clone/home/common/anki.nix new file mode 100644 index 0000000..e757c2e --- /dev/null +++ b/modules-clone/home/common/anki.nix @@ -0,0 +1,66 @@ +{ lib, config, pkgs, globals, confLib, type, ... }: +let + moduleName = "anki"; + inherit (config.swarselsystems) isPublic isNixos; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} + ({ + + programs.anki = { + enable = true; + package = pkgs.anki; + hideBottomBar = true; + hideBottomBarMode = "always"; + hideTopBar = true; + hideTopBarMode = "always"; + reduceMotion = true; + spacebarRatesCard = true; + # videoDriver = "opengl"; + profiles."User 1".sync = { + autoSync = false; # sync on profile close will delay system shutdown + syncMedia = true; + autoSyncMediaMinutes = 5; + url = "https://${globals.services.ankisync.domain}"; + usernameFile = confLib.getConfig.sops.secrets.anki-user.path; + # this is not the password but the syncKey + # get it by logging in or out, saving preferences and then + # show details on the "settings wont be saved" dialog + keyFile = confLib.getConfig.sops.secrets.anki-pw.path; + }; + addons = + let + minimize-to-tray = pkgs.anki-utils.buildAnkiAddon + (finalAttrs: { + pname = "minimize-to-tray"; + version = "2.0.1"; + src = pkgs.fetchFromGitHub { + owner = "simgunz"; + repo = "anki21-addons_minimize-to-tray"; + rev = finalAttrs.version; + sparseCheckout = [ "src" ]; + hash = "sha256-xmvbIOfi9K0yEUtUNKtuvv2Vmqrkaa4Jie6J1s+FuqY="; + }; + sourceRoot = "${finalAttrs.src.name}/src"; + }); + in + [ + (minimize-to-tray.withConfig + { + config = { + hide_on_startup = "true"; + }; + }) + ]; + }; + } // lib.optionalAttrs (type != "nixos") { + sops = lib.mkIf (!isPublic && !isNixos) { + secrets = { + anki-user = { }; + anki-pw = { }; + }; + }; + }); + +} diff --git a/modules-clone/home/common/attic-store-push.nix b/modules-clone/home/common/attic-store-push.nix new file mode 100644 index 0000000..197c625 --- /dev/null +++ b/modules-clone/home/common/attic-store-push.nix @@ -0,0 +1,26 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.attic-store-push = lib.mkEnableOption "enable automatic attic store push"; + config = lib.mkIf config.swarselmodules.attic-store-push { + + systemd.user.services.attic-store-push = { + Unit = { + Description = "Attic store pusher"; + Requires = [ "graphical-session.target" ]; + After = [ "graphical-session.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + + Service = { + ExecStart = "${lib.getExe pkgs.attic-client} watch-store ${config.swarselsystems.mainUser}:${config.swarselsystems.mainUser}"; + Restart = "on-failure"; + RestartSec = 30; + }; + }; + }; + +} diff --git a/modules-clone/home/common/atuin.nix b/modules-clone/home/common/atuin.nix new file mode 100644 index 0000000..f2d79ea --- /dev/null +++ b/modules-clone/home/common/atuin.nix @@ -0,0 +1,19 @@ +{ lib, config, globals, ... }: +let + atuinDomain = globals.services.atuin.domain; +in +{ + options.swarselmodules.atuin = lib.mkEnableOption "atuin settings"; + config = lib.mkIf config.swarselmodules.atuin { + programs.atuin = { + enable = true; + enableZshIntegration = true; + enableBashIntegration = true; + settings = { + auto_sync = true; + sync_frequency = "5m"; + sync_address = "https://${atuinDomain}"; + }; + }; + }; +} diff --git a/modules-clone/home/common/autotiling.nix b/modules-clone/home/common/autotiling.nix new file mode 100644 index 0000000..bcb4508 --- /dev/null +++ b/modules-clone/home/common/autotiling.nix @@ -0,0 +1,14 @@ +{ lib, config, ... }: +let + moduleName = "autotiling"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + services.${moduleName} = { + enable = true; + systemdTarget = "sway-session.target"; + }; + }; + +} diff --git a/modules-clone/home/common/bash.nix b/modules-clone/home/common/bash.nix new file mode 100644 index 0000000..ccf99c4 --- /dev/null +++ b/modules-clone/home/common/bash.nix @@ -0,0 +1,20 @@ +{ config, lib, ... }: +{ + options.swarselmodules.bash = lib.mkEnableOption "bash settings"; + config = lib.mkIf config.swarselmodules.bash { + + programs.bash = { + enable = true; + # needed for remote builders + bashrcExtra = lib.mkIf (!config.swarselsystems.isNixos) '' + export PATH="/nix/var/nix/profiles/default/bin:$PATH" + ''; + historyFile = "${config.home.homeDirectory}/.histfile"; + historySize = 100000; + historyFileSize = 100000; + historyControl = [ + "ignoreboth" + ]; + }; + }; +} diff --git a/modules-clone/home/common/batsignal.nix b/modules-clone/home/common/batsignal.nix new file mode 100644 index 0000000..a1e1b70 --- /dev/null +++ b/modules-clone/home/common/batsignal.nix @@ -0,0 +1,25 @@ +{ lib, config, ... }: +let + moduleName = "batsignal"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + services.${moduleName} = { + enable = true; + extraArgs = [ + "-W" + " Consider charging the battery" + "-C" + " Battery is low; plug in charger now" + "-D" + " Device will lose power in a few seconds" + "-c" + "10" + "-d" + "5" + ]; + }; + }; + +} diff --git a/modules-clone/home/common/blueman-applet.nix b/modules-clone/home/common/blueman-applet.nix new file mode 100644 index 0000000..26e5edd --- /dev/null +++ b/modules-clone/home/common/blueman-applet.nix @@ -0,0 +1,7 @@ +{ lib, config, ... }: +{ + options.swarselmodules.blueman-applet = lib.mkEnableOption "enable blueman applet for tray"; + config = lib.mkIf config.swarselmodules.blueman-applet { + services.blueman-applet.enable = true; + }; +} diff --git a/modules-clone/home/common/custom-packages.nix b/modules-clone/home/common/custom-packages.nix new file mode 100644 index 0000000..7cb8ea7 --- /dev/null +++ b/modules-clone/home/common/custom-packages.nix @@ -0,0 +1,42 @@ +{ lib, config, pkgs, ... }: + +{ + options.swarselmodules.ownpackages = lib.mkEnableOption "own packages settings"; + config = lib.mkIf config.swarselmodules.ownpackages { + home.packages = with pkgs; lib.mkIf (!config.swarselsystems.isPublic) [ + pass-fuzzel + cdw + cdb + cdr + bak + timer + e + niri-resize + swarselcheck + swarselcheck-niri + waybarupdate + opacitytoggle + fs-diff + github-notifications + hm-specialisation + t2ts + ts2t + vershell + eontimer + project + fhs + swarsel-bootstrap + swarsel-displaypower + swarsel-deploy + swarsel-instantiate + swarselzellij + sshrm + endme + git-replace + prstatus + swarsel-gens + swarsel-switch + swarsel-sops + ]; + }; +} diff --git a/modules-clone/home/common/default.nix b/modules-clone/home/common/default.nix new file mode 100644 index 0000000..382f4dd --- /dev/null +++ b/modules-clone/home/common/default.nix @@ -0,0 +1,9 @@ +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/home/common"; + sharedNames = lib.swarselsystems.readNix "modules-clone/shared"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/home/common" ++ + lib.swarselsystems.mkImports sharedNames "modules-clone/shared"; +} diff --git a/modules-clone/home/common/desktop.nix b/modules-clone/home/common/desktop.nix new file mode 100644 index 0000000..cd37b36 --- /dev/null +++ b/modules-clone/home/common/desktop.nix @@ -0,0 +1,107 @@ +{ lib, config, ... }: +{ + options.swarselmodules.desktop = lib.mkEnableOption "desktop settings"; + config = lib.mkIf config.swarselmodules.desktop { + xdg.desktopEntries = { + + cura = { + name = "Ultimaker Cura"; + genericName = "Cura"; + exec = "cura"; + terminal = false; + categories = [ "Application" ]; + }; + + teamsNoGpu = { + name = "Microsoft Teams (no GPU)"; + genericName = "Teams (no GPU)"; + exec = "teams-for-linux --disableGpu=true --trayIconEnabled=true"; + terminal = false; + categories = [ "Application" ]; + }; + + rustdesk-vbc = { + name = "Rustdesk VBC"; + genericName = "rustdesk-vbc"; + exec = "rustdesk-vbc"; + terminal = false; + categories = [ "Application" ]; + }; + + anki = { + name = "Anki Flashcards"; + genericName = "Anki"; + exec = "anki"; + terminal = false; + categories = [ "Application" ]; + }; + + element = { + name = "Element Matrix Client"; + genericName = "Element"; + exec = "element-desktop -enable-features=UseOzonePlatform -ozone-platform=wayland --disable-gpu-driver-bug-workarounds"; + terminal = false; + categories = [ "Application" ]; + }; + + emacsclient-newframe = { + name = "Emacs (Client, New Frame)"; + genericName = "Emacs (Client, New Frame)"; + exec = "emacsclient -r %u"; + icon = "emacs"; + terminal = false; + categories = [ "Development" "TextEditor" ]; + }; + + }; + + xdg = { + configFile."mimeapps.list".force = true; + mimeApps = { + enable = true; + defaultApplications = { + "application/epub+zip" = [ "calibre-ebook-viewer.desktop" ]; + "application/metalink+xml" = [ "emacsclient.desktop" ]; + "application/msword" = [ "writer.desktop" ]; + "application/pdf" = [ "org.gnome.Evince.desktop" ]; + "application/sql" = [ "emacsclient.desktop" ]; + "application/vnd.ms-excel" = [ "calc.desktop" ]; + "application/vnd.ms-powerpoint" = [ "impress.desktop" ]; + "application/x-extension-htm" = [ "firefox.desktop" ]; + "application/x-extension-html" = [ "firefox.desktop" ]; + "application/x-extension-shtml" = [ "firefox.desktop" ]; + "application/x-extension-xht" = [ "firefox.desktop" ]; + "application/x-extension-xhtml" = [ "firefox.desktop" ]; + "application/xhtml+xml" = [ "firefox.desktop" ]; + "audio/flac" = [ "mpv.desktop" ]; + "audio/mp3" = [ "mpv.desktop" ]; + "audio/ogg" = [ "mpv.desktop" ]; + "audio/wav" = [ "mpv.desktop" ]; + "image/gif" = [ "imv.desktop" ]; + "image/jpeg" = [ "imv.desktop" ]; + "image/png" = [ "imv.desktop" ]; + "image/svg" = [ "imv.desktop" ]; + "image/vnd.adobe.photoshop" = [ "gimp.desktop" ]; + "image/vnd.dxf" = [ "org.inkscape.Inkscape.desktop" ]; + "image/webp" = [ "firefox.desktop" ]; + "text/csv" = [ "emacsclient.desktop" ]; + "text/html" = [ "firefox.desktop" ]; + "text/plain" = [ "emacsclient.desktop" ]; + "video/3gp" = [ "umpv.desktop" ]; + "video/flv" = [ "umpv.desktop" ]; + "video/mkv" = [ "umpv.desktop" ]; + "video/mp4" = [ "umpv.desktop" ]; + "x-scheme-handler/chrome" = [ "firefox.desktop" ]; + "x-scheme-handler/http" = [ "firefox.desktop" ]; + "x-scheme-handler/https" = [ "firefox.desktop" ]; + }; + associations = { + added = { + "application/x-zerosize" = [ "emacsclient.desktop" ]; + "application/epub+zip" = [ "calibre-ebook-viewer.desktop" ]; + }; + }; + }; + }; + }; +} diff --git a/modules-clone/home/common/direnv.nix b/modules-clone/home/common/direnv.nix new file mode 100644 index 0000000..ea72d7d --- /dev/null +++ b/modules-clone/home/common/direnv.nix @@ -0,0 +1,11 @@ +{ lib, config, ... }: +{ + options.swarselmodules.direnv = lib.mkEnableOption "direnv settings"; + config = lib.mkIf config.swarselmodules.direnv { + programs.direnv = { + enable = true; + silent = true; + nix-direnv.enable = true; + }; + }; +} diff --git a/modules-clone/home/common/element-tray.nix b/modules-clone/home/common/element-tray.nix new file mode 100644 index 0000000..fdcba33 --- /dev/null +++ b/modules-clone/home/common/element-tray.nix @@ -0,0 +1,29 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.element-tray = lib.mkEnableOption "enable element applet for tray"; + config = lib.mkIf config.swarselmodules.element-tray { + + systemd.user.services.element-applet = { + Unit = { + Description = "Element applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs.element-desktop}/bin/element-desktop --hidden --enable-features=useozoneplatform --ozone-platform=wayland --disable-gpu-driver-bug-workarounds"; + }; + }; + }; + +} diff --git a/modules-clone/home/common/element.nix b/modules-clone/home/common/element.nix new file mode 100644 index 0000000..0398726 --- /dev/null +++ b/modules-clone/home/common/element.nix @@ -0,0 +1,29 @@ +{ lib, config, globals, ... }: +let + moduleName = "element-desktop"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.element-desktop = { + enable = true; + settings = { + default_server_config = { + "m.homeserver" = { + base_url = "https://${globals.services.matrix.domain}/"; + }; + }; + UIFeature = { + feedback = false; + voip = false; + widgets = false; + shareSocial = false; + registration = false; + passwordReset = false; + deactivate = false; + }; + }; + }; + }; + +} diff --git a/modules-clone/home/common/emacs.nix b/modules-clone/home/common/emacs.nix new file mode 100644 index 0000000..6d6abce --- /dev/null +++ b/modules-clone/home/common/emacs.nix @@ -0,0 +1,126 @@ +{ self, lib, config, pkgs, globals, inputs, type, ... }: +let + inherit (config.swarselsystems) homeDir mainUser isPublic isNixos; + inherit (config.repo.secrets.common.emacs) radicaleUser; +in +{ + options.swarselmodules.emacs = lib.mkEnableOption "emacs settings"; + config = lib.mkIf config.swarselmodules.emacs ({ + # needed for elfeed + # enable emacs overlay for bleeding edge features + # also read init.el file and install use-package packages + + home.activation.setupEmacsOrgFiles = + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + set -eu + + if [ ! -d ${homeDir}/Org ]; then + ${pkgs.coreutils}/bin/install -d -m 0755 ${homeDir}/Org + ${pkgs.coreutils}/bin/chown ${mainUser}:syncthing ${homeDir}/Org + fi + + # create dummy files to make Emacs calendar work + # these have low modified dates and should be marked as sync-conflicts + for file in "Tasks" "Archive" "Journal"; do + if [ ! -f ${homeDir}/Org/"$file".org ]; then + ${pkgs.coreutils}/bin/touch --time=access --time=modify -t 197001010000.00 ${homeDir}/Org/"$file".org + ${pkgs.coreutils}/bin/chown ${mainUser}:syncthing ${homeDir}/Org/"$file".org + fi + done + + # when the configuration is build again, these sync-conflicts will be cleaned up + for file in $(find ${homeDir}/Org/ -name "*sync-conflict*"); do + ${pkgs.coreutils}/bin/rm "$file" + done + ''; + + programs.emacs = { + enable = true; + package = pkgs.emacsWithPackagesFromUsePackage { + config = self + /files/emacs/init.el; + package = pkgs.emacs-unstable-pgtk; + alwaysEnsure = true; + alwaysTangle = true; + extraEmacsPackages = epkgs: [ + epkgs.mu4e + epkgs.use-package + epkgs.lsp-bridge + epkgs.doom-themes + epkgs.vterm + # pkgs.stable.emacs.pkgs.elpaPackages.tramp # use the unstable version from elpa + epkgs.treesit-grammars.with-all-grammars + + # build the rest of the packages myself + # org-calfw is severely outdated on MELPA and throws many warnings on emacs startup + # build the package from the haji-ali fork, which is well-maintained + + (epkgs.trivialBuild rec { + pname = "eglot-booster"; + version = "main-29-10-2024"; + + src = pkgs.fetchFromGitHub { + owner = "jdtsmith"; + repo = "eglot-booster"; + rev = "e6daa6bcaf4aceee29c8a5a949b43eb1b89900ed"; + hash = "sha256-PLfaXELkdX5NZcSmR1s/kgmU16ODF8bn56nfTh9g6bs="; + }; + + packageRequires = [ epkgs.jsonrpc epkgs.eglot ]; + }) + (inputs.nixpkgs-dev.legacyPackages.${pkgs.stdenv.hostPlatform.system}.emacsPackagesFor pkgs.emacs-git-pgtk).calfw + # epkgs.calfw + # (epkgs.trivialBuild rec { + # pname = "calfw"; + # version = "1.0.0-20231002"; + # src = pkgs.fetchFromGitHub { + # owner = "haji-ali"; + # repo = "emacs-calfw"; + # rev = "bc99afee611690f85f0cd0bd33300f3385ddd3d3"; + # hash = "sha256-0xMII1KJhTBgQ57tXJks0ZFYMXIanrOl9XyqVmu7a7Y="; + # }; + # packageRequires = [ epkgs.howm ]; + # }) + + (epkgs.trivialBuild rec { + pname = "fast-scroll"; + version = "1.0.0-20191016"; + src = pkgs.fetchFromGitHub { + owner = "ahungry"; + repo = "fast-scroll"; + rev = "3f6ca0d5556fe9795b74714304564f2295dcfa24"; + hash = "sha256-w1wmJW7YwXyjvXJOWdN2+k+QmhXr4IflES/c2bCX3CI="; + }; + packageRequires = [ ]; + }) + + ]; + }; + }; + + services.emacs = { + enable = true; + socketActivation.enable = false; + startWithUserSession = "graphical"; + }; + + } // lib.optionalAttrs (type != "nixos") { + + sops = lib.mkIf (!isPublic && !isNixos) { + secrets = { + fever-pw = { path = "${homeDir}/.emacs.d/.fever"; }; + emacs-radicale-pw = { }; + github-forge-token = { }; + }; + templates = { + authinfo = { + path = "${homeDir}/.emacs.d/.authinfo"; + content = '' + machine ${globals.services.radicale.domain} login ${radicaleUser} password ${config.sops.placeholder.emacs-radicale-pw} + machine api.github.com login ${mainUser}^forge password ${config.sops.placeholder.github-forge-token} + ''; + }; + }; + }; + + }); +} diff --git a/modules-clone/home/common/env.nix b/modules-clone/home/common/env.nix new file mode 100644 index 0000000..4fb6ae4 --- /dev/null +++ b/modules-clone/home/common/env.nix @@ -0,0 +1,43 @@ +{ lib, config, confLib, globals, ... }: +let + inherit (confLib.getConfig.repo.secrets.common.mail) address1 address2 address3 address4 allMailAddresses; + inherit (confLib.getConfig.repo.secrets.common.calendar) source1 source1-name source2 source2-name source3 source3-name; + inherit (confLib.getConfig.repo.secrets.common) fullName openrouterApi instaDomain sportDomain; + inherit (config.swarselsystems) isPublic homeDir; + + DISPLAY = ":0"; +in +{ + options.swarselmodules.env = lib.mkEnableOption "env settings"; + config = lib.mkIf config.swarselmodules.env { + home.sessionVariables = { + inherit DISPLAY; + EDITOR = "e -w"; + } // (lib.optionalAttrs (!isPublic) { }); + systemd.user.sessionVariables = { + DOCUMENT_DIR_PRIV = lib.mkForce "${homeDir}/Documents/Private"; + FLAKE = "${config.home.homeDirectory}/.dotfiles"; + } // lib.optionalAttrs (!isPublic) { + SWARSEL_DOMAIN = globals.domains.main; + SWARSEL_RSS_DOMAIN = globals.services.freshrss.domain; + SWARSEL_MUSIC_DOMAIN = globals.services.navidrome.domain; + SWARSEL_FILES_DOMAIN = globals.services.nextcloud.domain; + SWARSEL_INSTA_DOMAIN = instaDomain; + SWARSEL_SPORT_DOMAIN = sportDomain; + SWARSEL_MAIL1 = address1; + SWARSEL_MAIL2 = address2; + SWARSEL_MAIL3 = address3; + SWARSEL_MAIL4 = address4; + SWARSEL_CAL1 = source1; + SWARSEL_CAL1NAME = source1-name; + SWARSEL_CAL2 = source2; + SWARSEL_CAL2NAME = source2-name; + SWARSEL_CAL3 = source3; + SWARSEL_CAL3NAME = source3-name; + SWARSEL_FULLNAME = fullName; + SWARSEL_MAIL_ALL = lib.mkDefault allMailAddresses; + GITHUB_NOTIFICATION_TOKEN_PATH = confLib.getConfig.sops.secrets.github-notifications-token.path; + OPENROUTER_API_KEY = openrouterApi; + }; + }; +} diff --git a/modules-clone/home/common/eza.nix b/modules-clone/home/common/eza.nix new file mode 100644 index 0000000..56316f6 --- /dev/null +++ b/modules-clone/home/common/eza.nix @@ -0,0 +1,15 @@ +{ lib, config, ... }: +{ + options.swarselmodules.eza = lib.mkEnableOption "eza settings"; + config = lib.mkIf config.swarselmodules.eza { + programs.eza = { + enable = true; + icons = "auto"; + git = true; + extraOptions = [ + "-l" + "--group-directories-first" + ]; + }; + }; +} diff --git a/modules-clone/home/common/firefox.nix b/modules-clone/home/common/firefox.nix new file mode 100644 index 0000000..05bd393 --- /dev/null +++ b/modules-clone/home/common/firefox.nix @@ -0,0 +1,155 @@ +{ config, pkgs, lib, vars, ... }: +{ + options.swarselmodules.firefox = lib.mkEnableOption "firefox settings"; + config = lib.mkIf config.swarselmodules.firefox { + + programs.zsh.sessionVariables = { + MOZ_DISABLE_RDD_SANDBOX = "1"; + }; + + programs.firefox = { + enable = true; + package = pkgs.firefox; # uses overrides + policies = { + # CaptivePortal = false; + AppAutoUpdate = false; + BackgroundAppUpdate = false; + DisableBuiltinPDFViewer = true; + DisableFirefoxStudies = true; + DisablePocket = true; + DisableFirefoxScreenshots = true; + DisableTelemetry = true; + DisableFirefoxAccounts = false; + DisableProfileImport = true; + DisableProfileRefresh = true; + DisplayBookmarksToolbar = "always"; + DontCheckDefaultBrowser = true; + NoDefaultBookmarks = true; + OfferToSaveLogins = false; + OfferToSaveLoginsDefault = false; + PasswordManagerEnabled = false; + DisableMasterPasswordCreation = true; + ExtensionUpdate = false; + EnableTrackingProtection = { + Value = true; + Locked = true; + Cryptomining = true; + Fingerprinting = true; + EmailTracking = true; + # Exceptions = ["https://example.com"] + }; + PDFjs = { + Enabled = false; + EnablePermissions = false; + }; + Handlers = { + mimeTypes."application/pdf".action = "saveToDisk"; + }; + extensions = { + pdf = { + action = "useHelperApp"; + ask = true; + handlers = [ + { + name = "GNOME Document Viewer"; + path = "${pkgs.evince}/bin/evince"; + } + ]; + }; + }; + FirefoxHome = { + Search = true; + TopSites = true; + SponsoredTopSites = false; + Highlights = true; + Pocket = false; + SponsoredPocket = false; + Snippets = false; + Locked = true; + }; + FirefoxSuggest = { + WebSuggestions = false; + SponsoredSuggestions = false; + ImproveSuggest = false; + Locked = true; + }; + SanitizeOnShutdown = { + Cache = true; + Cookies = false; + Downloads = true; + FormData = true; + History = false; + Sessions = false; + SiteSettings = false; + OfflineApps = true; + Locked = true; + }; + SearchEngines = { + PreventInstalls = true; + Remove = [ + "Bing" # Fuck you + ]; + }; + UserMessaging = { + ExtensionRecommendations = false; # Don’t recommend extensions while the user is visiting web pages + FeatureRecommendations = false; # Don’t recommend browser features + Locked = true; # Prevent the user from changing user messaging preferences + MoreFromMozilla = false; # Don’t show the “More from Mozilla” section in Preferences + SkipOnboarding = true; # Don’t show onboarding messages on the new tab page + UrlbarInterventions = false; # Don’t offer suggestions in the URL bar + WhatsNew = false; # Remove the “What’s New” icon and menuitem + }; + ExtensionSettings = { + "3rdparty".Extensions = { + # https://github.com/gorhill/uBlock/blob/master/platform/common/managed_storage.json + "uBlock0@raymondhill.net".adminSettings = { + userSettings = rec { + uiTheme = "dark"; + uiAccentCustom = true; + uiAccentCustom0 = "#0C8084"; + cloudStorageEnabled = lib.mkForce false; + importedLists = [ + "https://filters.adtidy.org/extension/ublock/filters/3.txt" + "https://github.com/DandelionSprout/adfilt/raw/master/LegitimateURLShortener.txt" + ]; + externalLists = lib.concatStringsSep "\n" importedLists; + }; + selectedFilterLists = [ + "CZE-0" + "adguard-generic" + "adguard-annoyance" + "adguard-social" + "adguard-spyware-url" + "easylist" + "easyprivacy" + "https://github.com/DandelionSprout/adfilt/raw/master/LegitimateURLShortener.txt" + "plowe-0" + "ublock-abuse" + "ublock-badware" + "ublock-filters" + "ublock-privacy" + "ublock-quick-fixes" + "ublock-unbreak" + "urlhaus-1" + ]; + }; + }; + + }; + + }; + + profiles = { + default = lib.recursiveUpdate + { + id = 0; + isDefault = true; + settings = { + "browser.startup.homepage" = "https://lobste.rs"; + }; + } + vars.firefox; + }; + }; + }; +} diff --git a/modules-clone/home/common/firezone-tray.nix b/modules-clone/home/common/firezone-tray.nix new file mode 100644 index 0000000..b669901 --- /dev/null +++ b/modules-clone/home/common/firezone-tray.nix @@ -0,0 +1,29 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.firezone-tray = lib.mkEnableOption "enable firezone applet for tray"; + config = lib.mkIf config.swarselmodules.firezone-tray { + + systemd.user.services.firezone-applet = { + Unit = { + Description = "Firezone applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs.firezone-gui-client}/bin/firezone-client-gui"; + }; + }; + }; + +} diff --git a/modules-clone/home/common/fuzzel.nix b/modules-clone/home/common/fuzzel.nix new file mode 100644 index 0000000..89e6689 --- /dev/null +++ b/modules-clone/home/common/fuzzel.nix @@ -0,0 +1,17 @@ +{ lib, config, ... }: +{ + options.swarselmodules.fuzzel = lib.mkEnableOption "fuzzel settings"; + config = lib.mkIf config.swarselmodules.fuzzel { + programs.fuzzel = { + enable = true; + settings = { + main = { + layer = "overlay"; + lines = "10"; + width = "40"; + }; + border.radius = "0"; + }; + }; + }; +} diff --git a/modules-clone/home/common/gammastep.nix b/modules-clone/home/common/gammastep.nix new file mode 100644 index 0000000..7c7480a --- /dev/null +++ b/modules-clone/home/common/gammastep.nix @@ -0,0 +1,15 @@ +{ lib, config, confLib, ... }: +let + inherit (confLib.getConfig.repo.secrets.common.location) latitude longitude; +in +{ + options.swarselmodules.gammastep = lib.mkEnableOption "gammastep settings"; + config = lib.mkIf config.swarselmodules.gammastep { + systemd.user.services.gammastep = confLib.overrideTarget "sway-session.target"; + services.gammastep = lib.mkIf (config.swarselsystems.isNixos && !config.swarselsystems.isPublic) { + enable = true; + provider = "manual"; + inherit longitude latitude; + }; + }; +} diff --git a/modules-clone/home/common/git.nix b/modules-clone/home/common/git.nix new file mode 100644 index 0000000..cda162b --- /dev/null +++ b/modules-clone/home/common/git.nix @@ -0,0 +1,53 @@ +{ lib, config, globals, minimal, confLib, ... }: +let + inherit (confLib.getConfig.repo.secrets.common.mail) address1; + inherit (confLib.getConfig.repo.secrets.common) fullName; + + gitUser = globals.user.name; +in +{ + options.swarselmodules.git = lib.mkEnableOption "git settings"; + config = lib.mkIf config.swarselmodules.git { + programs.git = { + enable = true; + } // lib.optionalAttrs (!minimal) { + settings = { + alias = { + a = "add"; + c = "commit"; + cl = "clone"; + co = "checkout"; + b = "branch"; + i = "init"; + m = "merge"; + s = "status"; + r = "restore"; + p = "pull"; + pp = "push"; + }; + user = { + email = lib.mkIf (config.swarselsystems.isNixos && !config.swarselsystems.isPublic) (lib.mkDefault address1); + name = lib.mkIf (config.swarselsystems.isNixos && !config.swarselsystems.isPublic) fullName; + }; + }; + signing = { + key = "0x76FD3810215AE097"; + signByDefault = true; + }; + lfs.enable = true; + includes = [ + { + contents = { + github = { + user = gitUser; + }; + commit = { + template = "~/.gitmessage"; + }; + }; + } + ]; + }; + programs.difftastic.enable = lib.mkIf (!minimal) true; + }; +} diff --git a/modules-clone/home/common/gnome-keyring.nix b/modules-clone/home/common/gnome-keyring.nix new file mode 100644 index 0000000..c952e7b --- /dev/null +++ b/modules-clone/home/common/gnome-keyring.nix @@ -0,0 +1,9 @@ +{ lib, config, ... }: +{ + options.swarselmodules.gnome-keyring = lib.mkEnableOption "gnome keyring settings"; + config = lib.mkIf config.swarselmodules.gnome-keyring { + services.gnome-keyring = lib.mkIf (!config.swarselsystems.isNixos) { + enable = true; + }; + }; +} diff --git a/modules-clone/home/common/gpg-agent.nix b/modules-clone/home/common/gpg-agent.nix new file mode 100644 index 0000000..ae8f6dd --- /dev/null +++ b/modules-clone/home/common/gpg-agent.nix @@ -0,0 +1,59 @@ +{ self, lib, config, pkgs, ... }: +let + inherit (config.swarselsystems) mainUser homeDir; +in +{ + options.swarselmodules.gpgagent = lib.mkEnableOption "gpg agent settings"; + config = lib.mkIf config.swarselmodules.gpgagent { + services.gpg-agent = { + enable = true; + verbose = true; + enableZshIntegration = true; + enableScDaemon = true; + enableSshSupport = true; + enableExtraSocket = true; + pinentry.package = pkgs.wayprompt; + pinentry.program = "pinentry-wayprompt"; + # pinentry.package = pkgs.pinentry.gtk2; + defaultCacheTtl = 600; + maxCacheTtl = 7200; + extraConfig = '' + allow-loopback-pinentry + allow-emacs-pinentry + ''; + sshKeys = [ + "4BE7925262289B476DBBC17B76FD3810215AE097" + ]; + }; + + programs.gpg = { + enable = true; + scdaemonSettings = { + disable-ccid = true; # prevent conflicts between pcscd and scdameon + # pcsc-shared = true; # as long as only one key is used, this prevents key from not being detected sometimes + }; + publicKeys = [ + { + source = "${self}/secrets/public/gpg/gpg-public-key-0x76FD3810215AE097.asc"; + trust = 5; + } + ]; + }; + + systemd.user.tmpfiles.rules = [ + "d ${homeDir}/.gnupg 0700 ${mainUser} users - -" + ]; + + # assure correct permissions + # systemd.user.tmpfiles.settings."30-gpgagent".rules = { + # "${homeDir}/.gnupg" = { + # d = { + # group = "users"; + # user = mainUser; + # mode = "0700"; + # }; + # }; + # }; + }; + +} diff --git a/modules-clone/home/common/hexchat.nix b/modules-clone/home/common/hexchat.nix new file mode 100644 index 0000000..97f70c0 --- /dev/null +++ b/modules-clone/home/common/hexchat.nix @@ -0,0 +1,17 @@ +{ lib, config, confLib, ... }: +let + moduleName = "hexchat"; + inherit (confLib.getConfig.repo.secrets.common.irc) irc_nick1; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + settings = { + inherit irc_nick1; + }; + }; + }; + +} diff --git a/modules-clone/home/common/kanshi.nix b/modules-clone/home/common/kanshi.nix new file mode 100644 index 0000000..463bc08 --- /dev/null +++ b/modules-clone/home/common/kanshi.nix @@ -0,0 +1,104 @@ +{ self, lib, pkgs, config, confLib, ... }: +{ + options.swarselmodules.kanshi = lib.mkEnableOption "kanshi settings"; + config = lib.mkIf config.swarselmodules.kanshi { + swarselsystems = { + monitors = { + homedesktop = rec { + name = "Philips Consumer Electronics Company PHL BDM3270 AU11806002320"; + mode = "2560x1440"; + scale = "1"; + position = "0,0"; + workspace = "11:M"; + output = name; + }; + }; + }; + + systemd.user.services.kanshi = confLib.overrideTarget "sway-session.target"; + services.kanshi = { + enable = true; + settings = [ + { + # laptop screen + output = { + criteria = config.swarselsystems.sharescreen; + mode = "${config.swarselsystems.highResolution}@165.000"; + scale = 1.0; + }; + } + { + # home main screen + output = { + criteria = "Philips Consumer Electronics Company PHL BDM3270 AU11806002320"; + scale = 1.0; + mode = "2560x1440"; + }; + } + { + profile = { + name = "lidopen"; + exec = [ "${pkgs.swaybg}/bin/swaybg --output '${config.swarselsystems.sharescreen}' --image ${config.swarselsystems.wallpaper} --mode ${config.stylix.imageScalingMode}" ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "enable"; + scale = 1.0; + } + ]; + }; + } + { + profile = + let + monitor = "Philips Consumer Electronics Company PHL BDM3270 AU11806002320"; + in + { + name = "lidopen"; + exec = [ + "${pkgs.swaybg}/bin/swaybg --output '${config.swarselsystems.sharescreen}' --image ${config.swarselsystems.wallpaper} --mode ${config.stylix.imageScalingMode}" + "${pkgs.swaybg}/bin/swaybg --output '${monitor}' --image ${self}/files/wallpaper/landscape/standwp.png --mode ${config.stylix.imageScalingMode}" + ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "enable"; + scale = 1.7; + position = "2560,0"; + } + { + criteria = monitor; + scale = 1.0; + mode = "2560x1440"; + position = "0,0"; + } + ]; + }; + } + { + profile = + let + monitor = "Philips Consumer Electronics Company PHL BDM3270 AU11806002320"; + in + { + name = "lidclosed"; + exec = [ "${pkgs.swaybg}/bin/swaybg --output '${monitor}' --image ${self}/files/wallpaper/landscape/standwp.png --mode ${config.stylix.imageScalingMode}" ]; + outputs = [ + { + criteria = config.swarselsystems.sharescreen; + status = "disable"; + position = "2560,0"; + } + { + criteria = monitor; + scale = 1.0; + mode = "2560x1440"; + position = "0,0"; + } + ]; + }; + } + ]; + }; + }; +} diff --git a/modules-clone/home/common/kdeconnect.nix b/modules-clone/home/common/kdeconnect.nix new file mode 100644 index 0000000..c51ca32 --- /dev/null +++ b/modules-clone/home/common/kdeconnect.nix @@ -0,0 +1,11 @@ +{ lib, config, ... }: +{ + options.swarselmodules.kdeconnect = lib.mkEnableOption "kdeconnect settings"; + config = lib.mkIf config.swarselmodules.kdeconnect { + services.kdeconnect = { + enable = true; + indicator = true; + }; + }; + +} diff --git a/modules-clone/home/common/khal.nix b/modules-clone/home/common/khal.nix new file mode 100644 index 0000000..73ee4a0 --- /dev/null +++ b/modules-clone/home/common/khal.nix @@ -0,0 +1,14 @@ +{ lib, config, pkgs, ... }: +let + moduleName = "khal"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + package = pkgs.khal; + }; + }; + +} diff --git a/modules-clone/home/common/kitty.nix b/modules-clone/home/common/kitty.nix new file mode 100644 index 0000000..2778a54 --- /dev/null +++ b/modules-clone/home/common/kitty.nix @@ -0,0 +1,29 @@ +{ lib, config, ... }: +{ + options.swarselmodules.kitty = lib.mkEnableOption "kitty settings"; + config = lib.mkIf config.swarselmodules.kitty { + programs.kitty = { + enable = true; + keybindings = + let + bindWithModifier = lib.mapAttrs' (key: lib.nameValuePair ("ctrl+shift" + key)); + in + bindWithModifier { + "page_up" = "scroll_page_up"; + "up" = "scroll_page_up"; + "page_down" = "scroll_page_down"; + "down" = "scroll_page_down"; + "w" = "no_op"; + }; + settings = { + cursor_blink_interval = 0; + disable_ligatures = "cursor"; + enable_audio_bell = false; + notify_on_cmd_finish = "always 20"; + open_url_with = "xdg-open"; + scrollback_lines = 100000; + scrollback_pager_history_size = 512; + }; + }; + }; +} diff --git a/modules-clone/home/common/mail.nix b/modules-clone/home/common/mail.nix new file mode 100644 index 0000000..9ee8884 --- /dev/null +++ b/modules-clone/home/common/mail.nix @@ -0,0 +1,211 @@ +{ lib, config, globals, confLib, type, ... }: +let + inherit (confLib.getConfig.repo.secrets.common.mail) address1 address2 address2-name address3 address3-name address4; + inherit (confLib.getConfig.repo.secrets.common) fullName; + inherit (config.swarselsystems) xdgDir; +in +{ + options.swarselmodules.mail = lib.mkEnableOption "mail settings"; + config = lib.mkIf config.swarselmodules.mail + ({ + + programs = { + mbsync = { + enable = true; + }; + msmtp = { + enable = true; + }; + mu = { + enable = true; + }; + }; + + services.mbsync = { + enable = true; + }; + # this is needed so that mbsync can use the passwords from sops + systemd.user.services.mbsync.Unit.After = [ "sops-nix.service" ]; + + programs.thunderbird = { + enable = true; + profiles.default = { + isDefault = true; + withExternalGnupg = true; + settings = { + "mail.identity.default.archive_enabled" = true; + "mail.identity.default.archive_keep_folder_structure" = true; + "mail.identity.default.compose_html" = false; + "mail.identity.default.protectSubject" = true; + "mail.identity.default.reply_on_top" = 1; + "mail.identity.default.sig_on_reply" = false; + "mail.identity.default.sig_bottom" = false; + + "gfx.webrender.all" = true; + "gfx.webrender.enabled" = true; + }; + }; + + settings = { + "mail.server.default.allow_utf8_accept" = true; + "mail.server.default.max_articles" = 1000; + "mail.server.default.check_all_folders_for_new" = true; + "mail.show_headers" = 1; + "mail.identity.default.auto_quote" = true; + "mail.identity.default.attachPgpKey" = true; + "mailnews.default_sort_order" = 2; + "mailnews.default_sort_type" = 18; + "mailnews.default_view_flags" = 0; + "mailnews.sort_threads_by_root" = true; + "mailnews.headers.showMessageId" = true; + "mailnews.headers.showOrganization" = true; + "mailnews.headers.showReferences" = true; + "mailnews.headers.showUserAgent" = true; + "mail.imap.expunge_after_delete" = true; + "mail.server.default.delete_model" = 2; + "mail.warn_on_delete_from_trash" = false; + "mail.warn_on_shift_delete" = false; + "toolkit.telemetry.enabled" = false; + "toolkit.telemetry.rejected" = true; + "toolkit.telemetry.prompted" = 2; + "app.update.auto" = false; + "privacy.donottrackheader.enabled" = true; + }; + }; + + xdg.mimeApps.defaultApplications = { + "x-scheme-handler/mailto" = [ "thunderbird.desktop" ]; + "x-scheme-handler/mid" = [ "thunderbird.desktop" ]; + "message/rfc822" = [ "thunderbird.desktop" ]; + }; + + accounts = lib.mkIf (config.swarselsystems.isNixos && !config.swarselsystems.isPublic) { + email = + let + defaultSettings = { + imap = { + host = "imap.gmail.com"; + port = 993; + tls.enable = true; # SSL/TLS + }; + smtp = { + host = "smtp.gmail.com"; + port = 465; + tls.enable = true; # SSL/TLS + }; + thunderbird = { + enable = true; + profiles = [ "default" ]; + }; + mu.enable = true; + msmtp = { + enable = true; + }; + mbsync = { + enable = true; + create = "maildir"; + expunge = "both"; + patterns = [ "*" "![Gmail]*" "[Gmail]/Sent Mail" "[Gmail]/Starred" "[Gmail]/All Mail" ]; + extraConfig = { + channel = { + Sync = "All"; + }; + account = { + Timeout = 120; + PipelineDepth = 1; + AuthMechs = "LOGIN"; + }; + }; + }; + }; + in + { + maildirBasePath = "Mail"; + accounts = { + swarsel = { + imap = { + host = globals.services.mailserver.domain; + port = 993; + tls.enable = true; # SSL/TLS + }; + smtp = { + host = globals.services.mailserver.domain; + port = 465; + tls.enable = true; # SSL/TLS + }; + thunderbird = { + enable = true; + profiles = [ "default" ]; + }; + address = address4; + userName = address4; + realName = fullName; + passwordCommand = "cat ${confLib.getConfig.sops.secrets.address4-token.path}"; + mu.enable = true; + msmtp = { + enable = true; + }; + mbsync = { + enable = true; + create = "maildir"; + expunge = "both"; + patterns = [ "*" ]; + extraConfig = { + channel = { + Sync = "All"; + }; + account = { + Timeout = 120; + PipelineDepth = 1; + AuthMechs = "LOGIN"; + }; + }; + }; + }; + + leon = lib.recursiveUpdate + { + primary = true; + address = address1; + userName = address1; + realName = fullName; + passwordCommand = "cat ${confLib.getConfig.sops.secrets.address1-token.path}"; + gpg = { + key = "0x76FD3810215AE097"; + signByDefault = true; + }; + } + defaultSettings; + + nautilus = lib.recursiveUpdate + { + primary = false; + address = address2; + userName = address2; + realName = address2-name; + passwordCommand = "cat ${confLib.getConfig.sops.secrets.address2-token.path}"; + } + defaultSettings; + + mrswarsel = lib.recursiveUpdate + { + primary = false; + address = address3; + userName = address3; + realName = address3-name; + passwordCommand = "cat ${confLib.getConfig.sops.secrets.address3-token.path}"; + } + defaultSettings; + + }; + }; + }; + } // lib.optionalAttrs (type != "nixos") { + sops.secrets = lib.mkIf (!config.swarselsystems.isPublic && !config.swarselsystems.isNixos) { + address1-token = { path = "${xdgDir}/secrets/address1-token"; }; + address2-token = { path = "${xdgDir}/secrets/address2-token"; }; + address3-token = { path = "${xdgDir}/secrets/address3-token"; }; + address4-token = { path = "${xdgDir}/secrets/address4-token"; }; + }; + }); +} diff --git a/modules-clone/home/common/mako.nix b/modules-clone/home/common/mako.nix new file mode 100644 index 0000000..9a90f69 --- /dev/null +++ b/modules-clone/home/common/mako.nix @@ -0,0 +1,38 @@ +{ lib, config, ... }: +{ + options.swarselmodules.mako = lib.mkEnableOption "mako settings"; + config = lib.mkIf config.swarselmodules.mako { + services.mako = { + enable = true; + settings = { + border-radius = 15; + border-size = 1; + default-timeout = 5000; + ignore-timeout = false; + icons = 1; + layer = "overlay"; + sort = "-time"; + height = 150; + width = 300; + "urgency=low" = { + border-color = lib.mkForce "#cccccc"; + }; + "urgency=normal" = { + border-color = lib.mkForce "#d08770"; + }; + "urgency=high" = { + border-color = lib.mkForce "#bf616a"; + default-timeout = 3000; + }; + "category=mpd" = { + default-timeout = 2000; + group-by = "category"; + }; + "mode=do-not-disturb" = { + invisible = true; + }; + }; + }; + }; + +} diff --git a/modules-clone/home/common/network-manager-applet.nix b/modules-clone/home/common/network-manager-applet.nix new file mode 100644 index 0000000..a237ef7 --- /dev/null +++ b/modules-clone/home/common/network-manager-applet.nix @@ -0,0 +1,8 @@ +{ lib, config, ... }: +{ + options.swarselmodules.nm-applet = lib.mkEnableOption "enable network manager applet for tray"; + config = lib.mkIf config.swarselmodules.nm-applet { + services.network-manager-applet.enable = true; + xsession.preferStatusNotifierItems = true; # needed for indicator icon to show + }; +} diff --git a/modules-clone/home/common/nix-index.nix b/modules-clone/home/common/nix-index.nix new file mode 100644 index 0000000..b42b36a --- /dev/null +++ b/modules-clone/home/common/nix-index.nix @@ -0,0 +1,62 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.nix-index = lib.mkEnableOption "nix-index settings"; + config = lib.mkIf config.swarselmodules.nix-index { + programs.nix-index = + let + commandNotFound = pkgs.runCommandLocal "command-not-found.sh" { } '' + mkdir -p $out/etc/profile.d + cat > $out/etc/profile.d/command-not-found.sh <<'EOF' + # Adapted from https://github.com/bennofs/nix-index/blob/master/command-not-found.sh + command_not_found_handle() { + if [ -n "''${MC_SID-}" ] || ! [ -t 1 ]; then + >&2 echo "$1: command not found" + return 127 + fi + + echo -n "searching nix-index..." + ATTRS=$(@nix-locate@ --minimal --no-group --type x --type s --whole-name --at-root "/bin/$1") + + case $(echo -n "$ATTRS" | grep -c "^") in + 0) + >&2 echo -ne "$(@tput@ el1)\r" + >&2 echo "$1: command not found" + ;; + *) + >&2 echo -ne "$(@tput@ el1)\r" + >&2 echo "The program ‘$(@tput@ setaf 4)$1$(@tput@ sgr0)’ is currently not installed." + >&2 echo "It is provided by the following derivation(s):" + while read -r ATTR; do + ATTR=''${ATTR%.out} + >&2 echo " $(@tput@ setaf 12)nixpkgs#$(@tput@ setaf 4)$ATTR$(@tput@ sgr0)" + done <<< "$ATTRS" + ;; + esac + + return 127 + } + + command_not_found_handler() { + command_not_found_handle "$@" + return $? + } + EOF + + substitute $out/etc/profile.d/command-not-found.sh \ + $out/etc/profile.d/command-not-found.sh \ + --replace-fail @nix-locate@ ${pkgs.nix-index}/bin/nix-locate \ + --replace-fail @tput@ ${pkgs.ncurses}/bin/tput + ''; + in + + { + + enable = true; + package = pkgs.symlinkJoin { + name = "nix-index"; + paths = [ commandNotFound ]; + }; + }; + programs.nix-index-database.comma.enable = true; + }; +} diff --git a/modules-clone/home/common/nix-your-shell.nix b/modules-clone/home/common/nix-your-shell.nix new file mode 100644 index 0000000..1ad63cb --- /dev/null +++ b/modules-clone/home/common/nix-your-shell.nix @@ -0,0 +1,13 @@ +{ lib, config, ... }: +let + moduleName = "nix-your-shell"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + enableZshIntegration = true; + }; + }; +} diff --git a/modules-clone/home/common/nixgl.nix b/modules-clone/home/common/nixgl.nix new file mode 100644 index 0000000..30ae289 --- /dev/null +++ b/modules-clone/home/common/nixgl.nix @@ -0,0 +1,27 @@ +{ lib, config, inputs, ... }: +{ + options.swarselmodules.nixgl = lib.mkEnableOption "nixgl settings"; + options.swarselsystems = { + isSecondaryGpu = lib.mkEnableOption "device has a secondary GPU"; + SecondaryGpuCard = lib.mkOption { + type = lib.types.str; + default = ""; + }; + }; + config = lib.mkIf config.swarselmodules.nixgl { + nixGL = lib.mkIf (!config.swarselsystems.isNixos) { + inherit (inputs.nixgl) packages; + defaultWrapper = lib.mkDefault "mesa"; + vulkan.enable = lib.mkDefault false; + prime = lib.mkIf config.swarselsystems.isSecondaryGpu { + card = config.swarselsystems.secondaryGpuCard; + installScript = "mesa"; + }; + offloadWrapper = lib.mkIf config.swarselsystem.isSecondaryGpu "mesaPrime"; + installScripts = [ + "mesa" + "mesaPrime" + ]; + }; + }; +} diff --git a/modules-clone/home/common/obs-studio.nix b/modules-clone/home/common/obs-studio.nix new file mode 100644 index 0000000..03ef30c --- /dev/null +++ b/modules-clone/home/common/obs-studio.nix @@ -0,0 +1,13 @@ +{ lib, config, ... }: +let + moduleName = "obs-studio"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + }; + }; + +} diff --git a/modules-clone/home/common/obsidian-tray.nix b/modules-clone/home/common/obsidian-tray.nix new file mode 100644 index 0000000..98fe0e1 --- /dev/null +++ b/modules-clone/home/common/obsidian-tray.nix @@ -0,0 +1,29 @@ +{ lib, config, ... }: +{ + options.swarselmodules.obsidian-tray = lib.mkEnableOption "enable obsidian applet for tray"; + config = lib.mkIf config.swarselmodules.obsidian-tray { + + systemd.user.services.obsidian-applet = { + Unit = { + Description = "Obsidian applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${lib.getExe config.programs.obsidian.package}"; + }; + }; + }; + +} diff --git a/modules-clone/home/common/obsidian.nix b/modules-clone/home/common/obsidian.nix new file mode 100644 index 0000000..03219bc --- /dev/null +++ b/modules-clone/home/common/obsidian.nix @@ -0,0 +1,155 @@ +{ lib, config, pkgs, confLib, ... }: +let + moduleName = "obsidian"; + inherit (confLib.getConfig.repo.secrets.common.obsidian) userIgnoreFilters; + name = "Main"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} with settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + + home.file = { + "${config.programs.obsidian.vaults.${name}.target}/.obsidian/app.json".force = true; + "${config.programs.obsidian.vaults.${name}.target}/.obsidian/appearance.json".force = true; + "${config.programs.obsidian.vaults.${name}.target}/.obsidian/core-plugins.json".force = true; + }; + + programs.obsidian = + let + pluginSource = pkgs.nur.repos.swarsel; + in + { + enable = true; + package = pkgs.obsidian; + defaultSettings = { + app = { + attachmentFolderPath = "attachments"; + alwaysUpdateLinks = true; + spellcheck = false; + inherit userIgnoreFilters; + vimMode = false; + newFileLocation = "current"; + }; + hotkeys = { + "graph:open" = [ ]; + "omnisearch:show-modal" = [ + { + modifiers = [ + "Mod" + ]; + key = "S"; + } + ]; + "editor:save-file" = [ ]; + "editor:delete-paragraph" = [ ]; + }; + corePlugins = [ + "backlink" + "bookmarks" + "canvas" + "command-palette" + "daily-notes" + "editor-status" + "file-explorer" + "file-recovery" + "global-search" + "graph" + "note-composer" + "outgoing-link" + "outline" + "page-preview" + "properties" + "slides" + "switcher" + "tag-pane" + "templates" + "word-count" + ]; + # communityPlugins = with pkgs.swarsel-nix; [ + communityPlugins = with pluginSource; [ + advanced-tables + calendar + file-hider + linter + omnisearch + sort-and-permute-lines + tag-wrangler + tray + ]; + }; + vaults = { + ${name} = { + target = "./Obsidian/${name}"; + settings = { + appearance = { + baseFontSize = lib.mkForce 19; + }; + # communityPlugins = with pkgs.swarsel-nix; [ + communityPlugins = with pluginSource; [ + { + pkg = advanced-tables; + enable = true; + } + { + pkg = calendar; + enable = true; + } + { + pkg = sort-and-permute-lines; + enable = true; + } + { + pkg = tag-wrangler; + enable = true; + } + { + pkg = tray; + enable = true; + settings = { + launchOnStartup = false; + hideOnLaunch = true; + runInBackground = true; + hideTaskbarIcon = false; + createTrayIcon = true; + }; + } + { + pkg = file-hider; + enable = true; + settings = + { + hidden = true; + hiddenList = [ + "attachments" + "images" + "ltximg" + "logseq" + ]; + }; + } + { + pkg = linter; + enable = true; + settings = { + auto-correct-common-misspellings = { + skip-words-with-multiple-capitals = true; + }; + convert-bullet-list-markers = { + enabled = true; + }; + }; + } + { + pkg = omnisearch; + enable = true; + settings = { + hideExcluded = true; + }; + } + ]; + }; + }; + }; + }; + }; +} diff --git a/modules-clone/home/common/opkssh.nix b/modules-clone/home/common/opkssh.nix new file mode 100644 index 0000000..1481701 --- /dev/null +++ b/modules-clone/home/common/opkssh.nix @@ -0,0 +1,30 @@ +{ lib, config, globals, ... }: +let + moduleName = "opkssh"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + settings = { + default_provider = "kanidm"; + + providers = [ + { + alias = "kanidm"; + issuer = "https://${globals.services.kanidm.domain}/oauth2/openid/opkssh"; + client_id = "opkssh"; + scopes = "openid email profile"; + redirect_uris = [ + "http://localhost:3000/login-callback" + "http://localhost:10001/login-callback" + "http://localhost:11110/login-callback" + ]; + } + ]; + }; + }; + }; + +} diff --git a/modules-clone/home/common/packages.nix b/modules-clone/home/common/packages.nix new file mode 100644 index 0000000..dc7f04f --- /dev/null +++ b/modules-clone/home/common/packages.nix @@ -0,0 +1,182 @@ +{ lib, config, pkgs, ... }: + +{ + options.swarselmodules.packages = lib.mkEnableOption "packages settings"; + config = lib.mkIf config.swarselmodules.packages { + home.packages = with pkgs; [ + + # audio stuff + spek # spectrum analyzer + losslessaudiochecker + ffmpeg_7-full + flac + mediainfo + picard-tools + audacity + sox + calibre + + # printing + cups + simple-scan + cura-appimage + + # ssh login using idm + opkssh + + # cache + attic-client + + # dict + (aspellWithDicts (dicts: with dicts; [ de en en-computers en-science ])) + + # browser + vieb + mgba + + # utilities + util-linux + nmap + lsof + nvd + nix-output-monitor + hyprpicker # color picker + findutils + units + vim + sshfs + fuse + # ventoy + poppler-utils + + # nix + alejandra + nixpkgs-fmt + deadnix + statix + nix-tree + nix-diff + nix-visualize + nix-init + nix-inspect + (nixpkgs-review.override { nix = config.nix.package; }) + manix + + # shellscripts + shfmt + + # local file sharing + wormhole-rs + croc + + # b2 backup @backblaze + restic + + # "big" programs + # obs-studio + gimp + inkscape + zoom-us + # nomacs + libreoffice-qt + xournalpp + # obsidian + # spotify + # vesktop # discord client + # nextcloud-client # enables a systemd service that I do not want + # spotify-player + # element-desktop + + nicotine-plus + transmission_3 + mktorrent + hugo + + # kyria + qmk + qmk-udev-rules + + # firefox related + tridactyl-native + + # mako related + # mako + libnotify + + # general utilities + unrar + # samba + cifs-utils + zbar # qr codes + readline + autotiling + brightnessctl + libappindicator-gtk3 + sqlite + speechd + networkmanagerapplet + psmisc # kill etc + lm_sensors + # jq # used for searching the i3 tree in check.sh files + + # specifically needed for anki + # mpv + # anki-bin + + # dirvish file previews + fd + imagemagick + # poppler + ffmpegthumbnailer + mediainfo + gnutar + unzip + + #nautilus + nautilus + tumbler + libgsf + + # wayland stuff + wtype + wl-mirror + wl-clipboard + wf-recorder + kanshi + + # screenshotting tools + grim + slurp + + # the following packages are used (in some way) by waybar + pavucontrol + + #keychain + qalculate-gtk + gcr # needed for gnome-secrets to work + seahorse + + # sops-related + sops + ssh-to-age + + # mail related packages + mu + + # latex and related packages + (texlive.combine { + inherit (pkgs.texlive) scheme-full + dvisvgm dvipng# for preview and export as html + wrapfig amsmath ulem hyperref capt-of; + }) + + # font stuff + cantarell-fonts + nerd-fonts.fira-code + (iosevka-bin.override { variant = "Aile"; }) + nerd-fonts.symbols-only + noto-fonts-color-emoji + font-awesome_5 + ]; + }; +} diff --git a/modules-clone/home/common/password-store.nix b/modules-clone/home/common/password-store.nix new file mode 100644 index 0000000..bd9f640 --- /dev/null +++ b/modules-clone/home/common/password-store.nix @@ -0,0 +1,13 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.passwordstore = lib.mkEnableOption "passwordstore settings"; + config = lib.mkIf config.swarselmodules.passwordstore { + programs.password-store = { + enable = true; + settings = { + PASSWORD_STORE_DIR = "$HOME/.local/share/password-store"; + }; + package = pkgs.pass.withExtensions (exts: [ exts.pass-otp ]); + }; + }; +} diff --git a/modules-clone/home/common/programs.nix b/modules-clone/home/common/programs.nix new file mode 100644 index 0000000..d363b9e --- /dev/null +++ b/modules-clone/home/common/programs.nix @@ -0,0 +1,95 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.programs = lib.mkEnableOption "programs settings"; + config = lib.mkIf config.swarselmodules.programs { + programs = { + bat = { + enable = true; + extraPackages = [ + pkgs.bat-extras.batdiff + pkgs.bat-extras.batman + pkgs.bat-extras.batwatch + pkgs.bat-extras.batgrep + ]; + # extraPackages = with pkgs.bat-extras; [ batdiff batman batgrep batwatch ]; + }; + bottom.enable = true; + carapace.enable = true; + fzf = { + enable = true; + enableBashIntegration = false; + enableZshIntegration = false; + }; + imv.enable = true; + jq.enable = true; + less.enable = true; + lesspipe.enable = true; + mpv.enable = true; + pandoc.enable = true; + rclone.enable = true; + ripgrep.enable = true; + sioyek.enable = true; + swayr.enable = true; + timidity.enable = true; + wlogout = { + enable = true; + layout = [ + { + label = "lock"; + action = "loginctl lock-session"; + text = "Lock"; + keybind = "l"; + circular = true; + } + { + label = "hibernate"; + action = "systemctl hibernate"; + text = "Hibernate"; + keybind = "h"; + circular = true; + } + { + label = "logout"; + action = "loginctl terminate-user $USER"; + text = "Logout"; + keybind = "u"; + circular = true; + } + { + label = "shutdown"; + action = "systemctl poweroff"; + text = "Shutdown"; + keybind = "p"; + circular = true; + } + { + label = "suspend"; + action = "systemctl suspend"; + text = "Suspend"; + keybind = "s"; + circular = true; + } + { + label = "reboot"; + action = "systemctl reboot"; + text = "Reboot"; + keybind = "r"; + circular = true; + } + ]; + }; + yt-dlp.enable = true; + zoxide = { + enable = true; + enableZshIntegration = true; + options = [ + "--cmd cd" + ]; + }; + }; + + home.sessionVariables = { + _ZO_EXCLUDE_DIRS = "$HOME:$HOME/.ansible/*:$HOME/test/*:/persist"; + }; + }; +} diff --git a/modules-clone/home/common/settings.nix b/modules-clone/home/common/settings.nix new file mode 100644 index 0000000..66b372f --- /dev/null +++ b/modules-clone/home/common/settings.nix @@ -0,0 +1,119 @@ +{ self, outputs, lib, pkgs, config, globals, confLib, ... }: +let + inherit (config.swarselsystems) mainUser flakePath isNixos isLinux; + inherit (confLib.getConfig.repo.secrets.common) atticPublicKey; +in +{ + options.swarselmodules.general = lib.mkEnableOption "general nix settings"; + config = + let + nix-version = "2_30"; + in + lib.mkIf config.swarselmodules.general { + nix = lib.mkIf (!config.swarselsystems.isNixos) { + package = lib.mkForce pkgs.nixVersions."nix_${nix-version}"; + # extraOptions = '' + # plugin-files = ${pkgs.dev.nix-plugins}/lib/nix/plugins + # extra-builtins-file = ${self + /nix/extra-builtins.nix} + # ''; + extraOptions = + let + nix-plugins = pkgs.nix-plugins.override { + nixComponents = pkgs.nixVersions."nixComponents_${nix-version}"; + }; + in + '' + plugin-files = ${nix-plugins}/lib/nix/plugins + extra-builtins-file = ${self + /files/nix/extra-builtins.nix} + ''; + settings = { + experimental-features = [ + "nix-command" + "flakes" + "ca-derivations" + "cgroups" + "pipe-operators" + ]; + substituters = [ + "https://${globals.services.attic.domain}/${mainUser}" + ]; + trusted-public-keys = [ + atticPublicKey + ]; + trusted-users = [ + "@wheel" + "${mainUser}" + (lib.mkIf ((config.swarselmodules ? server) ? ssh-builder) "builder") + ]; + connect-timeout = 5; + bash-prompt-prefix = lib.mkIf config.swarselsystems.isClient "$SHLVL:\\w "; + bash-prompt = lib.mkIf config.swarselsystems.isClient "$(if [[ $? -gt 0 ]]; then printf \"\"; else printf \"\"; fi)λ "; + fallback = true; + min-free = 128000000; + max-free = 1000000000; + auto-optimise-store = true; + warn-dirty = false; + max-jobs = 1; + use-cgroups = lib.mkIf isLinux true; + }; + }; + + nixpkgs = lib.mkIf (!isNixos) { + overlays = [ + outputs.overlays.default + outputs.overlays.stables + outputs.overlays.modifications + (final: prev: + let + additions = final: _: import "${self}/pkgs/config" { + inherit self config lib; + pkgs = final; + homeConfig = config; + }; + in + additions final prev + ) + ]; + config = { + allowUnfree = true; + }; + }; + + programs = { + # home-manager.enable = lib.mkIf (!isNixos) true; + man = { + enable = true; + generateCaches = true; + }; + }; + + targets.genericLinux.enable = lib.mkIf (!isNixos) true; + + home = { + username = lib.mkDefault mainUser; + homeDirectory = lib.mkDefault "/home/${mainUser}"; + stateVersion = lib.mkDefault "23.05"; + keyboard.layout = "us"; + sessionVariables = { + FLAKE = "/home/${mainUser}/.dotfiles"; + }; + extraOutputsToInstall = [ + "doc" + "info" + "devdoc" + ]; + packages = lib.mkIf (!isNixos) [ + (pkgs.symlinkJoin { + name = "home-manager"; + buildInputs = [ pkgs.makeWrapper ]; + paths = [ pkgs.home-manager ]; + postBuild = '' + wrapProgram $out/bin/home-manager \ + --append-flags '--flake ${flakePath}#$(hostname)' + ''; + }) + ]; + }; + }; + +} diff --git a/modules-clone/home/common/sharedoptions.nix b/modules-clone/home/common/sharedoptions.nix new file mode 100644 index 0000000..043cf18 --- /dev/null +++ b/modules-clone/home/common/sharedoptions.nix @@ -0,0 +1,11 @@ +{ lib, config, nixosConfig ? null, ... }: +let + # mirrorAttrs = lib.mapAttrs (_: v: lib.mkDefault v) nixosConfig.swarselsystems; + mkDefaultCommonAttrs = base: defaults: + lib.mapAttrs (_: v: lib.mkDefault v) + (lib.filterAttrs (k: _: base ? ${k}) defaults); +in +{ + # config.swarselsystems = mirrorAttrs; + config.swarselsystems = lib.mkIf (nixosConfig != null) (mkDefaultCommonAttrs config.swarselsystems (nixosConfig.swarselsystems or { })); +} diff --git a/modules-clone/home/common/shikane.nix b/modules-clone/home/common/shikane.nix new file mode 100644 index 0000000..59a4985 --- /dev/null +++ b/modules-clone/home/common/shikane.nix @@ -0,0 +1,77 @@ +{ lib, config, confLib, ... }: +{ + options.swarselmodules.shikane = lib.mkEnableOption "kanshi settings"; + config = lib.mkIf config.swarselmodules.shikane { + + systemd.user.services.shikane = confLib.overrideTarget "noctalia-shell.target"; + services.shikane = { + enable = true; + settings = + let + homeMonitor = [ + "m=PHL BDM3270" + "s=AU11806002320" + "v=Philips Consumer Electronics Company" + ]; + exec = [ "notify-send shikane \"Profile $SHIKANE_PROFILE_NAME has been applied\"" ]; + in + { + profile = [ + + { + name = "internal-on"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = true; + mode = "${config.swarselsystems.highResolution}@165.000"; + scale = 1.0; + } + ]; + } + + { + name = "home-internal-on"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = true; + scale = 1.7; + position = "2560,0"; + } + { + match = homeMonitor; + enable = true; + scale = 1.0; + mode = "2560x1440"; + position = "0,0"; + } + ]; + } + + { + name = "home-internal-off"; + inherit exec; + output = [ + { + match = config.swarselsystems.sharescreen; + enable = false; + position = "2560,0"; + } + { + match = homeMonitor; + scale = 1.0; + enable = true; + mode = "2560x1440"; + position = "0,0"; + } + ]; + } + + ]; + }; + }; + }; +} diff --git a/modules-clone/home/common/sops.nix b/modules-clone/home/common/sops.nix new file mode 100644 index 0000000..d54ee8a --- /dev/null +++ b/modules-clone/home/common/sops.nix @@ -0,0 +1,16 @@ +{ self, config, lib, type, ... }: +let + inherit (config.swarselsystems) homeDir; +in +{ + options.swarselmodules.sops = lib.mkEnableOption "sops settings"; + config = lib.optionalAttrs (type != "nixos") { + sops = lib.mkIf (!config.swarselsystems.isNixos) { + age.sshKeyPaths = [ "${if config.swarselsystems.isImpermanence then "/persist" else ""}${homeDir}/.ssh/sops" ]; + # defaultSopsFile = "${if config.swarselsystems.isImpermanence then "/persist" else ""}${homeDir}/.dotfiles/secrets/repo/common.yaml"; + defaultSopsFile = self + "/secrets/repo/common.yaml"; + + validateSopsFiles = false; + }; + }; +} diff --git a/modules-clone/home/common/spicetify.nix b/modules-clone/home/common/spicetify.nix new file mode 100644 index 0000000..e043344 --- /dev/null +++ b/modules-clone/home/common/spicetify.nix @@ -0,0 +1,23 @@ +{ inputs, lib, config, pkgs, ... }: +let + moduleName = "spicetify"; + spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.stdenv.system}; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "${moduleName} settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.spicetify = { + enable = true; + # spotifyPackage = pkgs.stable24_11.spotify; + spotifyPackage = pkgs.spotify; + enabledExtensions = with spicePkgs.extensions; [ + fullAppDisplay + shuffle + hidePodcasts + fullAlbumDate + skipStats + history + ]; + }; + }; +} diff --git a/modules-clone/home/common/spotify-player.nix b/modules-clone/home/common/spotify-player.nix new file mode 100644 index 0000000..1ba1865 --- /dev/null +++ b/modules-clone/home/common/spotify-player.nix @@ -0,0 +1,13 @@ +{ lib, config, ... }: +let + moduleName = "spotify-player"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + }; + }; + +} diff --git a/modules-clone/home/common/ssh.nix b/modules-clone/home/common/ssh.nix new file mode 100644 index 0000000..ea81f5e --- /dev/null +++ b/modules-clone/home/common/ssh.nix @@ -0,0 +1,32 @@ +{ lib, config, confLib, type, ... }: +{ + options.swarselmodules.ssh = lib.mkEnableOption "ssh settings"; + config = lib.mkIf config.swarselmodules.ssh ({ + programs.ssh = { + enable = true; + enableDefaultConfig = false; + extraConfig = '' + SetEnv TERM=xterm-256color + ServerAliveInterval 20 + ''; + matchBlocks = { + "*" = { + forwardAgent = false; + addKeysToAgent = "no"; + compression = false; + serverAliveInterval = 0; + serverAliveCountMax = 3; + hashKnownHosts = false; + userKnownHostsFile = "~/.ssh/known_hosts"; + controlMaster = "no"; + controlPath = "~/.ssh/master-%r@%n:%p"; + controlPersist = "no"; + }; + } // confLib.getConfig.repo.secrets.common.ssh.hosts; + }; + } // lib.optionalAttrs (type != "nixos") { + sops.secrets = lib.mkIf (!config.swarselsystems.isPublic && !config.swarselsystems.isNixos) { + builder-key = { path = "${config.home.homeDirectory}/.ssh/builder"; mode = "0600"; }; + }; + }); +} diff --git a/modules-clone/home/common/starship.nix b/modules-clone/home/common/starship.nix new file mode 100644 index 0000000..ba0e897 --- /dev/null +++ b/modules-clone/home/common/starship.nix @@ -0,0 +1,124 @@ +{ lib, config, ... }: +{ + options.swarselmodules.starship = lib.mkEnableOption "starship settings"; + config = lib.mkIf config.swarselmodules.starship { + programs.starship = { + enable = true; + enableZshIntegration = true; + settings = { + add_newline = false; + format = "$shlvl$character"; + right_format = "$all"; + command_timeout = 3000; + + directory.substitutions = { + "Documents" = "󰈙 "; + "Downloads" = " "; + "Music" = " "; + "Pictures" = " "; + }; + + git_status = { + style = "bg:#394260"; + format = "[[($all_status$ahead_behind)](fg:#769ff0 bg:#394260)]($style) "; + }; + + character = { + success_symbol = "[λ](bold green)"; + error_symbol = "[λ](bold red)"; + }; + + shlvl = { + disabled = false; + symbol = "↳"; + format = "[$symbol]($style) "; + repeat = true; + repeat_offset = 1; + style = "blue"; + }; + + nix_shell = { + disabled = false; + heuristic = true; + format = "[$symbol$name]($style)"; + symbol = " "; + }; + + aws.symbol = " "; + buf.symbol = " "; + c.symbol = " "; + conda.symbol = " "; + dart.symbol = " "; + directory.read_only = " 󰌾"; + docker_context.symbol = " "; + elixir.symbol = " "; + elm.symbol = " "; + fossil_branch.symbol = " "; + git_branch.symbol = " "; + golang.symbol = " "; + guix_shell.symbol = " "; + haskell.symbol = " "; + haxe.symbol = " "; + hg_branch.symbol = " "; + hostname.ssh_symbol = " "; + java.symbol = " "; + julia.symbol = " "; + lua.symbol = " "; + memory_usage.symbol = "󰍛 "; + meson.symbol = "󰔷 "; + nim.symbol = "󰆥 "; + nodejs.symbol = " "; + + os.symbols = { + Alpaquita = " "; + Alpine = " "; + Amazon = " "; + Android = " "; + Arch = " "; + Artix = " "; + CentOS = " "; + Debian = " "; + DragonFly = " "; + Emscripten = " "; + EndeavourOS = " "; + Fedora = " "; + FreeBSD = " "; + Garuda = "󰛓 "; + Gentoo = " "; + HardenedBSD = "󰞌 "; + Illumos = "󰈸 "; + Linux = " "; + Mabox = " "; + Macos = " "; + Manjaro = " "; + Mariner = " "; + MidnightBSD = " "; + Mint = " "; + NetBSD = " "; + NixOS = " "; + OpenBSD = "󰈺 "; + openSUSE = " "; + OracleLinux = "󰌷 "; + Pop = " "; + Raspbian = " "; + Redhat = " "; + RedHatEnterprise = " "; + Redox = "󰀘 "; + Solus = "󰠳 "; + SUSE = " "; + Ubuntu = " "; + Unknown = " "; + Windows = "󰍲 "; + }; + + package.symbol = "󰏗 "; + pijul_channel.symbol = " "; + python.symbol = " "; + rlang.symbol = "󰟔 "; + ruby.symbol = " "; + rust.symbol = " "; + scala.symbol = " "; + }; + }; + }; +} diff --git a/modules-clone/home/common/stylix.nix b/modules-clone/home/common/stylix.nix new file mode 100644 index 0000000..64c58a5 --- /dev/null +++ b/modules-clone/home/common/stylix.nix @@ -0,0 +1,13 @@ +{ self, lib, config, vars, ... }: +{ + options.swarselmodules.stylix = lib.mkEnableOption "stylix settings"; + config = lib.mkIf config.swarselmodules.stylix { + stylix = lib.mkIf (!config.swarselsystems.isNixos && config.swarselmodules.stylix) (lib.recursiveUpdate + { + enable = true; + base16Scheme = "${self}/files/stylix/swarsel.yaml"; + targets = vars.stylixHomeTargets; + } + vars.stylix); + }; +} diff --git a/modules-clone/home/common/sway.nix b/modules-clone/home/common/sway.nix new file mode 100644 index 0000000..54604eb --- /dev/null +++ b/modules-clone/home/common/sway.nix @@ -0,0 +1,429 @@ +{ config, lib, vars, confLib, ... }: +let + eachOutput = _: monitor: { + inherit (monitor) name; + value = builtins.removeAttrs monitor [ "mode" "name" "scale" "transform" "position" ]; + }; +in +{ + options.swarselmodules.sway = lib.mkEnableOption "sway settings"; + options.swarselsystems = { + inputs = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.str); + default = { }; + }; + monitors = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.str); + default = { }; + }; + keybindings = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + }; + + startup = lib.mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.str); + default = [ + # { command = "nextcloud --background"; } + # { command = "vesktop --start-minimized --enable-speech-dispatcher --ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime"; } + # { command = "element-desktop --hidden --enable-features=useozoneplatform --ozone-platform=wayland --disable-gpu-driver-bug-workarounds"; } + # { command = "anki"; } + # { command = "obsidian"; } + # { command = "nm-applet"; } + # { command = "feishin"; } + ]; + }; + kyria = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.str); + default = { + "36125:53060:splitkb.com_splitkb.com_Kyria_rev3" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + "7504:24926:Kyria_Keyboard" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + }; + internal = true; + }; + standardinputs = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.str); + default = lib.recursiveUpdate (lib.recursiveUpdate config.swarselsystems.touchpad config.swarselsystems.kyria) config.swarselsystems.inputs; + internal = true; + }; + touchpad = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.str); + default = { }; + internal = true; + }; + swayfxConfig = lib.mkOption { + type = lib.types.str; + default = '' + blur enable + blur_xray disable + blur_passes 1 + blur_radius 1 + shadows enable + corner_radius 2 + titlebar_separator disable + default_dim_inactive 0.02 + ''; + internal = true; + }; + }; + config = lib.mkIf config.swarselmodules.sway { + swarselsystems = { + touchpad = lib.mkIf config.swarselsystems.isLaptop { + "type:touchpad" = { + dwt = "enabled"; + tap = "enabled"; + natural_scroll = "enabled"; + middle_emulation = "enabled"; + drag_lock = "disabled"; + }; + }; + swayfxConfig = lib.mkIf (!config.swarselsystems.isNixos) " "; + }; + + wayland.windowManager.sway = { + enable = true; + # checkConfig = false; # delete this line once SwayFX is fixed upstream + package = lib.mkIf config.swarselsystems.isNixos null; + systemd = { + enable = true; + xdgAutostart = true; + variables = [ + "DISPLAY" + "WAYLAND_DISPLAY" + "SWAYSOCK" + "XDG_CURRENT_DESKTOP" + "XDG_SESSION_TYPE" + "NIXOS_OZONE_WL" + "XCURSOR_THEME" + "XCURSOR_SIZE" + ]; + }; + wrapperFeatures = { + base = true; + gtk = true; + }; + config = rec { + modifier = "Mod4"; + # terminal = "kitty"; + menu = "fuzzel"; + bars = [{ + command = "waybar"; + mode = "hide"; + hiddenState = "hide"; + position = "top"; + extraConfig = "modifier Mod4"; + }]; + keybindings = + let + inherit (config.wayland.windowManager.sway.config) modifier; + in + lib.recursiveUpdate + { + "${modifier}+0" = "workspace 10:十"; + "${modifier}+1" = "workspace 1:一"; + "${modifier}+2" = "workspace 2:二"; + "${modifier}+3" = "workspace 3:三"; + "${modifier}+4" = "workspace 4:四"; + "${modifier}+5" = "workspace 5:五"; + "${modifier}+6" = "workspace 6:六"; + "${modifier}+7" = "workspace 7:七"; + "${modifier}+8" = "workspace 8:八"; + "${modifier}+9" = "workspace 9:九"; + "${modifier}+Ctrl+Shift+c" = "reload"; + "${modifier}+Ctrl+Shift+e" = "move container to workspace 13:E"; + "${modifier}+Ctrl+Shift+f" = "move container to workspace 16:F"; + "${modifier}+Ctrl+Shift+l" = "move container to workspace 15:L"; + "${modifier}+Ctrl+Shift+m" = "move container to workspace 11:M"; + "${modifier}+Ctrl+Shift+r" = "exec swarsel-displaypower"; + "${modifier}+Ctrl+Shift+s" = "move container to workspace 12:S"; + "${modifier}+Ctrl+Shift+t" = "move container to workspace 14:T"; + "${modifier}+Ctrl+e" = "workspace 13:E"; + "${modifier}+Ctrl+f" = "workspace 16:F"; + "${modifier}+Ctrl+l" = "workspace 15:L"; + "${modifier}+Ctrl+m" = "workspace 11:M"; + "${modifier}+Ctrl+p" = "exec 1password --quick-acces"; + "${modifier}+Ctrl+s" = "workspace 12:S"; + "${modifier}+Ctrl+t" = "workspace 14:T"; + "${modifier}+Down" = "focus down"; + "${modifier}+Escape" = "exec wlogout"; + "${modifier}+F12" = "scratchpad show"; + "${modifier}+Left" = "focus left"; + "${modifier}+Return" = "exec swarselzellij"; + "${modifier}+Right" = "focus right"; + "${modifier}+Shift+0" = "move container to workspace 10:十"; + "${modifier}+Shift+1" = "move container to workspace 1:一"; + "${modifier}+Shift+2" = "move container to workspace 2:二"; + "${modifier}+Shift+3" = "move container to workspace 3:三"; + "${modifier}+Shift+4" = "move container to workspace 4:四"; + "${modifier}+Shift+5" = "move container to workspace 5:五"; + "${modifier}+Shift+6" = "move container to workspace 6:六"; + "${modifier}+Shift+7" = "move container to workspace 7:七"; + "${modifier}+Shift+8" = "move container to workspace 8:八"; + "${modifier}+Shift+9" = "move container to workspace 9:九"; + "${modifier}+Shift+Down" = "move down 40px"; + "${modifier}+Shift+Escape" = "exec kitty -o confirm_os_window_close=0 btm"; + "${modifier}+Shift+F12" = "move scratchpad"; + "${modifier}+Shift+Left" = "move left 40px"; + "${modifier}+Shift+Right" = "move right 40px"; + "${modifier}+Shift+Space" = "floating toggle"; + "${modifier}+Shift+Up" = "move up 40px"; + "${modifier}+Shift+a" = "exec emacsclient -cF '((name . \"Emacs Popup Anchor\"))' -e '(prot-window-popup-swarsel/open-calendar)'"; + "${modifier}+Shift+c" = "exec qalculate-gtk"; + "${modifier}+Shift+e" = "exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'"; + "${modifier}+Shift+f" = "exec swaymsg fullscreen"; + "${modifier}+Shift+m" = "exec emacsclient -cF '((name . \"Emacs Popup Anchor\"))' -e '(prot-window-popup-mu4e)'"; + "${modifier}+Shift+o" = "exec pass-fuzzel --otp --type"; + "${modifier}+Shift+p" = "exec pass-fuzzel --type"; + "${modifier}+Shift+s" = "exec slurp | grim -g - Pictures/Screenshots/$(date +'screenshot_%Y-%m-%d-%H%M%S.png')"; + "${modifier}+Shift+t" = "exec opacitytoggle"; + "${modifier}+Shift+v" = "exec wf-recorder -g '$(slurp -f %o -or)' -f ~/Videos/screenrecord_$(date +%Y-%m-%d-%H%M%S).mkv"; + "${modifier}+Space" = "exec fuzzel"; + "${modifier}+Up" = "focus up"; + "${modifier}+a" = "exec swarselcheck -s"; + "${modifier}+c" = "exec emacsclient -cF '((name . \"Emacs Popup Anchor\"))' -e '(prot-window-popup-org-capture)'"; + "${modifier}+d" = "exec swarselcheck -d"; + "${modifier}+e" = "exec emacsclient -nquc -a emacs -e \"(dashboard-open)\""; + "${modifier}+f" = "exec firefox"; + "${modifier}+h" = "exec hyprpicker | wl-copy"; + "${modifier}+m" = "exec swaymsg workspace back_and_forth"; + "${modifier}+o" = "exec pass-fuzzel --otp"; + "${modifier}+p" = "exec pass-fuzzel"; + "${modifier}+q" = "kill"; + "${modifier}+r" = "mode resize"; + "${modifier}+s" = "exec grim -g \"$(slurp)\" -t png - | wl-copy -t image/png"; + "${modifier}+t" = "exec emacsclient -cF '((name . \"Emacs Popup Anchor\"))' -e '(prot-window-popup-org-agenda)'"; + "${modifier}+w" = "exec swarselcheck -e"; + "${modifier}+x" = "exec swarselcheck -k"; + # "${modifier}+Escape" = "mode $exit"; + # "${modifier}+Return" = "exec kitty"; + "XF86AudioRaiseVolume" = "exec swayosd-client --output-volume raise"; + "XF86AudioLowerVolume" = "exec swayosd-client --output-volume lower"; + "XF86AudioMute" = "exec swayosd-client --output-volume mute-toggle"; + "XF86MonBrightnessUp" = "exec swayosd-client --brightness raise"; + "XF86MonBrightnessDown" = "exec swayosd-client --brightness lower"; + "XF86Display" = "exec wl-mirror eDP-1"; + # "--no-repeat Super_L" = "exec killall -SIGUSR1 .waybar-wrapped"; + # "${modifier}+z" = "exec killall -SIGUSR1 .waybar-wrapped"; + } + config.swarselsystems.keybindings; + modes = { + resize = { + Down = "resize grow height 10 px or 10 ppt"; + Escape = "mode default"; + Left = "resize shrink width 10 px or 10 ppt"; + Return = "mode default"; + Right = "resize grow width 10 px or 10 ppt"; + Up = "resize shrink height 10 px or 10 ppt"; + Tab = "move position center, resize set width 50 ppt height 50 ppt"; + }; + }; + defaultWorkspace = "workspace 1:一"; + # output = { + # "${config.swarselsystems.sharescreen}" = { + # bg = "${self}/files/wallpaper/lenovowp.png ${config.stylix.imageScalingMode}"; + # }; + # "Philips Consumer Electronics Company PHL BDM3270 AU11806002320" = { + # bg = "${self}/files/wallpaper/standwp.png ${config.stylix.imageScalingMode}"; + # }; + # }; + input = config.swarselsystems.standardinputs; + workspaceOutputAssign = + let + workplaceSets = lib.mapAttrs' eachOutput config.swarselsystems.monitors; + workplaceOutputs = map (key: lib.getAttr key workplaceSets) (lib.attrNames workplaceSets); + in + workplaceOutputs; + startup = config.swarselsystems.startup ++ [ + { command = "kitty -T kittyterm -o confirm_os_window_close=0 zellij attach --create kittyterm"; } + { command = "sleep 60; kitty -T spotifytui -o confirm_os_window_close=0 spotify_player"; } + { command = "mako"; } + ]; + seat = { + "*" = { + hide_cursor = "when-typing enable"; + }; + }; + window = { + border = 1; + titlebar = false; + }; + assigns = { + "15:L" = [{ app_id = "teams-for-linux"; }]; + }; + floating = { + border = 1; + criteria = [ + { app_id = "qalculate-gtk"; } + { app_id = "blueman"; } + { app_id = "pavucontrol"; } + { app_id = "syncthingtray"; } + { app_id = "Element"; } + { app_id = "1Password"; } + { app_id = "com.nextcloud.desktopclient.nextcloud"; } + { title = "(?:Open|Save) (?:File|Folder|As)"; } + { title = "^Add$"; } + { title = "^Picture-in-Picture$"; } + { title = "Syncthing Tray"; } + { title = "^Emacs Popup Frame$"; } + { title = "^Emacs Popup Anchor$"; } + { title = "^spotifytui$"; } + { title = "^kittyterm$"; } + { app_id = "vesktop"; } + { window_role = "pop-up"; } + { window_role = "bubble"; } + { window_role = "dialog"; } + { window_role = "task_dialog"; } + { window_role = "menu"; } + { window_role = "Preferences"; } + ]; + titlebar = false; + }; + window = { + commands = [ + { + command = "opacity 0.95"; + criteria = { + class = ".*"; + }; + } + { + command = "opacity 1"; + criteria = { + app_id = "at.yrlf.wl_mirror"; + }; + } + { + command = "opacity 1"; + criteria = { + app_id = "Gimp-2.10"; + }; + } + { + command = "opacity 0.99"; + criteria = { + app_id = "firefox"; + }; + } + { + command = "opacity 0.99"; + criteria = { + app_id = "chromium-browser"; + }; + } + { + command = "sticky enable, shadows enable"; + criteria = { + title = "^Picture-in-Picture$"; + }; + } + { + command = "resize set width 60 ppt height 60 ppt, opacity 0.99, sticky enable"; + criteria = { + title = "^Emacs Popup Frame$"; + }; + } + { + command = "move container to scratchpad"; + criteria = { + title = "^Emacs Popup Anchor$"; + }; + } + { + command = "resize set width 60 ppt height 60 ppt, opacity 0.8, sticky enable, border normal, move container to scratchpad"; + criteria = { + title = "^kittyterm$"; + }; + } + { + command = "resize set width 60 ppt height 60 ppt, opacity 0.95, sticky enable, border normal, move container to scratchpad"; + criteria = { + title = "^spotifytui$"; + }; + } + { + + command = "resize set width 60 ppt height 60 ppt, sticky enable, move container to scratchpad"; + criteria = { + class = "Spotify"; + }; + } + { + command = "resize set width 60 ppt height 60 ppt, sticky enable"; + criteria = { + app_id = "vesktop"; + }; + } + { + command = "resize set width 60 ppt height 60 ppt, sticky enable"; + criteria = { + class = "Element"; + }; + } + # { + # command = "resize set width 60 ppt height 60 ppt, sticky enable, move container to scratchpad"; + # criteria = { + # app_id="^$"; + # class="^$"; + # }; + # } + ]; + }; + gaps = { + inner = 5; + }; + }; + extraSessionCommands = '' + export XDG_CURRENT_DESKTOP=sway; + export XDG_SESSION_DESKTOP=sway; + export _JAVA_AWT_WM_NONREPARENTING=1; + export GITHUB_NOTIFICATION_TOKEN_PATH=${confLib.getConfig.sops.secrets.github-notifications-token.path}; + '' + vars.waylandExports; + # extraConfigEarly = " + # exec systemctl --user import-environment DISPLAY WAYLAND_DISPLAY SWAYSOCK + # exec hash dbus-update-activation-environment 2>/dev/null && dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK + # "; + extraConfig = + let + inherit (config.wayland.windowManager.sway.config) modifier; + swayfxSettings = config.swarselsystems.swayfxConfig; + in + " + # exec_always autotiling + # set $exit \"exit: [s]leep, [l]ock, [p]oweroff, [r]eboot, [u]ser logout\" + + # mode $exit { + # bindsym --to-code { + # s exec \"systemctl suspend\", mode \"default\" + # h exec \"systemctl hibernate\", mode \"default\" + # l exec \"swaylock --screenshots --clock --effect-blur 7x5 --effect-vignette 0.5:0.5 --fade-in 0.2 --daemonize\", mode \"default\ + # p exec \"systemctl poweroff\" + # r exec \"systemctl reboot\" + # u exec \"swaymsg exit\" + + # Return mode \"default\" + # Escape mode \"default\" + # ${modifier}+Escape mode \"default\" + # } + # } + + exec systemctl --user import-environment + # exec swayidle -w + + seat * hide_cursor 2000 + + exec_always kill -1 $(pidof kanshi) + + bindswitch --locked lid:on exec kanshictl switch lidclosed + bindswitch --locked lid:off exec kanshictl switch lidopen + + ${swayfxSettings} + "; + }; + }; +} diff --git a/modules-clone/home/common/swayidle.nix b/modules-clone/home/common/swayidle.nix new file mode 100644 index 0000000..50e5f73 --- /dev/null +++ b/modules-clone/home/common/swayidle.nix @@ -0,0 +1,37 @@ +{ lib, config, pkgs, ... }: +let + moduleName = "swayidle"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + services.${moduleName} = + let + brightnessctl = "${lib.getExe pkgs.brightnessctl}"; + swaylock = "${lib.getExe pkgs.swaylock-effects}"; + suspend = "${pkgs.systemd}/bin/systemctl suspend"; + noctalia = "/etc/profiles/per-user/${config.swarselsystems.mainUser}/bin/noctalia-shell ipc call"; + in + { + enable = true; + # systemdTarget = "sway-session.target"; + extraArgs = [ "-w" ]; + timeouts = [ + { timeout = 60; command = "${brightnessctl} -s; ${brightnessctl} set 80%-"; resumeCommand = "${brightnessctl} -r"; } + # { timeout = 300; command = "${lib.getExe pkgs.swaylock-effects} -f --screenshots --clock --effect-blur 7x5 --effect-vignette 0.5:0.5 --fade-in 0.2"; } + # { timeout = 300; command = "${swaylock} -f"; } + { timeout = 300; command = "${noctalia} lockScreen lock || ${swaylock} -f"; } + # { timeout = 600; command = ''${pkgs.sway}/bin/swaymsg "output * dpms off"; resumeCommand = "${pkgs.sway}/bin/swaymsg output * dpms on''; } + # { timeout = 600; command = "${noctalia} sessionMenu lockAndSuspend || ${suspend}"; } + { timeout = 600; command = "${suspend}"; } + ]; + events = { + # { event = "before-sleep"; command = "${noctalia} lockScreen lock || ${lib.getExe pkgs.swaylock-effects} -f --screenshots --clock --effect-blur 7x5 --effect-vignette 0.5:0.5 --fade-in 0.2"; } + # { event = "after-resume"; command = "${swaylock} -f "; } + before-sleep = "${noctalia} lockScreen lock || ${swaylock} -f "; + # lock = "${swaylock} -f "; + }; + }; + }; + +} diff --git a/modules-clone/home/common/swaylock.nix b/modules-clone/home/common/swaylock.nix new file mode 100644 index 0000000..a3483af --- /dev/null +++ b/modules-clone/home/common/swaylock.nix @@ -0,0 +1,21 @@ +{ lib, config, pkgs, ... }: +let + moduleName = "swaylock"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + package = pkgs.swaylock-effects; + settings = { + screenshots = true; + clock = true; + effect-blur = "7x5"; + effect-vignette = "0.5:0.5"; + fade-in = "0.2"; + }; + }; + }; + +} diff --git a/modules-clone/home/common/swayosd.nix b/modules-clone/home/common/swayosd.nix new file mode 100644 index 0000000..4d81e1a --- /dev/null +++ b/modules-clone/home/common/swayosd.nix @@ -0,0 +1,12 @@ +{ lib, pkgs, config, confLib, ... }: +{ + options.swarselmodules.swayosd = lib.mkEnableOption "swayosd settings"; + config = lib.mkIf config.swarselmodules.swayosd { + systemd.user.services.swayosd = confLib.overrideTarget "sway-session.target"; + services.swayosd = { + enable = true; + package = pkgs.swayosd; + topMargin = 0.5; + }; + }; +} diff --git a/modules-clone/home/common/symlink.nix b/modules-clone/home/common/symlink.nix new file mode 100644 index 0000000..8f67660 --- /dev/null +++ b/modules-clone/home/common/symlink.nix @@ -0,0 +1,32 @@ +{ self, lib, config, ... }: +{ + options.swarselmodules.symlink = lib.mkEnableOption "symlink settings"; + config = lib.mkIf config.swarselmodules.symlink { + home.file = { + "init.el" = lib.mkDefault { + source = self + /files/emacs/init.el; + target = ".emacs.d/init.el"; + }; + "early-init.el" = { + source = self + /files/emacs/early-init.el; + target = ".emacs.d/early-init.el"; + }; + # on NixOS, Emacs does not find the aspell dicts easily. Write the configuration manually + ".aspell.conf" = { + source = self + /files/config/.aspell.conf; + target = ".aspell.conf"; + }; + ".gitmessage" = { + source = self + /files/git/.gitmessage; + target = ".gitmessage"; + }; + }; + + xdg.configFile = { + "tridactyl/tridactylrc".source = self + /files/firefox/tridactyl/tridactylrc; + "tridactyl/themes/base16-codeschool.css".source = self + /files/firefox/tridactyl/themes/base16-codeschool.css; + "tridactyl/themes/swarsel.css".source = self + /files/firefox/tridactyl/themes/swarsel.css; + # "swayidle/config".source = self + /files/swayidle/config; + }; + }; +} diff --git a/modules-clone/home/common/syncthing-tray.nix b/modules-clone/home/common/syncthing-tray.nix new file mode 100644 index 0000000..e0b5898 --- /dev/null +++ b/modules-clone/home/common/syncthing-tray.nix @@ -0,0 +1,120 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.syncthing-tray = lib.mkEnableOption "enable syncthing applet for tray"; + config = lib.mkIf config.swarselmodules.syncthing-tray { + + home.activation.setupSyncthingIni = + let + syncthingApiEnvVarName = "SYNCTHING_API_KEY"; + syncthingIni = { + file = "${config.home.homeDirectory}/.config/syncthingtray.ini"; + content = '' + [General] + v=2.0.2 + + [qt] + customfont=false + customicontheme=false + customlocale=false + custompalette=false + customstylesheet=false + customwidgetstyle=false + font="Cantarell,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1" + icontheme=hicolor + iconthemepath= + locale=en_US + palette="@Variant(\0\0\0\x44\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff jj\x86\x86\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\x17\x17\x1d\x1d##\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff^^\xc4\xc4\xff\xff\0\0\x1\x1\xff\xff\xc0\xc0nn\xce\xce\0\0\x1\x1\xff\xff\x17\x17\x1d\x1d##\0\0\x1\x1\xff\xff^^\xc4\xc4\xff\xff\0\0\x1\x1\xff\xff jj\x86\x86\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff^^\xc4\xc4\xff\xff\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff^^\xc4\xc4\xff\xff\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\x17\x17\x1d\x1d##\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\x66\x66\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff^^\xc4\xc4\xff\xff\0\0\x1\x1\xff\xff\xc0\xc0nn\xce\xce\0\0\x1\x1\xff\xff\x17\x17\x1d\x1d##\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff jj\x86\x86\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\0\0::ff\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff\x1d\x1d%%,,\0\0\x1\x1\xff\xff\x17\x17\x1d\x1d##\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x2\xff\xffP\x14\xff\xff\x65\x65\0\0\x1\x1\xff\xff\xa0\xa0\xb3\xb3\xc5\xc5\0\0\x1\x1\xff\xff^^\xc4\xc4\xff\xff\0\0\x1\x1\xff\xff\xc0\xc0nn\xce\xce\0\0\x1\x1\xff\xff\x17\x17\x1d\x1d##\0\0)" + plugindir= + stylesheetpath= + trpath= + widgetstyle= + + [startup] + considerForReconnect=false + considerLauncherForReconnect=false + showButton=false + showLauncherButton=false + stopOnMetered=false + stopServiceOnMetered=false + syncthingArgs="serve --no-browser --logflags=3" + syncthingAutostart=false + syncthingPath=syncthing + syncthingUnit=syncthing.service + systemUnit=false + useLibSyncthing=false + + [tray] + connections\1\apiKey=@ByteArray(''$${syncthingApiEnvVarName}) + connections\1\authEnabled=falsex + connections\1\autoConnect=true + connections\1\devStatsPollInterval=60000 + connections\1\diskEventLimit=200 + connections\1\errorsPollInterval=30000 + connections\1\httpsCertPath=${config.home.homeDirectory}/.config/syncthing/https-cert.pem + connections\1\label=Primary instance + connections\1\localPath= + connections\1\longPollingTimeout=0 + connections\1\password= + connections\1\pauseOnMetered=false + connections\1\reconnectInterval=30000 + connections\1\requestTimeout=0 + connections\1\statusComputionFlags=123 + connections\1\syncthingUrl=http://${config.services.syncthing.guiAddress} + connections\1\trafficPollInterval=5000 + connections\1\userName= + connections\size=1 + dbusNotifications=true + distinguishTrayIcons=false + frameStyle=16 + ignoreInavailabilityAfterStart=15 + notifyOnDisconnect=true + notifyOnErrors=true + notifyOnLauncherErrors=true + notifyOnLocalSyncComplete=false + notifyOnNewDeviceConnects=false + notifyOnNewDirectoryShared=false + notifyOnRemoteSyncComplete=false + positioning\assumedIconPos=@Point(0 0) + positioning\useAssumedIconPosition=false + positioning\useCursorPos=true + preferIconsFromTheme=false + showDownloads=false + showSyncthingNotifications=true + showTabTexts=true + showTraffic=true + statusIcons="#ff26b6db,#ff0882c8,#ffffffff;#ffdb3c26,#ffc80828,#ffffffff;#ffc9ce3b,#ffebb83b,#ffffffff;#ff2d9d69,#ff2d9d69,#ffffffff;#ff26b6db,#ff0882c8,#ffffffff;#ff26b6db,#ff0882c8,#ffffffff;#ffa9a9a9,#ff58656c,#ffffffff;#ffa9a9a9,#ff58656c,#ffffffff;#ffa9a9a9,#ff58656c,#ffffffff" + statusIconsRenderSize=@Size(32 32) + statusIconsStrokeWidth=0 + tabPos=1 + trayIcons="#ff26b6db,#ff0882c8,#ffffffff;#ffdb3c26,#ffc80828,#ffffffff;#ffc9ce3b,#ffebb83b,#ffffffff;#ff2d9d69,#ff2d9d69,#ffffffff;#ff26b6db,#ff0882c8,#ffffffff;#ff26b6db,#ff0882c8,#ffffffff;#ffa9a9a9,#ff58656c,#ffffffff;#ffa9a9a9,#ff58656c,#ffffffff;#ffa9a9a9,#ff58656c,#ffffffff" + trayIconsRenderSize=@Size(32 32) + trayIconsStrokeWidth=0 + trayMenuSize=@Size(575 475) + usePaletteForStatusIcons=false + usePaletteForTrayIcons=false + windowType=0 + + [webview] + customCommand= + disabled=false + mode=0 + + ''; + }; + in + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + set -eu + + if [ ! -f ${syncthingIni.file} ]; then + cat >${syncthingIni.file} <<'EOF' + ${syncthingIni.content} + EOF + export ${syncthingApiEnvVarName}=$(cat /run/syncthing-init/api_key) + ${lib.getExe pkgs.envsubst} -i ${syncthingIni.file} -o ${syncthingIni.file} + unset ${syncthingApiEnvVarName} + fi + ''; + + }; + +} diff --git a/modules-clone/home/common/tmux.nix b/modules-clone/home/common/tmux.nix new file mode 100644 index 0000000..f642c6b --- /dev/null +++ b/modules-clone/home/common/tmux.nix @@ -0,0 +1,99 @@ +{ lib, config, pkgs, ... }: +let + tmux-super-fingers = pkgs.tmuxPlugins.mkTmuxPlugin + { + pluginName = "tmux-super-fingers"; + version = "unstable-2023-01-06"; + src = pkgs.fetchFromGitHub { + owner = "artemave"; + repo = "tmux_super_fingers"; + rev = "2c12044984124e74e21a5a87d00f844083e4bdf7"; + sha256 = "sha256-cPZCV8xk9QpU49/7H8iGhQYK6JwWjviL29eWabuqruc="; + }; + }; +in +{ + options.swarselmodules.tmux = lib.mkEnableOption "tmux settings"; + config = lib.mkIf config.swarselmodules.tmux { + home.packages = with pkgs; [ + lsof + sesh + ]; + + programs.tmux = { + enable = true; + shell = "${pkgs.zsh}/bin/zsh"; + terminal = "tmux-256color"; + historyLimit = 100000; + plugins = with pkgs; + [ + tmuxPlugins.tmux-thumbs + { + plugin = tmux-super-fingers; + extraConfig = "set -g @super-fingers-key f"; + } + + tmuxPlugins.sensible + # must be before continuum edits right status bar + { + plugin = tmuxPlugins.catppuccin; + extraConfig = '' + set -g @catppuccin_flavour 'frappe' + set -g @catppuccin_window_tabs_enabled on + set -g @catppuccin_date_time "%H:%M" + ''; + } + { + plugin = tmuxPlugins.resurrect; + extraConfig = '' + set -g @resurrect-strategy-vim 'session' + set -g @resurrect-strategy-nvim 'session' + set -g @resurrect-capture-pane-contents 'on' + ''; + } + { + plugin = tmuxPlugins.continuum; + extraConfig = '' + set -g @continuum-restore 'on' + set -g @continuum-boot 'on' + set -g @continuum-save-interval '10' + ''; + } + tmuxPlugins.better-mouse-mode + tmuxPlugins.yank + ]; + extraConfig = '' + set -g default-terminal "tmux-256color" + set -ag terminal-overrides ",xterm-256color:RGB" + + set-option -g prefix C-a + unbind-key C-b + bind-key C-a send-prefix + + set -g mouse on + + # Open new split at cwd of current split + bind | split-window -h -c "#{pane_current_path}" + bind - split-window -v -c "#{pane_current_path}" + + # Use vim keybindings in copy mode + set-window-option -g mode-keys vi + + # v in copy mode starts making selection + bind-key -T copy-mode-vi v send-keys -X begin-selection + bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle + bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel + + # Escape turns on copy mode + bind Escape copy-mode + + set-option -g status-position top + + # make Prefix p paste the buffer. + unbind p + bind p paste-buffer + + ''; + }; + }; +} diff --git a/modules-clone/home/common/vesktop-tray.nix b/modules-clone/home/common/vesktop-tray.nix new file mode 100644 index 0000000..55bae7c --- /dev/null +++ b/modules-clone/home/common/vesktop-tray.nix @@ -0,0 +1,29 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.vesktop-tray = lib.mkEnableOption "enable vesktop applet for tray"; + config = lib.mkIf config.swarselmodules.vesktop-tray { + + systemd.user.services.vesktop-applet = { + Unit = { + Description = "Vesktop applet"; + Requires = [ "graphical-session.target" ]; + After = [ + "graphical-session.target" + "tray.target" + ]; + PartOf = [ + "tray.target" + ]; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs.vesktop}/bin/vesktop --start-minimized --ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime"; + }; + }; + }; + +} diff --git a/modules-clone/home/common/vesktop.nix b/modules-clone/home/common/vesktop.nix new file mode 100644 index 0000000..27b14e4 --- /dev/null +++ b/modules-clone/home/common/vesktop.nix @@ -0,0 +1,80 @@ +{ lib, pkgs, config, ... }: +let + moduleName = "vesktop"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "enable ${moduleName} and settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.${moduleName} = { + enable = true; + package = pkgs.vesktop; + settings = { + appBadge = false; + arRPC = false; + checkUpdates = false; + customTitleBar = false; + disableMinSize = true; + minimizeToTray = true; + tray = true; + staticTitle = true; + hardwareAcceleration = true; + discordBranch = "stable"; + }; + vencord = { + useSystem = true; + settings = { + autoUpdate = false; + autoUpdateNotification = false; + enableReactDevtools = false; + frameless = false; + transparent = false; + winCtrlQ = false; + notifyAboutUpdates = false; + useQuickCss = true; + disableMinSize = true; + winNativeTitleBar = false; + plugins = { + MessageLogger = { + enabled = true; + ignoreSelf = true; + }; + ChatInputButtonAPI = { + enabled = false; + }; + CommandsAPI = { + enabled = true; + }; + MemberListDecoratorsAPI = { + enabled = false; + }; + MessageAccessoriesAPI = { + enabled = true; + }; + MessageDecorationsAPI = { + enabled = false; + }; + MessageEventsAPI = { + enabled = false; + }; + MessagePopoverAPI = { + enabled = false; + }; + MessageUpdaterAPI = { + enabled = false; + }; + ServerListAPI = { + enabled = false; + }; + UserSettingsAPI = { + enabled = true; + }; + FakeNitro = { + enabled = true; + }; + }; + }; + }; + }; + }; + +} diff --git a/modules-clone/home/common/waybar.nix b/modules-clone/home/common/waybar.nix new file mode 100644 index 0000000..2b052c6 --- /dev/null +++ b/modules-clone/home/common/waybar.nix @@ -0,0 +1,328 @@ +{ self, config, lib, pkgs, type, ... }: +let + inherit (config.swarselsystems) xdgDir; + generateIcons = n: lib.concatStringsSep " " (builtins.map (x: "{icon" + toString x + "}") (lib.range 0 (n - 1))); + modulesLeft = [ + "custom/outer-left-arrow-dark" + "mpris" + "custom/left-arrow-light" + "network" + "custom/vpn" + "custom/left-arrow-dark" + "pulseaudio" + "custom/left-arrow-light" + ]; + modulesRight = [ + "custom/left-arrow-dark" + "group/hardware" + "custom/left-arrow-light" + "clock#2" + "custom/left-arrow-dark" + "clock#1" + ]; +in +{ + options.swarselmodules.waybar = lib.mkEnableOption "waybar settings"; + options.swarselsystems = { + cpuCount = lib.mkOption { + type = lib.types.int; + default = 8; + }; + temperatureHwmon = { + isAbsolutePath = lib.mkEnableOption "absolute temperature path"; + path = lib.mkOption { + type = lib.types.str; + default = ""; + }; + input-filename = lib.mkOption { + type = lib.types.str; + default = ""; + }; + }; + waybarModules = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = modulesLeft ++ [ + "custom/pseudobat" + ] ++ modulesRight; + }; + cpuString = lib.mkOption { + type = lib.types.str; + default = generateIcons config.swarselsystems.cpuCount; + description = "The generated icons string for use by Waybar."; + internal = true; + }; + }; + config = lib.mkIf config.swarselmodules.waybar ({ + + swarselsystems = { + waybarModules = lib.mkIf config.swarselsystems.isLaptop (modulesLeft ++ [ + "battery" + ] ++ modulesRight); + }; + + services.playerctld.enable = true; + + programs.waybar = { + enable = true; + systemd = { + enable = false; + target = "sway-session.target"; + # inherit (config.wayland.systemd) target; + }; + settings = { + mainBar = { + ipc = true; + id = "bar-0"; + # mode = "hide"; + # mode = "overlay"; + # passthrough = false; + # start_hidden = true; + layer = "top"; + position = "top"; + modules-left = [ "sway/workspaces" "niri/workspaces" "custom/outer-right-arrow-dark" "niri/window" "sway/window" ]; + modules-center = [ "sway/mode" "privacy" "custom/github" "custom/configwarn" "custom/nix-updates" ]; + "sway/mode" = { + format = "{}"; + }; + + "niri/window" = { + format = "{title} ({app_id})"; + }; + + modules-right = config.swarselsystems.waybarModules; + + "custom/pseudobat" = lib.mkIf (!config.swarselsystems.isLaptop) { + format = ""; + on-click-right = "${pkgs.wlogout}/bin/wlogout -p layer-shell"; + }; + + "custom/configwarn" = { + exec = "${pkgs.waybarupdate}/bin/waybarupdate"; + interval = 60; + }; + + "custom/scratchpad-indicator" = { + interval = 3; + exec = "${pkgs.swayfx}/bin/swaymsg -t get_tree | ${pkgs.jq}/bin/jq 'recurse(.nodes[]) | first(select(.name==\"__i3_scratch\")) | .floating_nodes | length | select(. >= 1)'"; + format = "{} "; + on-click = "${pkgs.swayfx}/bin/swaymsg 'scratchpad show'"; + on-click-right = "${pkgs.swayfx}/bin/swaymsg 'move scratchpad'"; + }; + + "custom/github" = { + format = "{}  "; + return-type = "json"; + interval = 60; + exec = "${pkgs.github-notifications}/bin/github-notifications"; + on-click = "${pkgs.xdg-utils}/bin/xdg-open https://github.com/notifications"; + }; + + idle_inhibitor = { + format = "{icon}"; + format-icons = { + activated = ""; + deactivated = ""; + }; + }; + + "group/hardware" = { + orientation = "inherit"; + drawer = { + "transition-left-to-right" = false; + }; + modules = [ + "tray" + "temperature" + "power-profiles-daemon" + "custom/left-arrow-light" + "custom/left-arrow-dark" + "custom/scratchpad-indicator" + "custom/left-arrow-light" + "disk" + "custom/left-arrow-dark" + "memory" + "custom/left-arrow-light" + "cpu" + "custom/left-arrow-dark" + "backlight/slider" + "idle_inhibitor" + ]; + }; + + "backlight/slider" = { + min = 0; + max = 100; + orientation = "horizontal"; + device = "intel_backlight"; + }; + + power-profiles-daemon = { + format = "{icon}"; + tooltip-format = "Power profile: {profile}\nDriver: {driver}"; + tooltip = true; + format-icons = { + "default" = ""; + "performance" = ""; + "balanced" = ""; + "power-saver" = ""; + }; + }; + + temperature = { + hwmon-path = lib.mkIf (!config.swarselsystems.temperatureHwmon.isAbsolutePath) config.swarselsystems.temperatureHwmon.path; + hwmon-path-abs = lib.mkIf config.swarselsystems.temperatureHwmon.isAbsolutePath config.swarselsystems.temperatureHwmon.path; + input-filename = lib.mkIf config.swarselsystems.temperatureHwmon.isAbsolutePath config.swarselsystems.temperatureHwmon.input-filename; + critical-threshold = 80; + format-critical = " {temperatureC}°C"; + format = " {temperatureC}°C"; + + }; + + mpris = { + format = "{player_icon} {title} [{position}/{length}]"; + format-paused = "{player_icon} {title} [{position}/{length}]"; + player-icons = { + "default" = "▶ "; + "mpv" = "🎵 "; + "spotify" = " "; + }; + status-icons = { + "paused" = " "; + }; + interval = 1; + title-len = 20; + artist-len = 20; + album-len = 10; + }; + "custom/left-arrow-dark" = { + format = ""; + tooltip = false; + }; + "custom/outer-left-arrow-dark" = { + format = ""; + tooltip = false; + }; + "custom/left-arrow-light" = { + format = ""; + tooltip = false; + }; + "custom/right-arrow-dark" = { + format = ""; + tooltip = false; + }; + "custom/outer-right-arrow-dark" = { + format = ""; + tooltip = false; + }; + "custom/right-arrow-light" = { + format = ""; + tooltip = false; + }; + "sway/workspaces" = { + disable-scroll = true; + format = "{name}"; + }; + + "clock#1" = { + min-length = 8; + interval = 1; + format = "{:%H:%M:%S}"; + # on-click-right= "gnome-clocks"; + tooltip-format = "{:%Y %B}\n{calendar}"; + }; + + "clock#2" = { + format = "{:%d. %B %Y}"; + # on-click-right= "gnome-clocks"; + tooltip-format = "{:%Y %B}\n{calendar}"; + }; + + pulseaudio = { + format = "{icon} {volume:2}%"; + format-bluetooth = "{icon} {volume}%"; + format-muted = "MUTE"; + format-icons = { + headphones = ""; + default = [ + "" + "" + ]; + }; + scroll-step = 1; + on-click = "${pkgs.pamixer}/bin/pamixer -t"; + on-click-right = "${pkgs.pavucontrol}/bin/pavucontrol"; + }; + + memory = { + interval = 5; + format = " {}%"; + tooltip-format = "Memory: {used:0.1f}G/{total:0.1f}G\nSwap: {swapUsed}G/{swapTotal}G"; + }; + cpu = { + format = config.swarselsystems.cpuString; + min-length = 6; + interval = 5; + format-icons = [ "▁" "▂" "▃" "▄" "▅" "▆" "▇" "█" ]; + # on-click-right= "com.github.stsdc.monitor"; + on-click-right = "${pkgs.kitty}/bin/kitty -o confirm_os_window_close=0 btm"; + + }; + "custom/vpn" = { + format = "()"; + exec = "echo '{\"class\": \"connected\"}'"; + exec-if = "${pkgs.toybox}/bin/test -d /proc/sys/net/ipv4/conf/tun0"; + return-type = "json"; + interval = 5; + }; + battery = { + states = { + "warning" = 60; + "error" = 30; + "critical" = 15; + }; + interval = 5; + format = "{icon} {capacity}%"; + format-charging = "{capacity}% "; + format-plugged = "{capacity}% "; + format-icons = [ + "" + "" + "" + "" + "" + ]; + on-click-right = "wlogout -p layer-shell"; + }; + disk = { + interval = 30; + format = "Disk {percentage_used:2}%"; + path = "/"; + states = { + "warning" = 80; + "critical" = 90; + }; + tooltip-format = "{used} used out of {total} on {path} ({percentage_used}%)\n{free} free on {path} ({percentage_free}%)"; + }; + tray = { + icon-size = 20; + }; + network = { + interval = 5; + format-wifi = "{signalStrength}% "; + format-ethernet = ""; + format-linked = "{ifname} (No IP) "; + format-disconnected = "Disconnected ⚠"; + format-alt = "{ifname}: {ipaddr}/{cidr}"; + tooltip-format-ethernet = "{ifname} via {gwaddr}: {essid} {ipaddr}/{cidr}\n\n⇡{bandwidthUpBytes} ⇣{bandwidthDownBytes}"; + tooltip-format-wifi = "{ifname} via {gwaddr}: {essid} {ipaddr}/{cidr} \n{signaldBm}dBm @ {frequency}MHz\n\n⇡{bandwidthUpBytes} ⇣{bandwidthDownBytes}"; + }; + }; + }; + style = builtins.readFile (self + /files/waybar/style.css); + }; + } // lib.optionalAttrs (type != "nixos") { + sops.secrets = lib.mkIf (!config.swarselsystems.isPublic && !config.swarselsystems.isNixos) { + github-notifications-token = { path = "${xdgDir}/secrets/github-notifications-token"; }; + }; + }); +} diff --git a/modules-clone/home/common/yubikey-touch-detector.nix b/modules-clone/home/common/yubikey-touch-detector.nix new file mode 100644 index 0000000..fc28488 --- /dev/null +++ b/modules-clone/home/common/yubikey-touch-detector.nix @@ -0,0 +1,32 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.yubikeytouch = lib.mkEnableOption "yubikey touch detector service settings"; + config = lib.mkIf config.swarselmodules.yubikeytouch { + systemd.user.services.yubikey-touch-detector = { + Unit = { + Description = "Detects when your YubiKey is waiting for a touch"; + Requires = [ "yubikey-touch-detector.socket" ]; + }; + Service = { + ExecStart = "${pkgs.yubikey-touch-detector}/bin/yubikey-touch-detector --libnotify"; + EnvironmentFile = "-%E/yubikey-touch-detector/service.conf"; + }; + Install = { + Also = [ "yubikey-touch-detector.socket" ]; + WantedBy = [ "default.target" ]; + }; + }; + systemd.user.sockets.yubikey-touch-detector = { + Unit = { + Description = "Unix socket activation for YubiKey touch detector service"; + }; + Socket = { + ListenStream = "%t/yubikey-touch-detector.socket"; + RemoveOnStop = true; + }; + Install = { + WantedBy = [ "sockets.target" ]; + }; + }; + }; +} diff --git a/modules-clone/home/common/yubikey.nix b/modules-clone/home/common/yubikey.nix new file mode 100644 index 0000000..5a91419 --- /dev/null +++ b/modules-clone/home/common/yubikey.nix @@ -0,0 +1,21 @@ +{ lib, config, confLib, type, ... }: +let + inherit (config.swarselsystems) homeDir; +in +{ + options.swarselmodules.yubikey = lib.mkEnableOption "yubikey settings"; + + config = lib.mkIf config.swarselmodules.yubikey ({ + + pam.yubico.authorizedYubiKeys = lib.mkIf (config.swarselsystems.isNixos && !config.swarselsystems.isPublic) { + ids = [ + confLib.getConfig.repo.secrets.common.yubikeys.dev1 + confLib.getConfig.secrets.common.yubikeys.dev2 + ]; + }; + } // lib.optionalAttrs (type != "nixos") { + sops.secrets = lib.mkIf (!config.swarselsystems.isPublic) { + u2f-keys = { path = "${homeDir}/.config/Yubico/u2f_keys"; }; + }; + }); +} diff --git a/modules-clone/home/common/zellij-keybinds.nix b/modules-clone/home/common/zellij-keybinds.nix new file mode 100644 index 0000000..80d28c5 --- /dev/null +++ b/modules-clone/home/common/zellij-keybinds.nix @@ -0,0 +1,1144 @@ +{ lib, config, ... }: +{ + config = lib.mkIf config.swarselmodules.zellij { + programs.zellij = { + settings.keybinds = { + _props.clear-defaults = true; + + locked = { + _children = [ + { + bind = { + _args = [ "Ctrl g" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + ]; + }; + + pane = { + _children = [ + { + bind = { + _args = [ "Ctrl p" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + { + bind = { + _args = [ "left" ]; + _children = [{ MoveFocus._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "down" ]; + _children = [{ MoveFocus._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "up" ]; + _children = [{ MoveFocus._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "right" ]; + _children = [{ MoveFocus._args = [ "right" ]; }]; + }; + } + { + bind = { + _args = [ "h" ]; + _children = [{ MoveFocus._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "j" ]; + _children = [{ MoveFocus._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "k" ]; + _children = [{ MoveFocus._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "l" ]; + _children = [{ MoveFocus._args = [ "right" ]; }]; + }; + } + { + bind = { + _args = [ "d" ]; + _children = [ + { NewPane._args = [ "down" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "e" ]; + _children = [ + { TogglePaneEmbedOrFloating = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "f" ]; + _children = [ + { ToggleFocusFullscreen = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "n" ]; + _children = [ + { NewPane = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "p" ]; + _children = [{ SwitchFocus = { }; }]; + }; + } + { + bind = { + _args = [ "f12" ]; + _children = [ + { ToggleFloatingPanes = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + ]; + }; + + tab = { + _children = [ + { + bind = { + _args = [ "Ctrl t" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + { + bind = { + _args = [ "left" ]; + _children = [{ GoToPreviousTab = { }; }]; + }; + } + { + bind = { + _args = [ "down" ]; + _children = [{ GoToNextTab = { }; }]; + }; + } + { + bind = { + _args = [ "up" ]; + _children = [{ GoToPreviousTab = { }; }]; + }; + } + { + bind = { + _args = [ "right" ]; + _children = [{ GoToNextTab = { }; }]; + }; + } + { + bind = { + _args = [ "1" ]; + _children = [ + { GoToTab._args = [ 1 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "2" ]; + _children = [ + { GoToTab._args = [ 2 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "3" ]; + _children = [ + { GoToTab._args = [ 3 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "4" ]; + _children = [ + { GoToTab._args = [ 4 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "5" ]; + _children = [ + { GoToTab._args = [ 5 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "6" ]; + _children = [ + { GoToTab._args = [ 6 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "7" ]; + _children = [ + { GoToTab._args = [ 7 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "8" ]; + _children = [ + { GoToTab._args = [ 8 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "9" ]; + _children = [ + { GoToTab._args = [ 9 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "h" ]; + _children = [{ GoToPreviousTab = { }; }]; + }; + } + { + bind = { + _args = [ "j" ]; + _children = [{ GoToNextTab = { }; }]; + }; + } + { + bind = { + _args = [ "k" ]; + _children = [{ GoToPreviousTab = { }; }]; + }; + } + { + bind = { + _args = [ "l" ]; + _children = [{ GoToNextTab = { }; }]; + }; + } + { + bind = { + _args = [ "n" ]; + _children = [ + { NewTab = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "r" ]; + _children = [ + { SwitchToMode._args = [ "renametab" ]; } + { TabNameInput._args = [ 0 ]; } + ]; + }; + } + { + bind = { + _args = [ "s" ]; + _children = [ + { ToggleActiveSyncTab = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "x" ]; + _children = [ + { CloseTab = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + ]; + }; + + resize = { + _children = [ + { + bind = { + _args = [ "Ctrl n" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + { + bind = { + _args = [ "left" ]; + _children = [{ Resize._args = [ "Increase left" ]; }]; + }; + } + { + bind = { + _args = [ "down" ]; + _children = [{ Resize._args = [ "Increase down" ]; }]; + }; + } + { + bind = { + _args = [ "up" ]; + _children = [{ Resize._args = [ "Increase up" ]; }]; + }; + } + { + bind = { + _args = [ "right" ]; + _children = [{ Resize._args = [ "Increase right" ]; }]; + }; + } + { + bind = { + _args = [ "+" ]; + _children = [{ Resize._args = [ "Increase" ]; }]; + }; + } + { + bind = { + _args = [ "-" ]; + _children = [{ Resize._args = [ "Decrease" ]; }]; + }; + } + { + bind = { + _args = [ "=" ]; + _children = [{ Resize._args = [ "Increase" ]; }]; + }; + } + { + bind = { + _args = [ "H" ]; + _children = [{ Resize._args = [ "Decrease left" ]; }]; + }; + } + { + bind = { + _args = [ "J" ]; + _children = [{ Resize._args = [ "Decrease down" ]; }]; + }; + } + { + bind = { + _args = [ "K" ]; + _children = [{ Resize._args = [ "Decrease up" ]; }]; + }; + } + { + bind = { + _args = [ "L" ]; + _children = [{ Resize._args = [ "Decrease right" ]; }]; + }; + } + { + bind = { + _args = [ "h" ]; + _children = [{ Resize._args = [ "Increase left" ]; }]; + }; + } + { + bind = { + _args = [ "j" ]; + _children = [{ Resize._args = [ "Increase down" ]; }]; + }; + } + { + bind = { + _args = [ "k" ]; + _children = [{ Resize._args = [ "Increase up" ]; }]; + }; + } + { + bind = { + _args = [ "l" ]; + _children = [{ Resize._args = [ "Increase right" ]; }]; + }; + } + ]; + }; + + move = { + _children = [ + { + bind = { + _args = [ "Ctrl h" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + { + bind = { + _args = [ "left" ]; + _children = [{ MovePane._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "down" ]; + _children = [{ MovePane._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "up" ]; + _children = [{ MovePane._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "right" ]; + _children = [{ MovePane._args = [ "right" ]; }]; + }; + } + { + bind = { + _args = [ "h" ]; + _children = [{ MovePane._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "j" ]; + _children = [{ MovePane._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "k" ]; + _children = [{ MovePane._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "l" ]; + _children = [{ MovePane._args = [ "right" ]; }]; + }; + } + ]; + }; + + scroll = { + _children = [ + { + bind = { + _args = [ "e" ]; + _children = [ + { EditScrollback = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "s" ]; + _children = [ + { SwitchToMode._args = [ "entersearch" ]; } + { SearchInput._args = [ 0 ]; } + ]; + }; + } + ]; + }; + + search = { + _children = [ + { + bind = { + _args = [ "c" ]; + _children = [{ SearchToggleOption._args = [ "CaseSensitivity" ]; }]; + }; + } + { + bind = { + _args = [ "n" ]; + _children = [{ Search._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "o" ]; + _children = [{ SearchToggleOption._args = [ "WholeWord" ]; }]; + }; + } + { + bind = { + _args = [ "p" ]; + _children = [{ Search._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "w" ]; + _children = [{ SearchToggleOption._args = [ "Wrap" ]; }]; + }; + } + ]; + }; + + session = { + _children = [ + { + bind = { + _args = [ "Ctrl o" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + { + bind = { + _args = [ "c" ]; + _children = [ + { + LaunchOrFocusPlugin._args = [ "configuration" ]; + LaunchOrFocusPlugin._children = [ + { floating._args = [ true ]; } + { move_to_focused_tab._args = [ true ]; } + ]; + } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "p" ]; + _children = [ + { + LaunchOrFocusPlugin._args = [ "plugin-manager" ]; + LaunchOrFocusPlugin._children = [ + { floating._args = [ true ]; } + { move_to_focused_tab._args = [ true ]; } + ]; + } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "w" ]; + _children = [ + { + LaunchOrFocusPlugin._args = [ "session-manager" ]; + LaunchOrFocusPlugin._children = [ + { floating._args = [ true ]; } + { move_to_focused_tab._args = [ true ]; } + ]; + } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + ]; + }; + + "shared_except \"locked\"" = { + _children = [ + { + bind = { + _args = [ "Alt left" ]; + _children = [{ MoveFocusOrTab._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "Alt down" ]; + _children = [{ MoveFocus._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "Alt up" ]; + _children = [{ MoveFocus._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "Alt right" ]; + _children = [{ MoveFocusOrTab._args = [ "right" ]; }]; + }; + } + { + bind = { + _args = [ "Alt +" ]; + _children = [{ Resize._args = [ "Increase" ]; }]; + }; + } + { + bind = { + _args = [ "Alt -" ]; + _children = [{ Resize._args = [ "Decrease" ]; }]; + }; + } + { + bind = { + _args = [ "Alt =" ]; + _children = [{ Resize._args = [ "Increase" ]; }]; + }; + } + { + bind = { + _args = [ "Alt r" ]; + _children = [ + { + WriteChars._args = [ "source cdr" ]; + } + { + WriteChars._args = [ "\n" ]; + } + ]; + }; + } + { + bind = { + _args = [ "Alt f" ]; + _children = [{ ToggleFloatingPanes = { }; }]; + }; + } + { + bind = { + _args = [ "Ctrl g" ]; + _children = [{ SwitchToMode._args = [ "locked" ]; }]; + }; + } + { + bind = { + _args = [ "Alt h" ]; + _children = [{ MoveFocusOrTab._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "Alt i" ]; + _children = [{ MoveTab._args = [ "left" ]; }]; + }; + } + { + bind = { + _args = [ "Alt j" ]; + _children = [{ MoveFocus._args = [ "down" ]; }]; + }; + } + { + bind = { + _args = [ "Alt k" ]; + _children = [{ MoveFocus._args = [ "up" ]; }]; + }; + } + { + bind = { + _args = [ "Alt p" ]; + _children = [{ NewPane = { }; }]; + }; + } + { + bind = { + _args = [ "Alt n" ]; + _children = [{ NewTab = { }; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"move\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl h" ]; + _children = [{ SwitchToMode._args = [ "move" ]; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"session\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl o" ]; + _children = [{ SwitchToMode._args = [ "session" ]; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"scroll\" \"search\" \"tmux\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl b" ]; + _children = [{ SwitchToMode._args = [ "tmux" ]; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"scroll\" \"search\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl s" ]; + _children = [{ SwitchToMode._args = [ "scroll" ]; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"tab\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl t" ]; + _children = [{ SwitchToMode._args = [ "tab" ]; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"pane\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl p" ]; + _children = [{ SwitchToMode._args = [ "pane" ]; }]; + }; + } + ]; + }; + + "shared_except \"locked\" \"resize\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl n" ]; + _children = [{ SwitchToMode._args = [ "resize" ]; }]; + }; + } + ]; + }; + + "shared_except \"normal\" \"locked\" \"entersearch\"" = { + _children = [ + { + bind = { + _args = [ "enter" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + ]; + }; + + "shared_except \"normal\" \"locked\" \"entersearch\" \"renametab\" \"renamepane\"" = { + _children = [ + { + bind = { + _args = [ "esc" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + ]; + }; + + "shared_among \"pane\" \"tmux\"" = { + _children = [ + { + bind = { + _args = [ "x" ]; + _children = [ + { CloseFocus = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + ]; + }; + + "shared_among \"scroll\" \"search\"" = { + _children = [ + { + bind = { + _args = [ "PageDown" ]; + _children = [{ PageScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "PageUp" ]; + _children = [{ PageScrollUp = { }; }]; + }; + } + { + bind = { + _args = [ "left" ]; + _children = [{ PageScrollUp = { }; }]; + }; + } + { + bind = { + _args = [ "down" ]; + _children = [{ ScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "up" ]; + _children = [{ ScrollUp = { }; }]; + }; + } + { + bind = { + _args = [ "right" ]; + _children = [{ PageScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "Ctrl b" ]; + _children = [{ PageScrollUp = { }; }]; + }; + } + { + bind = { + _args = [ "Ctrl c" ]; + _children = [ + { ScrollToBottom = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "d" ]; + _children = [{ HalfPageScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "Ctrl f" ]; + _children = [{ PageScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "h" ]; + _children = [{ PageScrollUp = { }; }]; + }; + } + { + bind = { + _args = [ "j" ]; + _children = [{ ScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "k" ]; + _children = [{ ScrollUp = { }; }]; + }; + } + { + bind = { + _args = [ "l" ]; + _children = [{ PageScrollDown = { }; }]; + }; + } + { + bind = { + _args = [ "Ctrl s" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + { + bind = { + _args = [ "u" ]; + _children = [{ HalfPageScrollUp = { }; }]; + }; + } + ]; + }; + + entersearch = { + _children = [ + { + bind = { + _args = [ "Ctrl c" ]; + _children = [{ SwitchToMode._args = [ "scroll" ]; }]; + }; + } + { + bind = { + _args = [ "esc" ]; + _children = [{ SwitchToMode._args = [ "scroll" ]; }]; + }; + } + { + bind = { + _args = [ "enter" ]; + _children = [{ SwitchToMode._args = [ "search" ]; }]; + }; + } + ]; + }; + + renametab = { + _children = [ + { + bind = { + _args = [ "esc" ]; + _children = [ + { UndoRenameTab = { }; } + { SwitchToMode._args = [ "tab" ]; } + ]; + }; + } + ]; + }; + + "shared_among \"renametab\" \"renamepane\"" = { + _children = [ + { + bind = { + _args = [ "Ctrl c" ]; + _children = [{ SwitchToMode._args = [ "normal" ]; }]; + }; + } + ]; + }; + + renamepane = { + _children = [ + { + bind = { + _args = [ "esc" ]; + _children = [ + { UndoRenamePane = { }; } + { SwitchToMode._args = [ "pane" ]; } + ]; + }; + } + ]; + }; + + "shared_among \"session\" \"tmux\"" = { + _children = [ + { + bind = { + _args = [ "d" ]; + _children = [{ Detach = { }; }]; + }; + } + ]; + }; + + tmux = { + _children = [ + { + bind = { + _args = [ "left" ]; + _children = [ + { MoveFocus._args = [ "left" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "down" ]; + _children = [ + { MoveFocus._args = [ "down" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "up" ]; + _children = [ + { MoveFocus._args = [ "up" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "right" ]; + _children = [ + { MoveFocus._args = [ "right" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "space" ]; + _children = [{ NextSwapLayout = { }; }]; + }; + } + { + bind = { + _args = [ "\"" ]; + _children = [ + { NewPane._args = [ "down" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "%" ]; + _children = [ + { NewPane._args = [ "right" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "," ]; + _children = [{ SwitchToMode._args = [ "renametab" ]; }]; + }; + } + { + bind = { + _args = [ "[" ]; + _children = [{ SwitchToMode._args = [ "scroll" ]; }]; + }; + } + { + bind = { + _args = [ "Ctrl b" ]; + _children = [ + { Write._args = [ 2 ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "c" ]; + _children = [ + { NewTab = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "h" ]; + _children = [ + { MoveFocus._args = [ "left" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "j" ]; + _children = [ + { MoveFocus._args = [ "down" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "k" ]; + _children = [ + { MoveFocus._args = [ "up" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "l" ]; + _children = [ + { MoveFocus._args = [ "right" ]; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "n" ]; + _children = [ + { GoToNextTab = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "o" ]; + _children = [{ FocusNextPane = { }; }]; + }; + } + { + bind = { + _args = [ "p" ]; + _children = [ + { GoToPreviousTab = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + { + bind = { + _args = [ "z" ]; + _children = [ + { ToggleFocusFullscreen = { }; } + { SwitchToMode._args = [ "normal" ]; } + ]; + }; + } + ]; + }; + }; + }; + }; + +} diff --git a/modules-clone/home/common/zellij.nix b/modules-clone/home/common/zellij.nix new file mode 100644 index 0000000..9f00748 --- /dev/null +++ b/modules-clone/home/common/zellij.nix @@ -0,0 +1,59 @@ +{ self, lib, config, pkgs, ... }: +{ + options.swarselmodules.zellij = lib.mkEnableOption "zellij settings"; + config = lib.mkIf config.swarselmodules.zellij { + programs.zellij = { + enable = true; + enableZshIntegration = true; + attachExistingSession = false; + exitShellOnExit = true; + settings = { + pane_frames = false; + simplified_ui = false; + default_shell = "zsh"; + copy_on_select = true; + on_force_close = "quit"; + show_startup_tips = false; + support_kitty_keyboard_protocol = true; + default_layout = "swarsel"; + layout_dir = "${config.home.homeDirectory}/.config/zellij/layouts"; + theme_dir = "${config.home.homeDirectory}/.config/zellij/themes"; + scrollback_lines_to_serialize = config.programs.kitty.settings.scrollback_lines; + session_serialization = true; + + copy_command = + if pkgs.stdenv.hostPlatform.isLinux then + "wl-copy" + else if pkgs.stdenv.hostPlatform.isDarwin then + "pbcopy" + else + ""; + ui.pane_frames = { + rounded_corners = true; + hide_session_name = true; + }; + plugins = { + tab-bar.path = "tab-bar"; + status-bar.path = "status-bar"; + strider.path = "strider"; + compact-bar.path = "compact-bar"; + # configuration.path = "configuration"; + # filepicker.path = "strider"; + # plugin-manager.path = "plugin-manager"; + # session-manager.path = "session-manager"; + # welcome-screen.path = "session-manager"; + }; + }; + }; + + home.packages = with pkgs; [ + zjstatus + ]; + + xdg.configFile = { + # "zellij/config.kdl".text = import "${self}/files/zellij/config.kdl.nix" { inherit config; }; + "zellij/layouts/swarsel.kdl".text = import "${self}/files/zellij/layouts/swarsel.kdl.nix" { inherit config pkgs; }; + }; + }; + +} diff --git a/modules-clone/home/common/zsh.nix b/modules-clone/home/common/zsh.nix new file mode 100644 index 0000000..d06566a --- /dev/null +++ b/modules-clone/home/common/zsh.nix @@ -0,0 +1,149 @@ +{ self, config, pkgs, lib, minimal, globals, confLib, type, arch, ... }: +let + inherit (config.swarselsystems) flakePath isNixos homeDir; + crocDomain = globals.services.croc.domain; +in +{ + options.swarselmodules.zsh = lib.mkEnableOption "zsh settings"; + options.swarselsystems = { + shellAliases = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + }; + }; + config = lib.mkIf config.swarselmodules.zsh + ({ + + programs.zsh = { + enable = true; + } + // lib.optionalAttrs (!minimal) { + shellAliases = lib.recursiveUpdate + { + nb = "nix build"; + nbl = "nix build --builders \"\""; + nbo = "nix build --offline --builders \"\""; + nd = "nix develop"; + ns = "nix shell"; + hmswitch = lib.mkIf (!isNixos) "${lib.getExe pkgs.home-manager} --flake ${flakePath}#$(hostname) switch |& nom"; + nswitch = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) switch; cd -;"; + ntest = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) test; cd -;"; + nboot = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) boot; cd -;"; + ndry = lib.mkIf isNixos "cd ${flakePath}; swarsel-deploy $(hostname) dry-activate; cd -;"; + magit = "emacsclient -nc -e \"(magit-status)\""; + config = "git --git-dir=$HOME/.cfg/ --work-tree=$HOME"; + g = "git"; + c = "git --git-dir=$FLAKE/.git --work-tree=$FLAKE/"; + passpush = "cd ~/.local/share/password-store; git add .; git commit -m 'pass file changes'; git push; cd -;"; + passpull = "cd ~/.local/share/password-store; git pull; cd -;"; + hotspot = "nmcli connection up local; nmcli device wifi hotspot;"; + youtube-dl = "yt-dlp"; + cat-orig = "cat"; + # cdr = "cd \"$( (find $DOCUMENT_DIR_WORK $DOCUMENT_DIR_PRIV -maxdepth 1 && echo $FLAKE) | fzf )\""; + cdr = "source cdr"; + nix-ldd-ldd = "LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH ldd"; + nix-ldd = "LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH ldd"; + nix-ldd-locate = "nix-locate --minimal --top-level -w "; + nix-store-search = "ls /nix/store | grep"; + fs-diff = "sudo mount -o subvol=/ /dev/mapper/cryptroot /mnt ; fs-diff"; + lt = "eza -las modified --total-size"; + boot-diff = "nix store diff-closures /run/*-system"; + gen-diff = "nix profile diff-closures --profile /nix/var/nix/profiles/system"; + cc = "wl-copy"; + build-topology = "nix build --override-input topologyPrivate ${self}/files/topology/private ${flakePath}#topology.${arch}.config.output"; + build-topology-dev = "nix build --show-trace --override-input nix-topology ${homeDir}/Documents/Private/nix-topology --override-input topologyPrivate ${self}/files/topology/private ${flakePath}#topology.${arch}.config.output"; + build-iso = "nix build --print-out-paths .#live-iso"; + nix-review-local = "nix run nixpkgs#nixpkgs-review -- rev HEAD"; + nix-review-post = "nix run nixpkgs#nixpkgs-review -- pr --post-result --systems linux"; + } + config.swarselsystems.shellAliases; + autosuggestion.enable = true; + enableCompletion = true; + syntaxHighlighting.enable = true; + autocd = false; + cdpath = [ + "~/.dotfiles" + # "~/Documents/GitHub" + ]; + defaultKeymap = "emacs"; + dirHashes = { + dl = "$HOME/Downloads"; + gh = "$HOME/Documents/GitHub"; + }; + history = { + expireDuplicatesFirst = true; + append = true; + ignoreSpace = true; + ignoreDups = true; + path = "${config.home.homeDirectory}/.histfile"; + save = 100000; + size = 100000; + }; + historySubstringSearch = { + enable = true; + searchDownKey = "^[OB"; + searchUpKey = "^[OA"; + }; + plugins = [ + # { + # name = "fzf-tab"; + # src = pkgs.zsh-fzf-tab; + # } + ]; + initContent = '' + my-forward-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle forward-word + } + zle -N my-forward-word + # ctrl + right + bindkey "^[[1;5C" my-forward-word + + # shift + right + bindkey "^[[1;2C" forward-word + + my-backward-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle backward-word + } + zle -N my-backward-word + # ctrl + left + bindkey "^[[1;5D" my-backward-word + + # shift + left + bindkey "^[[1;2D" backward-word + + my-backward-delete-word() { + local WORDCHARS=$WORDCHARS + WORDCHARS="''${WORDCHARS//:}" + WORDCHARS="''${WORDCHARS//\/}" + WORDCHARS="''${WORDCHARS//.}" + zle backward-delete-word + } + zle -N my-backward-delete-word + # ctrl + del + bindkey '^H' my-backward-delete-word + ''; + sessionVariables = lib.mkIf (!config.swarselsystems.isPublic) { + CROC_RELAY = crocDomain; + CROC_PASS = "$(cat ${confLib.getConfig.sops.secrets.croc-password.path or ""})"; + GITHUB_TOKEN = "$(cat ${confLib.getConfig.sops.secrets.github-nixpkgs-review-token.path or ""})"; + QT_QPA_PLATFORM_PLUGIN_PATH = "${pkgs.libsForQt5.qt5.qtbase.bin}/lib/qt-${pkgs.libsForQt5.qt5.qtbase.version}/plugins"; + # QTWEBENGINE_CHROMIUM_FLAGS = "--no-sandbox"; + }; + }; + } // lib.optionalAttrs (type != "nixos") { + + sops.secrets = lib.mkIf (!config.swarselsystems.isPublic) { + croc-password = { }; + github-nixpkgs-review-token = { }; + }; + + }); +} diff --git a/modules-clone/home/darwin/default.nix b/modules-clone/home/darwin/default.nix new file mode 100644 index 0000000..e7da9d0 --- /dev/null +++ b/modules-clone/home/darwin/default.nix @@ -0,0 +1,9 @@ +{ self, ... }: +{ + home.stateVersion = "23.05"; + imports = [ + "${self}/modules-clone/home/common/settings.nix" + "${self}/modules-clone/shared/options.nix" + "${self}/modules-clone/shared/vars.nix" + ]; +} diff --git a/modules-clone/home/default.nix b/modules-clone/home/default.nix new file mode 100644 index 0000000..5a2b19a --- /dev/null +++ b/modules-clone/home/default.nix @@ -0,0 +1,8 @@ +# @ future me: dont panic, this file is not read in by readNix +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/home"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/home"; +} diff --git a/modules-clone/home/optional/default.nix b/modules-clone/home/optional/default.nix new file mode 100644 index 0000000..8915c71 --- /dev/null +++ b/modules-clone/home/optional/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/home/optional"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/home/optional"; +} diff --git a/modules-clone/home/optional/framework.nix b/modules-clone/home/optional/framework.nix new file mode 100644 index 0000000..3d4baab --- /dev/null +++ b/modules-clone/home/optional/framework.nix @@ -0,0 +1,13 @@ +_: +{ + config = { + swarselsystems = { + inputs = { + "12972:18:Framework_Laptop_16_Keyboard_Module_-_ANSI_Keyboard" = { + xkb_layout = "us"; + xkb_variant = "altgr-intl"; + }; + }; + }; + }; +} diff --git a/modules-clone/home/optional/gaming.nix b/modules-clone/home/optional/gaming.nix new file mode 100644 index 0000000..04b2817 --- /dev/null +++ b/modules-clone/home/optional/gaming.nix @@ -0,0 +1,57 @@ +{ config, pkgs, confLib, ... }: +let + inherit (config.swarselsystems) isNixos; +in +{ + config = { + # specialisation = { + # gaming.configuration = { + home.packages = with pkgs; [ + # lutris + wine + protonplus + winetricks + libudev-zero + dwarfs + fuse-overlayfs + # steam + steam-run + patchelf + gamescope + vulkan-tools + moonlight-qt + ns-usbloader + + quark-goldleaf + + # gog games installing + heroic + + # minecraft + prismlauncher # has overrides + temurin-bin-17 + + pokefinder + retroarch + flips + ]; + + programs.lutris = { + enable = true; + extraPackages = with pkgs; [ + winetricks + gamescope + umu-launcher + ]; + steamPackage = if isNixos then confLib.getConfig.programs.steam.package else pkgs.steam; + winePackages = with pkgs; [ + wineWow64Packages.waylandFull + ]; + protonPackages = with pkgs; [ + proton-ge-bin + ]; + }; + # }; + # }; + }; +} diff --git a/modules-clone/home/optional/niri.nix b/modules-clone/home/optional/niri.nix new file mode 100644 index 0000000..6d36f35 --- /dev/null +++ b/modules-clone/home/optional/niri.nix @@ -0,0 +1,259 @@ +{ inputs, config, pkgs, lib, vars, type, ... }: +{ + imports = lib.optionals (type != "nixos") [ + inputs.niri-flake.homeModules.niri + ]; + config = { + programs.niri = { + package = pkgs.niri-stable; # which package to use for niri validation + settings = { + gestures.hot-corners.enable = false; + hotkey-overlay.skip-at-startup = true; + debug = { + honor-xdg-activation-with-invalid-serial = [ ]; + }; + xwayland-satellite = { + enable = true; + path = "${lib.getExe pkgs.xwayland-satellite-unstable}"; + }; + prefer-no-csd = true; + layer-rules = [ + { matches = [{ namespace = "^notificatioans$"; }]; block-out-from = "screen-capture"; } + { matches = [{ namespace = "^wallpaper$"; }]; place-within-backdrop = true; } + { matches = [{ namespace = "^noctalia-overview*"; }]; place-within-backdrop = true; } + ]; + window-rules = [ + { + matches = [{ app-id = ".*"; }]; + opacity = 0.95; + default-column-width = { proportion = 0.5; }; + shadow = { + enable = true; + draw-behind-window = true; + }; + clip-to-geometry = true; + geometry-corner-radius = { top-left = 5.0; top-right = 5.0; bottom-left = 5.0; bottom-right = 5.0; }; + } + { matches = [{ app-id = "at.yrlf.wl_mirror"; }]; opacity = 1.0; } + { matches = [{ app-id = "Gimp"; }]; opacity = 1.0; } + { matches = [{ app-id = "^firefox$"; }]; opacity = 0.95; } + { matches = [{ app-id = "^special.*"; }]; default-column-width = { proportion = 0.9; }; open-on-workspace = "Scratchpad"; } + { matches = [{ app-id = "chromium-browser"; }]; opacity = 0.99; } + { matches = [{ app-id = "^qalculate-gtk$"; }]; open-floating = true; } + { matches = [{ app-id = "^blueman$"; }]; open-floating = true; } + { matches = [{ app-id = "^pavucontrol$"; }]; open-floating = true; } + { matches = [{ app-id = "^syncthingtray$"; }]; open-floating = true; } + { matches = [{ app-id = "^Element$"; }]; open-floating = true; default-column-width = { proportion = 0.5; }; block-out-from = "screen-capture"; } + # { matches = [{ app-id = "^Element$"; }]; default-column-width = { proportion = 0.9; }; open-on-workspace = "Scratchpad"; block-out-from = "screencast"; } + { matches = [{ app-id = "^vesktop$"; }]; open-floating = true; default-column-width = { proportion = 0.5; }; block-out-from = "screen-capture"; } + # { matches = [{ app-id = "^vesktop$"; }]; default-column-width = { proportion = 0.9; }; open-on-workspace = "Scratchpad"; block-out-from = "screencast"; } + { matches = [{ app-id = "^com.nextcloud.desktopclient.nextcloud$"; }]; open-floating = true; } + { matches = [{ title = ".*1Password.*"; }]; excludes = [{ app-id = "^firefox$"; } { app-id = "^emacs$"; } { app-id = "^kitty$"; }]; open-floating = true; block-out-from = "screen-capture"; } + { matches = [{ title = "(?:Open|Save) (?:File|Folder|As)"; }]; open-floating = true; } + { matches = [{ title = "^Add$"; }]; open-floating = true; } + { matches = [{ title = "^Picture-in-Picture$"; }]; open-floating = true; } + { matches = [{ title = "Syncthing Tray"; }]; open-floating = true; } + { matches = [{ title = "^Emacs Popup Frame$"; }]; open-floating = true; } + { matches = [{ title = "^Emacs Popup Anchor$"; }]; open-floating = true; } + { matches = [{ app-id = "^spotifytui$"; }]; open-floating = true; default-column-width = { proportion = 0.5; }; } + { matches = [{ app-id = "^kittyterm$"; }]; open-floating = true; default-column-width = { proportion = 0.5; }; } + ]; + environment = vars.waylandSessionVariables // { + DISPLAY = ":0"; + QT_QPA_PLATFORM = lib.mkForce "wayland"; + EDITOR = "emacsclient -c"; + }; + screenshot-path = "~/Pictures/Screenshots/screenshot_%Y-%m-%d-%H%M%S.png"; + input = { + mod-key = "Super"; + keyboard = { + xkb = { + layout = "us"; + variant = "altgr-intl"; + }; + }; + mouse = { + natural-scroll = false; + }; + touchpad = { + enable = true; + tap = true; + tap-button-map = "left-right-middle"; + natural-scroll = true; + scroll-method = "two-finger"; + click-method = "clickfinger"; + disabled-on-external-mouse = true; + drag = true; + drag-lock = false; + dwt = true; + dwtp = true; + }; + }; + cursor = { + hide-after-inactive-ms = 2000; + hide-when-typing = true; + }; + layout = { + background-color = "transparent"; + border = { + enable = true; + width = 1; + }; + focus-ring = { + enable = false; + }; + gaps = 5; + }; + binds = with config.lib.niri.actions; let + sh = spawn "sh" "-c"; + in + { + "Mod+Shift+t".action = toggle-window-rule-opacity; + "Mod+m".action = focus-workspace-previous; + "Mod+Shift+Space".action = toggle-window-floating; + "Mod+Shift+f".action = fullscreen-window; + # "Mod+q".action = sh "${resizer} && niri msg action close-window"; + "Mod+q".action = sh "niri msg action close-window"; + # "Mod+f".action = sh "${resizer} && exec firefox"; + "Mod+f".action = sh "exec firefox"; + # "Mod+Space".action = spawn "noctalia-shell" "ipc" "call" "launcher" "toggle"; + # "Mod+Space".action = sh "${resizer} && exec noctalia-shell ipc call launcher toggle"; + "Mod+Space".action = sh "exec noctalia-shell ipc call launcher toggle"; + # "Mod+Space".action = sh "${resizer} & exec fuzzel"; + "Mod+z".action = spawn "noctalia-shell" "ipc" "call" "bar" "toggle"; + "Mod+Shift+c".action = spawn "qalculate-gtk"; + "Mod+Ctrl+p".action = spawn "1password" "--quick-acces"; + "Mod+Shift+Escape".action = spawn "kitty" "-o" "confirm_os_window_close=0" "btm"; + "Mod+h".action = sh ''hyprpicker | wl-copy''; + # "Mod+s".action = spawn "grim" "-g" "\"$(slurp)\"" "-t" "png" "-" "|" "wl-copy" "-t" "image/png"; + # "Mod+s".action = screenshot { show-pointer = false; }; + "Mod+s".action.screenshot = { show-pointer = false; }; + # "Mod+Shift+s".action = spawn "slurp" "|" "grim" "-g" "-" "Pictures/Screenshots/$(date +'screenshot_%Y-%m-%d-%H%M%S.png')"; + # "Mod+Shift+s".action = screenshot-window { write-to-disk = true; }; + "Mod+Shift+s".action.screenshot-window = { write-to-disk = true; }; + # "Mod+Shift+v".action = spawn "wf-recorder" "-g" "'$(slurp -f %o -or)'" "-f" "~/Videos/screenrecord_$(date +%Y-%m-%d-%H%M%S).mkv"; + + # "Mod+e".action = sh "${resizer} && exec emacsclient -nquc -a emacs -e '(dashboard-open)'"; + "Mod+e".action = sh "exec emacsclient -nquc -a emacs -e '(dashboard-open)'"; + # "Mod+c".action = sh "${resizer} && exec emacsclient -ce '(org-capture)'"; + "Mod+c".action = sh "exec emacsclient -ce '(org-capture)'"; + # "Mod+t".action = sh "${resizer} && exec emacsclient -ce '(org-agenda)'"; + "Mod+t".action = sh "exec emacsclient -ce '(org-agenda)'"; + # "Mod+Shift+m".action = sh "${resizer} && exec emacsclient -ce '(mu4e)'"; + "Mod+Shift+m".action = sh "exec emacsclient -ce '(mu4e)'"; + # "Mod+Shift+a".action = sh "${resizer} && exec emacsclient -ce '(swarsel/open-calendar)'"; + "Mod+Shift+a".action = sh "exec emacsclient -ce '(swarsel/open-calendar)'"; + + "Mod+a".action = spawn "swarselcheck-niri" "-s"; + "Mod+x".action = spawn "swarselcheck-niri" "-k"; + "Mod+d".action = spawn "swarselcheck-niri" "-d"; + "Mod+w".action = spawn "swarselcheck-niri" "-e"; + + "Mod+p".action = spawn "pass-fuzzel"; + "Mod+o".action = spawn "pass-fuzzel" "--otp"; + "Mod+Shift+p".action = spawn "pass-fuzzel" "--type"; + "Mod+Shift+o".action = spawn "pass-fuzzel" "--otp" "--type"; + + "Mod+Left".action = focus-column-or-monitor-left; + "Mod+Right".action = focus-column-or-monitor-right; + "Mod+Down".action = focus-window-or-workspace-down; + "Mod+Up".action = focus-window-or-workspace-up; + "Mod+Shift+Left".action = move-column-left-or-to-monitor-left; + "Mod+Shift+Right".action = move-column-right-or-to-monitor-right; + "Mod+Shift+Down".action = move-window-down-or-to-workspace-down; + "Mod+Shift+Up".action = move-window-up-or-to-workspace-up; + # "Mod+Ctrl+Shift+c".action = "reload"; + # "Mod+Ctrl+Shift+r".action = "exec swarsel-displaypower"; + # "Mod+Shift+e".action = "exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'"; + # "Mod+r".action = "mode resize"; + # "Mod+Return".action = "exec kitty"; + # "Mod+Return".action = sh "${resizer} && exec kitty -o confirm_os_window_close=0"; + "Mod+Return".action = sh "exec kitty -o confirm_os_window_close=0"; + "XF86AudioRaiseVolume".action = spawn "noctalia-shell" "ipc" "call" "volume" "increase"; + "XF86AudioLowerVolume".action = spawn "noctalia-shell" "ipc" "call" "volume" "decrease"; + "XF86AudioMute".action = spawn "noctalia-shell" "ipc" "call" "volume" "muteOutput"; + "XF86AudioPrev".action = spawn "noctalia-shell" "ipc" "call" "media" "previous"; + "XF86AudioPlay".action = spawn "noctalia-shell" "ipc" "call" "media" "playPause"; + "XF86AudioNext".action = spawn "noctalia-shell" "ipc" "call" "media" "next"; + "XF86MonBrightnessUp".action = spawn "noctalia-shell" "ipc" "call" "brightness" "increase"; + "XF86MonBrightnessDown".action = spawn "noctalia-shell" "ipc" "call" "brightness" "decrease"; + "XF86Display".action = spawn "wl-mirror" "eDP-1"; + "Mod+Escape".action = spawn "noctalia-shell" "ipc" "call" "sessionMenu" "toggle"; + "Mod+i".action = spawn "noctalia-shell" "ipc" "call" "launcher" "emoji"; + "Mod+Equal".action = set-column-width "+10%"; + "Mod+Minus".action = set-column-width "-10%"; + + "Mod+1".action = focus-workspace 1; + "Mod+2".action = focus-workspace 2; + "Mod+3".action = focus-workspace 3; + "Mod+4".action = focus-workspace 4; + "Mod+5".action = focus-workspace 5; + "Mod+6".action = focus-workspace 6; + "Mod+7".action = focus-workspace 7; + "Mod+8".action = focus-workspace 8; + "Mod+9".action = focus-workspace 9; + "Mod+0".action = focus-workspace 0; + + "Mod+Shift+1".action = move-column-to-index 1; + "Mod+Shift+2".action = move-column-to-index 2; + "Mod+Shift+3".action = move-column-to-index 3; + "Mod+Shift+4".action = move-column-to-index 4; + "Mod+Shift+5".action = move-column-to-index 5; + "Mod+Shift+6".action = move-column-to-index 6; + "Mod+Shift+7".action = move-column-to-index 7; + "Mod+Shift+8".action = move-column-to-index 8; + "Mod+Shift+9".action = move-column-to-index 9; + "Mod+Shift+0".action = move-column-to-index 0; + }; + spawn-at-startup = [ + # { command = [ "vesktop" "--start-minimized" "--enable-speech-dispatcher" "--ozone-platform-hint=auto" "--enable-features=WaylandWindowDecorations" "--enable-wayland-ime" ]; } + # { command = [ "element-desktop" "--hidden" "--enable-features=UseOzonePlatform" "--ozone-platform=wayland" "--disable-gpu-driver-bug-workarounds" ]; } + # { command = [ "anki" ]; } + # { command = [ "obsidian" ]; } + # { command = [ "nm-applet" ]; } + # { command = [ "niri" "msg" "action" "focus-workspace" "2" ]; } + # { command = [ "noctalia-shell" ]; } + # { argv = [ "pkill" "mako" ]; } + { argv = [ "systemctl" "--user" "restart" "noctalia-shell.target" ]; } + ]; + # workspaces = { + # "01-Main" = { + # name = "Scratchpad"; + # }; + # "99-Scratchpad" = { + # name = ""; + # }; + # }; + }; + }; + + xdg.portal = { + enable = true; + xdgOpenUsePortal = true; + config.niri = { + default = [ + "gtk" + "gnome" + ]; + "org.freedesktop.impl.portal.Access" = [ "gtk" ]; + "org.freedesktop.impl.portal.Notification" = [ "gtk" ]; + "org.freedesktop.impl.portal.Secret" = [ "gnome-keyring" ]; + "org.freedesktop.impl.portal.FileChooser" = [ "gtk" ]; + "org.freedesktop.impl.portal.ScreenCast" = [ "xdg-desktop-portal-gnome" ]; + "org.freedesktop.impl.portal.Screenshot" = [ "xdg-desktop-portal-gnome" ]; + }; + extraPortals = [ + pkgs.gnome-keyring + pkgs.xdg-desktop-portal-gtk + pkgs.xdg-desktop-portal-gnome + ]; + }; + + swarselmodules.gnome-keyring = lib.swarselsystems.mkStrong true; + + home.packages = [ + pkgs.nirius + ]; + + }; +} diff --git a/modules-clone/home/optional/noctalia.nix b/modules-clone/home/optional/noctalia.nix new file mode 100644 index 0000000..4641ef8 --- /dev/null +++ b/modules-clone/home/optional/noctalia.nix @@ -0,0 +1,677 @@ +{ self, inputs, config, pkgs, lib, confLib, type, ... }: +let + inherit (confLib.getConfig.repo.secrets.common) caldavTasksEndpoint; + inherit (config.swarselsystems) xdgDir; +in +{ + imports = [ + inputs.noctalia.homeModules.default + ]; + options.swarselmodules.optional-noctalia = lib.swarselsystems.mkTrueOption; + config = { + systemd.user = { + targets = { + noctalia-shell.Unit = { + After = [ "graphical-session.target" ]; + }; + tray = { + Unit = { + Wants = [ "noctalia-init.service" ]; + After = [ + "noctalia-shell.service" + "noctalia-init.service" + ]; + }; + Install.WantedBy = [ "noctalia-shell.target" ]; + }; + }; + services = { + noctalia-shell = { + Unit.PartOf = [ "noctalia-shell.target" ]; + Install.WantedBy = [ "noctalia-shell.target" ]; + }; + noctalia-init = { + + Unit = { + Requires = [ "noctalia-shell.service" ]; + After = [ "noctalia-shell.service" ]; + }; + + Service = { + Type = "oneshot"; + ExecStart = "${pkgs.coreutils}/bin/sleep 3"; + RemainAfterExit = true; + }; + + Install = { + WantedBy = [ "tray.target" ]; + }; + }; + }; + }; + + programs = { + fastfetch.enable = true; + noctalia-shell = { + enable = true; + package = pkgs.noctalia-shell; + systemd.enable = true; + settings = { + bar = { + barType = "simple"; + position = "top"; + monitors = [ ]; + density = "default"; + showCapsule = false; + showOutline = false; + capsuleOpacity = lib.mkForce 1; + backgroundOpacity = lib.mkForce 0.5; + useSeparateOpacity = true; + floating = false; + marginVertical = 4; + marginHorizontal = 0; + frameThickness = 8; + frameRadius = 12; + outerCorners = false; + hideOnOverview = false; + displayMode = "non_exclusive"; + autoHideDelay = 100; + autoShowDelay = 300; + screenOverrides = [ ]; + widgets = { + left = [ + { + characterCount = 4; + colorizeIcons = false; + emptyColor = "primary"; + enableScrollWheel = false; + focusedColor = "secondary"; + followFocusedScreen = false; + groupedBorderOpacity = 1; + hideUnoccupied = false; + iconScale = 0.5; + id = "Workspace"; + labelMode = "name"; + occupiedColor = "primary"; + pillSize = 0.4; + reverseScroll = false; + showApplications = true; + showBadge = true; + showLabelsOnlyWhenOccupied = false; + unfocusedIconsOpacity = 0.25; + } + + { + defaultSettings = { + completedCount = 0; + count = 0; + current_page_id = 0; + isExpanded = false; + pages = [ + { + id = 0; + name = "General"; + } + ]; + priorityColors = { + high = "#f44336"; + low = "#9e9e9e"; + medium = "#2196f3"; + }; + showBackground = true; + showCompleted = true; + todos = [ ]; + useCustomColors = false; + }; + id = "plugin:ba7043:todo"; + } + ]; + center = [ + { + colorizeIcons = true; + hideMode = "hidden"; + id = "ActiveWindow"; + maxWidth = 145; + scrollingMode = "hover"; + showIcon = true; + useFixedWidth = false; + } + { + id = "plugin:privacy-indicator"; + } + { + id = "plugin:screen-recorder"; + } + ]; + right = [ + { + blacklist = [ + "bluetooth*" + ]; + colorizeIcons = false; + drawerEnabled = true; + hidePassive = true; + id = "Tray"; + pinned = [ ]; + } + { + displayMode = "alwaysShow"; + id = "Volume"; + middleClickCommand = "pavucontrol"; + } + { + id = "NotificationHistory"; + hideWhenZero = false; + showUnreadBadge = true; + } + { + id = "plugin:github-feed"; + } + { + id = "plugin:clipper"; + } + { + displayMode = "onhover"; + id = "Network"; + } + { + displayMode = "onhover"; + id = "Bluetooth"; + } + { + displayMode = "onhover"; + id = "VPN"; + } + { + deviceNativePath = "__default__"; + hideIfIdle = true; + hideIfNotDetected = true; + id = "Battery"; + showNoctaliaPerformance = false; + showPowerProfiles = true; + } + { + iconColor = "none"; + id = "SessionMenu"; + } + { + customFont = "FiraCode Nerd Font Mono"; + formatHorizontal = "ddd dd. MMM HH:mm:ss"; + formatVertical = ""; + id = "Clock"; + tooltipFormat = "ddd dd. MMM HH:mm:ss"; + useCustomFont = true; + usePrimaryColor = true; + } + { + colorizeDistroLogo = false; + colorizeSystemIcon = "none"; + customIconPath = "${self}/files/icons/swarsel.png"; + enableColorization = true; + icon = "noctalia"; + id = "ControlCenter"; + useDistroLogo = false; + } + ]; + }; + }; + general = { + avatarImage = "${self}/files/icons/swarsel.png"; + dimmerOpacity = 0.2; + showScreenCorners = false; + forceBlackScreenCorners = false; + scaleRatio = 1; + radiusRatio = 0.2; + iRadiusRatio = 1; + boxRadiusRatio = 1; + screenRadiusRatio = 1; + animationSpeed = 1.5; + animationDisabled = false; + compactLockScreen = true; + lockOnSuspend = true; + showSessionButtonsOnLockScreen = true; + showHibernateOnLockScreen = false; + enableShadows = true; + shadowDirection = "center"; + shadowOffsetX = 0; + shadowOffsetY = 0; + language = ""; + allowPanelsOnScreenWithoutBar = true; + showChangelogOnStartup = true; + telemetryEnabled = false; + enableLockScreenCountdown = true; + lockScreenCountdownDuration = 10000; + autoStartAuth = true; + allowPasswordWithFprintd = true; + }; + ui = { + fontDefaultScale = 1; + fontFixedScale = 1; + tooltipsEnabled = true; + panelBackgroundOpacity = lib.mkForce 1; + panelsAttachedToBar = true; + settingsPanelMode = "centered"; + wifiDetailsViewMode = "grid"; + bluetoothDetailsViewMode = "grid"; + networkPanelView = "wifi"; + bluetoothHideUnnamedDevices = false; + boxBorderEnabled = false; + }; + location = { + name = confLib.getConfig.repo.secrets.common.location.timezoneSpecific; + weatherEnabled = true; + weatherShowEffects = false; + useFahrenheit = false; + use12hourFormat = false; + showWeekNumberInCalendar = true; + showCalendarEvents = true; + showCalendarWeather = true; + analogClockInCalendar = false; + firstDayOfWeek = 1; + hideWeatherTimezone = false; + hideWeatherCityName = false; + }; + calendar = { + cards = [ + { + enabled = true; + id = "calendar-header-card"; + } + { + enabled = true; + id = "calendar-month-card"; + } + { + enabled = true; + id = "weather-card"; + } + ]; + }; + wallpaper = { + enabled = true; + overviewEnabled = true; + directory = "${self}/files/wallpaper/landscape"; + monitorDirectories = [ ]; + enableMultiMonitorDirectories = true; + showHiddenFiles = false; + viewMode = "single"; + setWallpaperOnAllMonitors = false; + fillMode = "crop"; + fillColor = "#000000"; + useSolidColor = false; + solidColor = "#1a1a2e"; + automationEnabled = true; + wallpaperChangeMode = "random"; + randomIntervalSec = 300; + transitionDuration = 500; + transitionType = "random"; + transitionEdgeSmoothness = 0.05; + panelPosition = "follow_bar"; + hideWallpaperFilenames = false; + useWallhaven = false; + wallhavenQuery = ""; + wallhavenSorting = "relevance"; + wallhavenOrder = "desc"; + wallhavenCategories = "111"; + wallhavenPurity = "100"; + wallhavenRatios = ""; + wallhavenApiKey = ""; + wallhavenResolutionMode = "atleast"; + wallhavenResolutionWidth = ""; + wallhavenResolutionHeight = ""; + sortOrder = "name"; + }; + appLauncher = { + enableClipboardHistory = false; + autoPasteClipboard = false; + enableClipPreview = true; + clipboardWrapText = true; + clipboardWatchTextCommand = "wl-paste --type text --watch cliphist store"; + clipboardWatchImageCommand = "wl-paste --type image --watch cliphist store"; + position = "center"; + pinnedApps = [ ]; + useApp2Unit = false; + sortByMostUsed = true; + terminalCommand = "kitty -e"; + customLaunchPrefixEnabled = false; + customLaunchPrefix = ""; + viewMode = "list"; + showCategories = false; + iconMode = "native"; + density = "compact"; + overviewLayer = false; + showIconBackground = false; + enableSettingsSearch = false; + enableWindowsSearch = false; + enableSessionSearch = false; + ignoreMouseInput = true; + screenshotAnnotationTool = ""; + }; + controlCenter = { + position = "close_to_bar_button"; + diskPath = "/"; + shortcuts = { + left = [ + { + id = "Network"; + } + { + id = "Bluetooth"; + } + ]; + right = [ + { + id = "Notifications"; + } + { + id = "PowerProfile"; + } + { + id = "KeepAwake"; + } + { + id = "plugin:screen-recorder"; + } + ]; + }; + cards = [ + { + enabled = true; + id = "profile-card"; + } + { + enabled = true; + id = "shortcuts-card"; + } + { + enabled = true; + id = "audio-card"; + } + { + enabled = true; + id = "brightness-card"; + } + { + enabled = true; + id = "weather-card"; + } + { + enabled = true; + id = "media-sysmon-card"; + } + ]; + }; + systemMonitor = { + cpuWarningThreshold = 80; + cpuCriticalThreshold = 90; + tempWarningThreshold = 80; + tempCriticalThreshold = 90; + gpuWarningThreshold = 80; + gpuCriticalThreshold = 90; + memWarningThreshold = 80; + memCriticalThreshold = 90; + swapWarningThreshold = 80; + swapCriticalThreshold = 90; + diskWarningThreshold = 80; + diskCriticalThreshold = 90; + diskAvailWarningThreshold = 20; + diskAvailCriticalThreshold = 10; + cpuPollingInterval = 1000; + gpuPollingInterval = 3000; + enableDgpuMonitoring = false; + memPollingInterval = 1000; + diskPollingInterval = 30000; + networkPollingInterval = 1000; + loadAvgPollingInterval = 3000; + useCustomColors = true; + warningColor = "#5ec4ff"; + criticalColor = "#d95468"; + externalMonitor = "btm"; + }; + dock = { + enabled = false; + }; + network = { + wifiEnabled = true; + bluetoothRssiPollingEnabled = false; + bluetoothRssiPollIntervalMs = 10000; + wifiDetailsViewMode = "grid"; + bluetoothDetailsViewMode = "grid"; + bluetoothHideUnnamedDevices = false; + }; + sessionMenu = { + enableCountdown = true; + countdownDuration = 3000; + position = "center"; + showHeader = true; + largeButtonsStyle = true; + largeButtonsLayout = "grid"; + showNumberLabels = true; + powerOptions = [ + { + action = "lock"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "L"; + } + { + action = "suspend"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "S"; + } + { + action = "hibernate"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "H"; + } + { + action = "reboot"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "R"; + } + { + action = "logout"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "U"; + } + { + action = "shutdown"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "P"; + } + { + action = "rebootToUefi"; + command = ""; + countdownEnabled = true; + enabled = true; + keybind = "B"; + } + ]; + }; + notifications = { + enabled = true; + monitors = [ ]; + location = "top_right"; + overlayLayer = true; + backgroundOpacity = 0.5; + respectExpireTimeout = true; + lowUrgencyDuration = 3; + normalUrgencyDuration = 8; + criticalUrgencyDuration = 15; + enableMediaToast = false; + enableKeyboardLayoutToast = true; + batteryWarningThreshold = 20; + batteryCriticalThreshold = 5; + saveToHistory = { + low = true; + normal = true; + critical = true; + }; + sounds.enabled = false; + }; + osd = { + enabled = true; + location = "right"; + autoHideMs = 2000; + overlayLayer = true; + backgroundOpacity = 0.5; + monitors = [ ]; + enabledTypes = [ 0 1 2 3 ]; + }; + audio = { + volumeStep = 5; + volumeOverdrive = false; + cavaFrameRate = 30; + visualizerType = "linear"; + mprisBlacklist = [ ]; + preferredPlayer = ""; + volumeFeedback = false; + }; + brightness = { + brightnessStep = 5; + enforceMinimum = true; + enableDdcSupport = false; + }; + nightLight = { + enabled = true; + autoSchedule = true; + nightTemp = "3700"; + dayTemp = "5500"; + manualSunrise = "06:30"; + manualSunset = "18:30"; + }; + hooks.enabled = false; + desktopWidgets.enabled = false; + + plugins = { + sources = [ + { + enabled = true; + name = "Official Noctalia Plugins"; + url = "https://github.com/noctalia-dev/noctalia-plugins"; + } + { + enabled = true; + name = "Dev"; + url = "https://github.com/Swarsel/noctalia-plugins"; + } + ]; + states = lib.listToAttrs + (map + (plugin: + lib.nameValuePair plugin { + enabled = true; + sourceUrl = "https://github.com/noctalia-dev/noctalia-plugins"; + }) + [ + "clipper" + "github-feed" + "privacy-indicator" + "kaomoji-provider" + "unicode-picker" + "screen-recorder" + ]) // { + todo = { + enabled = true; + sourceUrl = "https://github.com/Swarsel/noctalia-plugins"; + }; + }; + }; + pluginSettings = { + clipper = { + enableTodoIntegration = false; + }; + + todo = { + + caldavEnabled = true; + caldavUrl = caldavTasksEndpoint; + caldavUsername = config.swarselsystems.mainUser; + caldavPasswordType = "file"; + caldavPasswordCmd = ""; + caldavPasswordFile = confLib.getConfig.sops.secrets.radicale-token.path; + caldavSyncInterval = 300; + current_page_id = 1; + pages = [ + { + id = 0; + name = "General"; + } + { + id = 1; + name = "Work"; + } + ]; + }; + + privacy-indicator = { + hideInactive = true; + iconSpacing = 4; + removeMargins = true; + }; + + screen-recorder = { + hideInactive = true; + directory = ""; + filenamePattern = "recording_yyyyMMdd_HHmmss"; + frameRate = "60"; + audioCodec = "opus"; + videoCodec = "h264"; + quality = "very_high"; + colorRange = "limited"; + showCursor = true; + copyToClipboard = true; + audioSource = "default_output"; + videoSource = "portal"; + resolution = "original"; + }; + + github-feed = { + username = lib.toUpper config.swarselsystems.mainUser; + token = confLib.getConfig.repo.secrets.common.noctaliaGithubToken; + refreshInterval = 300; + maxEvents = 50; + showStars = false; + showForks = false; + showPRs = false; + showRepoCreations = false; + showMyRepoStars = true; + showMyRepoForks = true; + openInBrowser = true; + # my fork: + showNotificationBadge = true; + colorizationEnabled = true; + colorizationIcon = "None"; + colorizationBadge = "Primary"; + colorizationBadgeText = "None"; + defaultTab = 1; + enableSystemNotifications = true; + notifyGitHubNotifications = true; + notifyStars = true; + notifyForks = true; + notifyPRs = true; + notifyRepoCreations = true; + notifyMyRepoStars = true; + notifyMyRepoForks = true; + }; + }; + }; + }; + }; + } // lib.optionalAttrs (type != "nixos") { + sops.secrets = lib.mkIf (!config.swarselsystems.isPublic && !config.swarselsystems.isNixos) { + radicale-token = { path = "${xdgDir}/secrets/radicaleToken"; }; + }; + }; +} diff --git a/modules-clone/home/optional/uni.nix b/modules-clone/home/optional/uni.nix new file mode 100644 index 0000000..a841620 --- /dev/null +++ b/modules-clone/home/optional/uni.nix @@ -0,0 +1,22 @@ +{ confLib, ... }: +{ + config = { + services.pizauth = { + enable = true; + accounts = { + uni = { + authUri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; + tokenUri = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + clientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584"; + clientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82"; + scopes = [ + "https://outlook.office365.com/IMAP.AccessAsUser.All" + "https://outlook.office365.com/SMTP.Send" + "offline_access" + ]; + loginHint = "${confLib.getConfig.repo.secrets.local.uni.mailAddress}"; + }; + }; + }; + }; +} diff --git a/modules-clone/home/server/default.nix b/modules-clone/home/server/default.nix new file mode 100644 index 0000000..5b260e9 --- /dev/null +++ b/modules-clone/home/server/default.nix @@ -0,0 +1,10 @@ +{ self, lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/home/server"; + modulesPath = "${self}/modules-clone"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/home/server" ++ [ + "${modulesPath}/home/common/settings.nix" + ]; +} diff --git a/modules-clone/home/server/symlink.nix b/modules-clone/home/server/symlink.nix new file mode 100644 index 0000000..76ddb32 --- /dev/null +++ b/modules-clone/home/server/symlink.nix @@ -0,0 +1,12 @@ +{ self, lib, config, ... }: +{ + options.swarselmodules.server.dotfiles = lib.mkEnableOption "server dotfiles settings"; + config = lib.mkIf config.swarselmodules.server.dotfiles { + home.file = { + "init.el" = lib.mkForce { + source = self + /files/emacs/server.el; + target = ".emacs.d/init.el"; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/appimage.nix b/modules-clone/nixos/client/appimage.nix new file mode 100644 index 0000000..b32e107 --- /dev/null +++ b/modules-clone/nixos/client/appimage.nix @@ -0,0 +1,11 @@ +{ lib, config, ... }: +{ + options.swarselmodules.appimage = lib.mkEnableOption "appimage config"; + config = lib.mkIf config.swarselmodules.appimage { + programs.appimage = { + enable = true; + binfmt = true; + }; + }; + +} diff --git a/modules-clone/nixos/client/autologin.nix b/modules-clone/nixos/client/autologin.nix new file mode 100644 index 0000000..0d27f6d --- /dev/null +++ b/modules-clone/nixos/client/autologin.nix @@ -0,0 +1,13 @@ +{ lib, config, ... }: +let + inherit (config.swarselsystems) mainUser; +in +{ + options.swarselmodules.autologin = lib.mkEnableOption "optional autologin settings"; + config = lib.mkIf config.swarselmodules.autologin { + services = { + getty.autologinUser = mainUser; + greetd.settings.initial_session.user = mainUser; + }; + }; +} diff --git a/modules-clone/nixos/client/blueman.nix b/modules-clone/nixos/client/blueman.nix new file mode 100644 index 0000000..cadc5e6 --- /dev/null +++ b/modules-clone/nixos/client/blueman.nix @@ -0,0 +1,8 @@ +{ lib, config, ... }: +{ + options.swarselmodules.blueman = lib.mkEnableOption "blueman config"; + config = lib.mkIf config.swarselmodules.blueman { + services.blueman.enable = true; + services.hardware.bolt.enable = true; + }; +} diff --git a/modules-clone/nixos/client/default.nix b/modules-clone/nixos/client/default.nix new file mode 100644 index 0000000..339260f --- /dev/null +++ b/modules-clone/nixos/client/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/nixos/client"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/nixos/client"; +} diff --git a/modules-clone/nixos/client/distrobox.nix b/modules-clone/nixos/client/distrobox.nix new file mode 100644 index 0000000..4ec1203 --- /dev/null +++ b/modules-clone/nixos/client/distrobox.nix @@ -0,0 +1,16 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.distrobox = lib.mkEnableOption "distrobox config"; + config = lib.mkIf config.swarselmodules.distrobox { + environment.systemPackages = with pkgs; [ + distrobox + boxbuddy + ]; + + virtualisation.podman = { + enable = true; + dockerCompat = true; + package = pkgs.podman; + }; + }; +} diff --git a/modules-clone/nixos/client/env.nix b/modules-clone/nixos/client/env.nix new file mode 100644 index 0000000..79429ff --- /dev/null +++ b/modules-clone/nixos/client/env.nix @@ -0,0 +1,21 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.env = lib.mkEnableOption "environment config"; + config = lib.mkIf config.swarselmodules.env { + + environment = { + wordlist.enable = true; + sessionVariables = { + NIXOS_OZONE_WL = "1"; + SWARSEL_LO_RES = config.swarselsystems.lowResolution; + SWARSEL_HI_RES = config.swarselsystems.highResolution; + GST_PLUGIN_SYSTEM_PATH_1_0 = lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" (with pkgs.gst_all_1; [ + gst-plugins-good + gst-plugins-bad + gst-plugins-ugly + gst-libav + ]); + } // (lib.optionalAttrs (!config.swarselsystems.isPublic) { }); + }; + }; +} diff --git a/modules-clone/nixos/client/firezone-client.nix b/modules-clone/nixos/client/firezone-client.nix new file mode 100644 index 0000000..922a038 --- /dev/null +++ b/modules-clone/nixos/client/firezone-client.nix @@ -0,0 +1,15 @@ +{ lib, config, ... }: +let + moduleName = "firezone-client"; + inherit (config.swarselsystems) mainUser; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "${moduleName} settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + services.firezone.gui-client = { + enable = true; + inherit (config.node) name; + allowedUsers = [ mainUser ]; + }; + }; +} diff --git a/modules-clone/nixos/client/gnome-keyring.nix b/modules-clone/nixos/client/gnome-keyring.nix new file mode 100644 index 0000000..403bdfb --- /dev/null +++ b/modules-clone/nixos/client/gnome-keyring.nix @@ -0,0 +1,11 @@ +{ lib, config, ... }: +{ + options.swarselmodules.gnome-keyring = lib.mkEnableOption "gnome-keyring config"; + config = lib.mkIf config.swarselmodules.gnome-keyring { + services.gnome.gnome-keyring = { + enable = true; + }; + + programs.seahorse.enable = true; + }; +} diff --git a/modules-clone/nixos/client/gvfs.nix b/modules-clone/nixos/client/gvfs.nix new file mode 100644 index 0000000..059723b --- /dev/null +++ b/modules-clone/nixos/client/gvfs.nix @@ -0,0 +1,7 @@ +{ lib, config, ... }: +{ + options.swarselmodules.gvfs = lib.mkEnableOption "gvfs config for nautilus"; + config = lib.mkIf config.swarselmodules.gvfs { + services.gvfs.enable = true; + }; +} diff --git a/modules-clone/nixos/client/hardware.nix b/modules-clone/nixos/client/hardware.nix new file mode 100644 index 0000000..0273fed --- /dev/null +++ b/modules-clone/nixos/client/hardware.nix @@ -0,0 +1,48 @@ +{ pkgs, config, lib, ... }: +{ + + options.swarselmodules.hardware = lib.mkEnableOption "hardware config"; + options.swarselsystems = { + hasBluetooth = lib.mkEnableOption "bluetooth availability"; + hasFingerprint = lib.mkEnableOption "fingerprint sensor availability"; + trackpoint = { + isAvailable = lib.mkEnableOption "trackpoint availability"; + trackpoint.device = lib.mkOption { + type = lib.types.str; + default = ""; + }; + }; + }; + config = lib.mkIf config.swarselmodules.hardware { + hardware = { + # opengl.driSupport32Bit = true is replaced with graphics.enable32Bit and hence redundant + graphics = { + enable = true; + enable32Bit = true; + }; + + + trackpoint = lib.mkIf config.swarselsystems.trackpoint.isAvailable { + enable = true; + inherit (config.swarselsystems.trackpoint) device; + }; + + keyboard.qmk.enable = true; + + enableAllFirmware = lib.mkDefault true; + + bluetooth = lib.mkIf config.swarselsystems.hasBluetooth { + enable = true; + package = pkgs.bluez; + powerOnBoot = true; + settings = { + General = { + Enable = "Source,Sink,Media,Socket"; + }; + }; + }; + }; + + services.fprintd.enable = lib.mkIf config.swarselsystems.hasFingerprint true; + }; +} diff --git a/modules-clone/nixos/client/hardwarecompatibility-keyboards.nix b/modules-clone/nixos/client/hardwarecompatibility-keyboards.nix new file mode 100644 index 0000000..346c0c2 --- /dev/null +++ b/modules-clone/nixos/client/hardwarecompatibility-keyboards.nix @@ -0,0 +1,11 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.keyboards = lib.mkEnableOption "keyboards config"; + config = lib.mkIf config.swarselmodules.keyboards { + services.udev.packages = with pkgs; [ + qmk-udev-rules + vial + via + ]; + }; +} diff --git a/modules-clone/nixos/client/hardwarecompatibility-ledger.nix b/modules-clone/nixos/client/hardwarecompatibility-ledger.nix new file mode 100644 index 0000000..b919e7a --- /dev/null +++ b/modules-clone/nixos/client/hardwarecompatibility-ledger.nix @@ -0,0 +1,12 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.ledger = lib.mkEnableOption "ledger config"; + config = lib.mkIf config.swarselmodules.ledger { + hardware.ledger.enable = true; + + services.udev.packages = with pkgs; [ + ledger-udev-rules + ]; + }; + +} diff --git a/modules-clone/nixos/client/hardwarecompatibility-yubikey.nix b/modules-clone/nixos/client/hardwarecompatibility-yubikey.nix new file mode 100644 index 0000000..8c5e11e --- /dev/null +++ b/modules-clone/nixos/client/hardwarecompatibility-yubikey.nix @@ -0,0 +1,45 @@ +{ lib, config, pkgs, ... }: +let + inherit (config.swarselsystems) mainUser; + inherit (config.repo.secrets.common.yubikeys) cfg1 cfg2; +in +{ + options.swarselmodules.yubikey = lib.mkEnableOption "yubikey config"; + config = lib.mkIf config.swarselmodules.yubikey { + programs.ssh = { + startAgent = false; # yes we want this to use FIDO2 keys + # enableAskPassword = true; + # askPassword = lib.getExe pkgs.kdePackages.ksshaskpass; + }; + services = { + gnome.gcr-ssh-agent.enable = false; + yubikey-agent.enable = false; + pcscd.enable = true; + + udev.packages = with pkgs; [ + yubikey-personalization + ]; + }; + + hardware.gpgSmartcards.enable = true; + + security.pam.u2f = { + enable = true; + control = "sufficient"; + settings = { + interactive = false; # displays a prompt BEFORE asking for presence + cue = true; # prints a message that a touch is requrired + origin = "pam://${mainUser}"; # make the keys work on all machines + authfile = pkgs.writeText "u2f-mappings" (lib.concatStrings [ + mainUser + cfg1 + cfg2 + ]); + }; + }; + + environment.systemPackages = with pkgs; [ + kdePackages.ksshaskpass + ]; + }; +} diff --git a/modules-clone/nixos/client/interceptiontools.nix b/modules-clone/nixos/client/interceptiontools.nix new file mode 100644 index 0000000..935829f --- /dev/null +++ b/modules-clone/nixos/client/interceptiontools.nix @@ -0,0 +1,32 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.interceptionTools = lib.mkEnableOption "interception tools config"; + config = lib.mkIf config.swarselmodules.interceptionTools { + # Make CAPS work as a dual function ESC/CTRL key + services.interception-tools = { + enable = true; + udevmonConfig = + let + dualFunctionKeysConfig = builtins.toFile "dual-function-keys.yaml" '' + TIMING: + TAP_MILLISEC: 200 + DOUBLE_TAP_MILLISEC: 0 + + MAPPINGS: + - KEY: KEY_CAPSLOCK + TAP: KEY_ESC + HOLD: KEY_LEFTCTRL + ''; + in + '' + - JOB: | + ${pkgs.interception-tools}/bin/intercept -g $DEVNODE \ + | ${pkgs.interception-tools-plugins.dual-function-keys}/bin/dual-function-keys -c ${dualFunctionKeysConfig} \ + | ${pkgs.interception-tools}/bin/uinput -d $DEVNODE + DEVICE: + EVENTS: + EV_KEY: [KEY_CAPSLOCK] + ''; + }; + }; +} diff --git a/modules-clone/nixos/client/keyd.nix b/modules-clone/nixos/client/keyd.nix new file mode 100644 index 0000000..486cac3 --- /dev/null +++ b/modules-clone/nixos/client/keyd.nix @@ -0,0 +1,23 @@ +{ lib, config, ... }: +let + moduleName = "keyd"; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "${moduleName} tools config"; + config = lib.mkIf config.swarselmodules.${moduleName} { + services.keyd = { + enable = true; + keyboards = { + default = { + ids = [ "*" ]; + settings = { + main = { + leftmeta = "overload(meta, macro(rightmeta+z))"; + rightmeta = "overload(meta, macro(rightmeta+z))"; + }; + }; + }; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/lid.nix b/modules-clone/nixos/client/lid.nix new file mode 100644 index 0000000..c426e24 --- /dev/null +++ b/modules-clone/nixos/client/lid.nix @@ -0,0 +1,37 @@ +{ lib, config, ... }: +{ + options.swarselmodules.lid = lib.mkEnableOption "lid config"; + config = lib.mkIf config.swarselmodules.lid { + services.logind.settings.Login = { + HandleLidSwitch = "suspend"; + HandleLidSwitchDocked = "ignore"; + }; + services.acpid = { + enable = true; + handlers.lidClosed = { + event = "button/lid \\w+ close"; + action = '' + cat /sys/class/backlight/amdgpu_bl1/device/enabled + if grep -Fxq disabled /sys/class/backlight/amdgpu_bl1/device/enabled + then + echo "Lid closed. Disabling fprintd." + systemctl stop fprintd + ln -s /dev/null /run/systemd/transient/fprintd.service + systemctl daemon-reload + fi + ''; + }; + handlers.lidOpen = { + event = "button/lid \\w+ open"; + action = '' + if ! $(systemctl is-active --quiet fprintd); then + echo "Lid open. Enabling fprintd." + rm -f /run/systemd/transient/fprintd.service + systemctl daemon-reload + systemctl start fprintd + fi + ''; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/login.nix b/modules-clone/nixos/client/login.nix new file mode 100644 index 0000000..7fbcf7c --- /dev/null +++ b/modules-clone/nixos/client/login.nix @@ -0,0 +1,25 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.login = lib.mkEnableOption "login config"; + config = lib.mkIf config.swarselmodules.login { + services.greetd = { + enable = true; + settings = { + # initial_session.command = "sway"; + initial_session.command = "uwsm start -- niri-uwsm.desktop"; + # --cmd sway + default_session.command = '' + ${pkgs.tuigreet}/bin/tuigreet \ + --time \ + --asterisks \ + --user-menu \ + --cmd "uwsm start -- niri-uwsm.desktop" + ''; + }; + }; + + # environment.etc."greetd/environments".text = '' + # sway + # ''; + }; +} diff --git a/modules-clone/nixos/client/lowbattery.nix b/modules-clone/nixos/client/lowbattery.nix new file mode 100644 index 0000000..cec7315 --- /dev/null +++ b/modules-clone/nixos/client/lowbattery.nix @@ -0,0 +1,33 @@ +{ pkgs, lib, config, ... }: +{ + options.swarselmodules.lowBattery = lib.mkEnableOption "low battery notification config"; + config = lib.mkIf config.swarselmodules.lowBattery { + systemd.user.services."battery-low" = + let + target = "sway-session.target"; + in + { + enable = true; + description = "Timer for battery check that alerts at 10% or less"; + partOf = [ target ]; + wantedBy = [ target ]; + serviceConfig = { + Type = "simple"; + ExecStart = pkgs.writeShellScript "battery-low-notification" + '' + if (( 10 >= $(${lib.getExe pkgs.acpi} -b | head -n 1 | ${lib.getExe pkgs.ripgrep} -o "\d+%" | ${lib.getExe pkgs.ripgrep} -o "\d+") && $(${lib.getExe pkgs.acpi} -b | head -n 1 | ${lib.getExe pkgs.ripgrep} -o "\d+%" | ${lib.getExe pkgs.ripgrep} -o "\d+") > 0 )); + then ${lib.getExe pkgs.libnotify} --urgency=critical "low battery" "$(${lib.getExe pkgs.acpi} -b | head -n 1 | ${lib.getExe pkgs.ripgrep} -o "\d+%")"; + fi; + ''; + }; + }; + systemd.user.timers."battery-low" = { + wantedBy = [ "timers.target" ]; + timerConfig = { + # Every Minute + OnCalendar = "*-*-* *:*:00"; + Unit = "battery-low.service"; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/nautilus.nix b/modules-clone/nixos/client/nautilus.nix new file mode 100644 index 0000000..7ad593a --- /dev/null +++ b/modules-clone/nixos/client/nautilus.nix @@ -0,0 +1,10 @@ +{ lib, config, ... }: +{ + options.swarselmodules.nautilus = lib.mkEnableOption "nautilus config"; + config = lib.mkIf config.swarselmodules.nautilus { + programs.nautilus-open-any-terminal = { + enable = true; + terminal = "kitty"; + }; + }; +} diff --git a/modules-clone/nixos/client/network.nix b/modules-clone/nixos/client/network.nix new file mode 100644 index 0000000..e33b147 --- /dev/null +++ b/modules-clone/nixos/client/network.nix @@ -0,0 +1,307 @@ +{ self, lib, pkgs, config, globals, ... }: +let + certsSopsFile = self + /secrets/repo/certs.yaml; + clientSopsFile = config.node.secretsDir + "/secrets.yaml"; + + inherit (config.repo.secrets.common.network) wlan1 mobile1 vpn1-location vpn1-cipher vpn1-address eduroam-anon; + + iwd = config.networking.networkmanager.wifi.backend == "iwd"; +in +{ + options.swarselsystems = { + firewall = lib.swarselsystems.mkTrueOption; + }; + options.swarselmodules.network = lib.mkEnableOption "network config"; + config = lib.mkIf config.swarselmodules.network { + + sops = { + secrets = lib.mkIf (!config.swarselsystems.isPublic) { + wlan1-pw = { }; + wlan2-pw = { }; + laptop-hotspot-pw = { }; + mobile-hotspot-pw = { }; + eduroam-user = { }; + eduroam-pw = { }; + pia-vpn-user = { }; + pia-vpn-pw = { }; + home-wireguard-client-private-key = { sopsFile = clientSopsFile; }; + home-wireguard-server-public-key = { }; + home-wireguard-endpoint = { }; + pia-vpn1-crl-pem = { sopsFile = certsSopsFile; }; + pia-vpn1-ca-pem = { sopsFile = certsSopsFile; }; + }; + templates = lib.mkIf (!config.swarselsystems.isPublic) { + "network-manager.env".content = '' + WLAN1_PW=${config.sops.placeholder.wlan1-pw} + WLAN2_PW=${config.sops.placeholder.wlan2-pw} + LAPTOP_HOTSPOT_PW=${config.sops.placeholder.laptop-hotspot-pw} + MOBILE_HOTSPOT_PW=${config.sops.placeholder.mobile-hotspot-pw} + EDUROAM_USER=${config.sops.placeholder.eduroam-user} + EDUROAM_PW=${config.sops.placeholder.eduroam-pw} + PIA_VPN_USER=${config.sops.placeholder.pia-vpn-user} + PIA_VPN_PW=${config.sops.placeholder.pia-vpn-pw} + HOME_WIREGUARD_CLIENT_PRIVATE_KEY=${config.sops.placeholder.home-wireguard-client-private-key} + HOME_WIREGUARD_SERVER_PUBLIC_KEY=${config.sops.placeholder.home-wireguard-server-public-key} + HOME_WIREGUARD_ENDPOINT=${config.sops.placeholder.home-wireguard-endpoint} + ''; + }; + }; + + services.resolved.enable = true; + + networking = { + hostName = config.node.name; + hosts = { + "${globals.networks.home-lan.hosts.winters.ipv4}" = [ globals.services.transmission.domain ]; + }; + wireless.iwd = { + enable = true; + settings = { + IPv6 = { + Enabled = true; + }; + Settings = { + AutoConnect = true; + }; + # DriverQuirks = { + # UseDefaultInterface = true; + # }; + }; + }; + nftables.enable = lib.mkDefault true; + enableIPv6 = lib.mkDefault true; + firewall = { + enable = lib.swarselsystems.mkStrong config.swarselsystems.firewall; + checkReversePath = lib.mkDefault false; + allowedUDPPorts = [ 51820 ]; # 51820: wireguard + allowedTCPPortRanges = [ + { from = 1714; to = 1764; } # kde-connect + ]; + allowedUDPPortRanges = [ + { from = 1714; to = 1764; } # kde-connect + ]; + }; + + + networkmanager = { + enable = true; + wifi.backend = "iwd"; + dns = "systemd-resolved"; + plugins = [ + # list of plugins: https://search.nixos.org/packages?query=networkmanager- + # docs https://networkmanager.dev/docs/vpn/ + pkgs.networkmanager-openconnect + pkgs.networkmanager-openvpn + ]; + ensureProfiles = lib.mkIf (!config.swarselsystems.isPublic) { + environmentFiles = [ + "${config.sops.templates."network-manager.env".path}" + ]; + profiles = + let + inherit (config.repo.secrets.local.network) home-wireguard-address home-wireguard-allowed-ips; + in + { + ${wlan1} = { + connection = { + id = wlan1; + # permissions = ""; + type = "wifi"; + autoconnect-priority = "999"; + }; + ipv4 = { + # dns-search = ""; + method = "auto"; + }; + ipv6 = { + addr-gen-mode = "stable-privacy"; + # dns-search = ""; + method = "auto"; + }; + wifi = { + # mac-address-blacklist = ""; + mode = "infrastructure"; + # band = "a"; + ssid = wlan1; + }; + wifi-security = { + # auth-alg = "open"; + key-mgmt = "wpa-psk"; + psk = "$WLAN1_PW"; + }; + }; + + LAN-Party = { + connection = { + autoconnect = "false"; + id = "LAN-Party"; + type = "ethernet"; + }; + ethernet = { + auto-negotiate = "true"; + cloned-mac-address = "preserve"; + }; + ipv4 = { method = "shared"; }; + ipv6 = { + addr-gen-mode = "stable-privacy"; + method = "auto"; + }; + proxy = { }; + }; + + eduroam = { + "802-1x" = { + eap = if (!iwd) then "ttls;" else "peap;"; + identity = "$EDUROAM_USER"; + password = "$EDUROAM_PW"; + phase2-auth = "mschapv2"; + anonymous-identity = lib.mkIf iwd eduroam-anon; + }; + connection = { + id = "eduroam"; + type = "wifi"; + }; + ipv4 = { method = "auto"; }; + ipv6 = { + addr-gen-mode = "default"; + method = "auto"; + }; + proxy = { }; + wifi = { + mode = "infrastructure"; + ssid = "eduroam"; + }; + wifi-security = { + auth-alg = "open"; + key-mgmt = "wpa-eap"; + }; + }; + + local = { + connection = { + autoconnect = "false"; + id = "local"; + type = "ethernet"; + }; + ethernet = { }; + ipv4 = { + address1 = "10.42.1.1/24"; + method = "shared"; + }; + ipv6 = { + addr-gen-mode = "stable-privacy"; + method = "auto"; + }; + proxy = { }; + }; + + ${mobile1} = { + connection = { + id = mobile1; + type = "wifi"; + autoconnect-priority = "500"; + }; + ipv4 = { method = "auto"; }; + ipv6 = { + addr-gen-mode = "default"; + method = "auto"; + }; + proxy = { }; + wifi = { + mode = "infrastructure"; + ssid = mobile1; + }; + wifi-security = { + auth-alg = "open"; + key-mgmt = "wpa-psk"; + psk = "$MOBILE_HOTSPOT_PW"; + }; + }; + + home-wireguard = { + connection = { + id = "HomeVPN"; + type = "wireguard"; + autoconnect = "false"; + interface-name = "wg1"; + }; + wireguard = { private-key = "$HOME_WIREGUARD_CLIENT_PRIVATE_KEY"; }; + "wireguard-peer.$HOME_WIREGURARD_SERVER_PUBLIC_KEY" = { + endpoint = "$HOME_WIREGUARD_ENDPOINT"; + allowed-ips = home-wireguard-allowed-ips; + }; + ipv4 = { + method = "ignore"; + address1 = home-wireguard-address; + }; + ipv6 = { + addr-gen-mode = "stable-privacy"; + method = "ignore"; + }; + proxy = { }; + }; + + pia-vpn1 = { + connection = { + autoconnect = "false"; + id = "PIA ${vpn1-location}"; + type = "vpn"; + }; + ipv4 = { method = "auto"; }; + ipv6 = { + addr-gen-mode = "stable-privacy"; + method = "auto"; + }; + proxy = { }; + vpn = { + auth = "sha1"; + ca = config.sops.secrets."pia-vpn1-ca-pem".path; + challenge-response-flags = "2"; + cipher = vpn1-cipher; + compress = "yes"; + connection-type = "password"; + crl-verify-file = config.sops.secrets."pia-vpn1-crl-pem".path; + dev = "tun"; + password-flags = "0"; + remote = vpn1-address; + remote-cert-tls = "server"; + reneg-seconds = "0"; + service-type = "org.freedesktop.NetworkManager.openvpn"; + username = "$PIA_VPN_USER"; + }; + vpn-secrets = { password = "$PIA_VPN_PW"; }; + }; + + Hotspot = { + connection = { + autoconnect = "false"; + id = "Hotspot"; + type = "wifi"; + }; + ipv4 = { method = "shared"; }; + ipv6 = { + addr-gen-mode = "default"; + method = "ignore"; + }; + proxy = { }; + wifi = { + mode = "ap"; + ssid = "Hotspot-${config.swarselsystems.mainUser}"; + }; + wifi-security = { + group = "ccmp;"; + key-mgmt = "wpa-psk"; + pairwise = "ccmp;"; + proto = "rsn;"; + psk = "$MOBILE_HOTSPOT_PW"; + }; + }; + + }; + }; + }; + }; + + systemd.services.NetworkManager-ensure-profiles.after = [ "NetworkManager.service" ]; + }; +} diff --git a/modules-clone/nixos/client/networkdevices.nix b/modules-clone/nixos/client/networkdevices.nix new file mode 100644 index 0000000..71b5f1d --- /dev/null +++ b/modules-clone/nixos/client/networkdevices.nix @@ -0,0 +1,33 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.networkDevices = lib.mkEnableOption "network device config"; + config = lib.mkIf config.swarselmodules.networkDevices { + # enable scanners over network + hardware.sane = { + enable = true; + extraBackends = [ pkgs.sane-airscan ]; + }; + + # enable discovery and usage of network devices (esp. printers) + services.printing = { + enable = true; + drivers = [ + pkgs.gutenprint + pkgs.gutenprintBin + ]; + browsedConf = '' + BrowseDNSSDSubTypes _cups,_print + BrowseLocalProtocols all + BrowseRemoteProtocols all + CreateIPPPrinterQueues All + BrowseProtocols all + ''; + }; + + services.avahi = { + enable = true; + nssmdns4 = true; + openFirewall = true; + }; + }; +} diff --git a/modules-clone/nixos/client/nix-ld.nix b/modules-clone/nixos/client/nix-ld.nix new file mode 100644 index 0000000..3e61505 --- /dev/null +++ b/modules-clone/nixos/client/nix-ld.nix @@ -0,0 +1,113 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.nix-ld = lib.mkEnableOption "nix-ld config"; + config = lib.mkIf config.swarselmodules.nix-ld { + programs.nix-ld = { + enable = true; + libraries = with pkgs; [ + SDL + SDL2 + SDL2_image + SDL2_mixer + SDL2_ttf + SDL_image + SDL_mixer + SDL_ttf + alsa-lib + at-spi2-atk + at-spi2-core + atk + bzip2 + cairo + cups + curl + dbus + dbus-glib + expat + ffmpeg + flac + fontconfig + freeglut + freetype + fuse3 + gdk-pixbuf + glew_1_10 + glib + gnome2.GConf + pango + gtk2 + gtk3 + icu + libGL + libappindicator-gtk2 + libappindicator-gtk3 + libcaca + libcanberra + libcap + libdbusmenu-gtk2 + libdrm + libelf + libgbm + libgcrypt + libglvnd + libidn + libindicator-gtk2 + libjpeg + libmikmod + libnotify + libogg + libpng + libpng12 + libpulseaudio + librsvg + libsamplerate + libtheora + libtiff + libudev0-shim + libunwind + libusb1 + libuuid + libva + libvdpau + libvorbis + libvpx + libxkbcommon + libxml2 + libz + mesa + nspr + nss + openssl + pango + pipewire + pixman + speex + steam-fhsenv-without-steam + systemd + tbb + vulkan-loader + libice + libsm + libx11 + libxscrnsaver + libxcomposite + libxcursor + libxdamage + libxext + libxfixes + libxft + libxi + libxinerama + libxmu + libxrandr + libxrender + libxt + libxtst + libxxf86vm + libxcb + libxshmfence + zlib + ]; + }; + }; +} diff --git a/modules-clone/nixos/client/nvd-rebuild.nix b/modules-clone/nixos/client/nvd-rebuild.nix new file mode 100644 index 0000000..0f936e0 --- /dev/null +++ b/modules-clone/nixos/client/nvd-rebuild.nix @@ -0,0 +1,18 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.nvd = lib.mkEnableOption "nvd config"; + config = lib.mkIf config.swarselmodules.nvd { + + environment.systemPackages = [ + pkgs.nvd + ]; + + # system.activationScripts.diff = { + # supportsDryActivation = true; + # text = '' + # ${pkgs.nvd}/bin/nvd --color=always --nix-bin-dir=${pkgs.nix}/bin diff \ + # /run/current-system "$systemConfig" + # ''; + # }; + }; +} diff --git a/modules-clone/nixos/client/packages.nix b/modules-clone/nixos/client/packages.nix new file mode 100644 index 0000000..1d9ee61 --- /dev/null +++ b/modules-clone/nixos/client/packages.nix @@ -0,0 +1,101 @@ +{ lib, config, pkgs, minimal, ... }: +{ + options.swarselmodules.packages = lib.mkEnableOption "install packages"; + config = lib.mkIf config.swarselmodules.packages { + + environment.systemPackages = with pkgs; lib.optionals (!minimal) [ + # yubikey packages + gnupg + yubikey-personalization + yubico-pam + yubioath-flutter + yubikey-manager + yubikey-touch-detector + yubico-piv-tool + cfssl + pcsc-tools + pcscliteWithPolkit.out + + + # ledger packages + ledger-live-desktop + + # pinentry + dbus + # swaylock-effects + syncthingtray-minimal + swayosd + + # secure boot + sbctl + + libsForQt5.qt5.qtwayland + + # do not do this! clashes with the flake + # nix-index + + nixos-generators + + # commit hooks + pre-commit + + # proc info + acpi + + # pci info + pciutils + usbutils + + # better make for general tasks + just + + # sops + ssh-to-age + sops + + # keyboards + qmk + vial + via + + # theme related + adwaita-icon-theme + + # kde-connect + xdg-desktop-portal + xdg-desktop-portal-gtk + xdg-desktop-portal-wlr + + # bluetooth + bluez + ghostscript_headless + wireguard-tools + nixd + zig + zls + + elk-to-svg + + ] ++ lib.optionals minimal [ + networkmanager + curl + git + gnupg + rsync + ssh-to-age + sops + vim + just + sbctl + ]; + + nixpkgs.config.permittedInsecurePackages = lib.mkIf (!minimal) [ + "jitsi-meet-1.0.8043" + "electron-29.4.6" + "SDL_ttf-2.0.11" + # audacity? + "mbedtls-2.28.10" + # "qtwebengine-5.15.19" + ]; + }; +} diff --git a/modules-clone/nixos/client/pipewire.nix b/modules-clone/nixos/client/pipewire.nix new file mode 100644 index 0000000..b8e742b --- /dev/null +++ b/modules-clone/nixos/client/pipewire.nix @@ -0,0 +1,20 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.pipewire = lib.mkEnableOption "pipewire config"; + config = lib.mkIf config.swarselmodules.pipewire { + security.rtkit.enable = true; # this is required for pipewire real-time access + + services.pipewire = { + enable = true; + package = pkgs.pipewire; + pulse.enable = true; + jack.enable = true; + audio.enable = true; + wireplumber.enable = true; + alsa = { + enable = true; + support32Bit = true; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/polkit.nix b/modules-clone/nixos/client/polkit.nix new file mode 100644 index 0000000..42786b9 --- /dev/null +++ b/modules-clone/nixos/client/polkit.nix @@ -0,0 +1,29 @@ +{ lib, config, minimal, ... }: +{ + options.swarselmodules.security = lib.mkEnableOption "security config"; + config = lib.mkIf config.swarselmodules.security { + + security = { + # pki.certificateFiles = [ + # config.sops.secrets.harica-root-ca.path + # ]; + pam.services = lib.mkIf (!minimal) { + login.u2fAuth = true; + sudo.u2fAuth = true; + sshd.u2fAuth = false; + swaylock = { + u2fAuth = true; + fprintAuth = false; + }; + }; + polkit.enable = lib.mkIf (!minimal) true; + + sudo.extraConfig = '' + Defaults env_keep+=SSH_AUTH_SOCK + '' + lib.optionalString (!minimal) '' + Defaults env_keep+=XDG_RUNTIME_DIR + Defaults env_keep+=WAYLAND_DISPLAY + ''; + }; + }; +} diff --git a/modules-clone/nixos/client/power-profiles-daemon.nix b/modules-clone/nixos/client/power-profiles-daemon.nix new file mode 100644 index 0000000..a3d8ac3 --- /dev/null +++ b/modules-clone/nixos/client/power-profiles-daemon.nix @@ -0,0 +1,7 @@ +{ lib, config, ... }: +{ + options.swarselmodules.ppd = lib.mkEnableOption "power profiles daemon config"; + config = lib.mkIf config.swarselmodules.ppd { + services.power-profiles-daemon.enable = true; + }; +} diff --git a/modules-clone/nixos/client/programs.nix b/modules-clone/nixos/client/programs.nix new file mode 100644 index 0000000..893a11a --- /dev/null +++ b/modules-clone/nixos/client/programs.nix @@ -0,0 +1,11 @@ +{ lib, config, ... }: +{ + options.swarselmodules.programs = lib.mkEnableOption "small program modules config"; + config = lib.mkIf config.swarselmodules.programs { + programs = { + dconf.enable = true; + evince.enable = true; + kdeconnect.enable = true; + }; + }; +} diff --git a/modules-clone/nixos/client/pulseaudio.nix b/modules-clone/nixos/client/pulseaudio.nix new file mode 100644 index 0000000..84f4dea --- /dev/null +++ b/modules-clone/nixos/client/pulseaudio.nix @@ -0,0 +1,11 @@ +{ config, pkgs, lib, ... }: { + + options.swarselmodules.pulseaudio = lib.mkEnableOption "pulseaudio config"; + config = lib.mkIf config.swarselmodules.pulseaudio { + services.pulseaudio = { + enable = lib.mkIf (!config.services.pipewire.enable) true; + package = pkgs.pulseaudioFull; + }; + }; + +} diff --git a/modules-clone/nixos/client/remotebuild.nix b/modules-clone/nixos/client/remotebuild.nix new file mode 100644 index 0000000..daad657 --- /dev/null +++ b/modules-clone/nixos/client/remotebuild.nix @@ -0,0 +1,86 @@ +{ lib, config, globals, ... }: +let + inherit (config.swarselsystems) homeDir mainUser isClient; +in +{ + options.swarselmodules.remotebuild = lib.mkEnableOption "enable remote builds on this machine"; + config = lib.mkIf config.swarselmodules.remotebuild { + + sops.secrets = { + builder-key = lib.mkIf isClient { owner = mainUser; path = "${homeDir}/.ssh/builder"; mode = "0600"; }; + nixbuild-net-key = { owner = mainUser; path = "${homeDir}/.ssh/nixbuild-net"; mode = "0600"; }; + }; + + nix = { + settings.builders-use-substitutes = true; + distributedBuilds = true; + buildMachines = [ + (lib.mkIf isClient { + hostName = config.repo.secrets.common.builder1-ip; + system = "aarch64-linux"; + maxJobs = 20; + speedFactor = 10; + }) + (lib.mkIf isClient { + hostName = globals.hosts.belchsfactory.wanAddress4; + system = "aarch64-linux"; + maxJobs = 4; + speedFactor = 2; + protocol = "ssh-ng"; + }) + { + hostName = "eu.nixbuild.net"; + system = "x86_64-linux"; + maxJobs = 100; + speedFactor = 2; + supportedFeatures = [ "big-parallel" ]; + } + ]; + }; + + programs.ssh = { + knownHosts = { + nixbuild = { + hostNames = [ "eu.nixbuild.net" ]; + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM"; + }; + builder1 = lib.mkIf isClient { + hostNames = [ config.repo.secrets.common.builder1-ip ]; + publicKey = config.repo.secrets.common.builder1-pubHostKey; + }; + jump = lib.mkIf isClient { + hostNames = [ globals.hosts.liliputsteps.wanAddress4 ]; + publicKey = config.repo.secrets.common.jump-pubHostKey; + }; + builder2 = lib.mkIf isClient { + hostNames = [ globals.hosts.belchsfactory.wanAddress4 ]; + publicKey = config.repo.secrets.common.builder2-pubHostKey; + }; + }; + extraConfig = '' + Host eu.nixbuild.net + ConnectTimeout 1 + PubkeyAcceptedKeyTypes ssh-ed25519 + ServerAliveInterval 60 + IPQoS throughput + IdentityFile ${config.sops.secrets.nixbuild-net-key.path} + '' + lib.optionalString isClient '' + Host ${config.repo.secrets.common.builder1-ip} + ConnectTimeout 1 + User ${mainUser} + IdentityFile ${config.sops.secrets.builder-key.path} + + Host ${globals.hosts.belchsfactory.wanAddress4} + ConnectTimeout 5 + ProxyJump ${globals.hosts.liliputsteps.wanAddress4} + User builder + IdentityFile ${config.sops.secrets.builder-key.path} + + Host ${globals.hosts.liliputsteps.wanAddress4} + ConnectTimeout 1 + User jump + IdentityFile ${config.sops.secrets.builder-key.path} + ''; + }; + }; +} diff --git a/modules-clone/nixos/client/sops.nix b/modules-clone/nixos/client/sops.nix new file mode 100644 index 0000000..d117b22 --- /dev/null +++ b/modules-clone/nixos/client/sops.nix @@ -0,0 +1,16 @@ +{ self, config, lib, ... }: +{ + options.swarselmodules.sops = lib.mkEnableOption "sops config"; + config = lib.mkIf config.swarselmodules.sops { + sops = { + + # age.sshKeyPaths = lib.swarselsystems.mkIfElseList config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" "/persist/.ssh/ssh_host_ed25519_key" ] [ "${config.swarselsystems.homeDir}/.ssh/sops" "/etc/ssh/sops" "/etc/ssh/ssh_host_ed25519_key" ]; + age.sshKeyPaths = [ "${if config.swarselsystems.isImpermanence then "/persist" else ""}/etc/ssh/ssh_host_ed25519_key" ]; + # defaultSopsFile = "${if config.swarselsystems.isImpermanence then "/persist" else ""}${config.swarselsystems.flakePath}/secrets/repo/common.yaml"; + defaultSopsFile = self + "/secrets/repo/common.yaml"; + + validateSopsFiles = false; + + }; + }; +} diff --git a/modules-clone/nixos/client/stylix.nix b/modules-clone/nixos/client/stylix.nix new file mode 100644 index 0000000..8a43e2b --- /dev/null +++ b/modules-clone/nixos/client/stylix.nix @@ -0,0 +1,22 @@ +{ self, lib, config, vars, withHomeManager, ... }: +{ + options.swarselmodules.stylix = lib.mkEnableOption "stylix config"; + config = { + stylix = { + enable = true; + base16Scheme = "${self}/files/stylix/swarsel.yaml"; + } // lib.optionalAttrs config.swarselmodules.stylix + (lib.recursiveUpdate + { + targets.grub.enable = false; # the styling makes grub more ugly + image = config.swarselsystems.wallpaper; + } + vars.stylix); + } // lib.optionalAttrs withHomeManager { + home-manager.users."${config.swarselsystems.mainUser}" = { + stylix = { + targets = vars.stylixHomeTargets; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/sway.nix b/modules-clone/nixos/client/sway.nix new file mode 100644 index 0000000..5cd6adb --- /dev/null +++ b/modules-clone/nixos/client/sway.nix @@ -0,0 +1,20 @@ +{ lib, config, pkgs, withHomeManager, ... }: +let + inherit (config.swarselsystems) mainUser; +in +{ + options.swarselmodules.sway = lib.mkEnableOption "sway config"; + config = lib.mkIf config.swarselmodules.sway + { + programs.sway = { + enable = true; + package = pkgs.swayfx; + wrapperFeatures = { + base = true; + gtk = true; + }; + }; + } // lib.optionalAttrs withHomeManager { + inherit (config.home-manager.users.${mainUser}.wayland.windowManager.sway) extraSessionCommands; + }; +} diff --git a/modules-clone/nixos/client/swayosd.nix b/modules-clone/nixos/client/swayosd.nix new file mode 100644 index 0000000..9b306ef --- /dev/null +++ b/modules-clone/nixos/client/swayosd.nix @@ -0,0 +1,22 @@ +{ lib, pkgs, config, ... }: +{ + options.swarselmodules.swayosd = lib.mkEnableOption "swayosd settings"; + config = lib.mkIf config.swarselmodules.swayosd { + environment.systemPackages = [ pkgs.swayosd ]; + services.udev.packages = [ pkgs.swayosd ]; + systemd.services.swayosd-libinput-backend = { + description = "SwayOSD LibInput backend for listening to certain keys like CapsLock, ScrollLock, VolumeUp, etc."; + documentation = [ "https://github.com/ErikReider/SwayOSD" ]; + wantedBy = [ "graphical.target" ]; + partOf = [ "graphical.target" ]; + after = [ "graphical.target" ]; + + serviceConfig = { + Type = "dbus"; + BusName = "org.erikreider.swayosd"; + ExecStart = "${pkgs.swayosd}/bin/swayosd-libinput-backend"; + Restart = "on-failure"; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/syncthing.nix b/modules-clone/nixos/client/syncthing.nix new file mode 100644 index 0000000..407379d --- /dev/null +++ b/modules-clone/nixos/client/syncthing.nix @@ -0,0 +1,51 @@ +{ lib, config, pkgs, ... }: +let + inherit (config.swarselsystems) mainUser homeDir; + devices = config.swarselsystems.syncthing.syncDevices; + servicePort = 8384; +in +{ + options.swarselmodules.syncthing = lib.mkEnableOption "syncthing config"; + config = lib.mkIf config.swarselmodules.syncthing { + services.syncthing = { + enable = true; + systemService = true; + guiAddress = "127.0.0.1:${builtins.toString servicePort}"; + package = pkgs.syncthing; + user = mainUser; + dataDir = homeDir; + configDir = "${homeDir}/.config/syncthing"; + openDefaultPorts = true; + overrideDevices = true; + overrideFolders = true; + settings = { + options = { + urAccepted = -1; + }; + inherit (config.swarselsystems.syncthing) devices; + folders = { + "Default Folder" = lib.mkDefault { + path = "${homeDir}/Sync"; + inherit devices; + id = "default"; + }; + "Obsidian" = { + path = "${homeDir}/Obsidian"; + inherit devices; + id = "yjvni-9eaa7"; + }; + "Org" = { + path = "${homeDir}/Org"; + inherit devices; + id = "a7xnl-zjj3d"; + }; + "Vpn" = { + path = "${homeDir}/Vpn"; + inherit devices; + id = "hgp9s-fyq3p"; + }; + }; + }; + }; + }; +} diff --git a/modules-clone/nixos/client/systemd.nix b/modules-clone/nixos/client/systemd.nix new file mode 100644 index 0000000..2341d95 --- /dev/null +++ b/modules-clone/nixos/client/systemd.nix @@ -0,0 +1,11 @@ +{ lib, config, ... }: +{ + options.swarselmodules.systemdTimeout = lib.mkEnableOption "systemd timeout config"; + config = lib.mkIf config.swarselmodules.systemdTimeout { + # systemd + systemd.settings.Manager = { + DefaultTimeoutStartSec = "60s"; + DefaultTimeoutStopSec = "15s"; + }; + }; +} diff --git a/modules-clone/nixos/client/uwsm.nix b/modules-clone/nixos/client/uwsm.nix new file mode 100644 index 0000000..e2f170c --- /dev/null +++ b/modules-clone/nixos/client/uwsm.nix @@ -0,0 +1,59 @@ +{ lib, config, pkgs, ... }: +let + moduleName = "uwsm"; + cfg = config.programs.uwsm; +in +{ + options.swarselmodules.${moduleName} = lib.mkEnableOption "${moduleName} settings"; + config = lib.mkIf config.swarselmodules.${moduleName} { + programs.uwsm = { + enable = true; + waylandCompositors = { + sway = { + prettyName = "Sway"; + comment = "Sway compositor managed by UWSM"; + binPath = "/run/current-system/sw/bin/sway"; + }; + niri = lib.mkIf (config.programs ? niri) { + prettyName = "Niri"; + comment = "Niri compositor managed by UWSM"; + binPath = "/run/current-system/sw/bin/niri-session"; + }; + }; + }; + + services.displayManager.sessionPackages = + let + mk_uwsm_desktop_entry = + opts: + (pkgs.writeTextFile { + name = "${opts.name}-uwsm"; + text = '' + [Desktop Entry] + Name=${opts.prettyName} (UWSM) + Comment=${opts.comment} + Exec=${lib.getExe cfg.package} start -F -- ${opts.binPath} ${lib.strings.escapeShellArgs opts.extraArgs} + Type=Application + ''; + destination = "/share/wayland-sessions/${opts.name}-uwsm.desktop"; + derivationArgs = { + passthru.providedSessions = [ "${opts.name}-uwsm" ]; + }; + }); + in + lib.mkForce (lib.mapAttrsToList + ( + name: value: + mk_uwsm_desktop_entry { + inherit name; + inherit (value) + prettyName + comment + binPath + extraArgs + ; + } + ) + cfg.waylandCompositors); + }; +} diff --git a/modules-clone/nixos/client/xdg-portal.nix b/modules-clone/nixos/client/xdg-portal.nix new file mode 100644 index 0000000..633f184 --- /dev/null +++ b/modules-clone/nixos/client/xdg-portal.nix @@ -0,0 +1,20 @@ +{ lib, config, ... }: +{ + options.swarselmodules.xdg-portal = lib.mkEnableOption "xdg portal config"; + config = lib.mkIf config.swarselmodules.xdg-portal { + xdg.portal = { + enable = true; + # config = { + # common = { + # default = "wlr"; + # }; + # }; + # wlr.enable = true; + # wlr.settings.screencast = { + # output_name = "eDP-1"; + # chooser_type = "simple"; + # chooser_cmd = "${pkgs.slurp}/bin/slurp -f %o -or"; + # }; + }; + }; +} diff --git a/modules-clone/nixos/client/zsh.nix b/modules-clone/nixos/client/zsh.nix new file mode 100644 index 0000000..3e6b771 --- /dev/null +++ b/modules-clone/nixos/client/zsh.nix @@ -0,0 +1,13 @@ +{ lib, config, pkgs, ... }: +{ + options.swarselmodules.zsh = lib.mkEnableOption "zsh base config"; + config = lib.mkIf config.swarselmodules.zsh { + programs.zsh = { + enable = true; + enableCompletion = false; + }; + users.defaultUserShell = pkgs.zsh; + environment.shells = with pkgs; [ zsh ]; + environment.pathsToLink = [ "/share/zsh" ]; + }; +} diff --git a/modules-clone/nixos/common/boot.nix b/modules-clone/nixos/common/boot.nix new file mode 100644 index 0000000..758f29c --- /dev/null +++ b/modules-clone/nixos/common/boot.nix @@ -0,0 +1,25 @@ +{ lib, pkgs, config, globals, ... }: +{ + options.swarselmodules.boot = lib.mkEnableOption "boot config"; + config = lib.mkIf config.swarselmodules.boot { + boot = { + initrd.systemd = { + enable = true; + emergencyAccess = globals.root.hashedPassword; + users.root.shell = "${pkgs.bashInteractive}/bin/bash"; + storePaths = [ "${pkgs.bashInteractive}/bin/bash" ]; + extraBin = { + ip = "${pkgs.iproute2}/bin/ip"; + ping = "${pkgs.iputils}/bin/ping"; + cryptsetup = "${pkgs.cryptsetup}/bin/cryptsetup"; + }; + }; + kernelParams = [ "log_buf_len=16M" ]; + tmp.useTmpfs = true; + loader.timeout = lib.mkDefault 2; + }; + + console.earlySetup = true; + + }; +} diff --git a/modules-clone/nixos/common/default.nix b/modules-clone/nixos/common/default.nix new file mode 100644 index 0000000..3ef120f --- /dev/null +++ b/modules-clone/nixos/common/default.nix @@ -0,0 +1,9 @@ +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/nixos/common"; + sharedNames = lib.swarselsystems.readNix "modules-clone/shared"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/nixos/common" ++ + lib.swarselsystems.mkImports sharedNames "modules-clone/shared"; +} diff --git a/modules-clone/nixos/common/globals.nix b/modules-clone/nixos/common/globals.nix new file mode 100644 index 0000000..82db167 --- /dev/null +++ b/modules-clone/nixos/common/globals.nix @@ -0,0 +1,290 @@ +{ lib, options, ... }: +let + inherit (lib) + mkOption + types + ; + + firewallOptions = { + allowedTCPPorts = mkOption { + type = types.listOf types.port; + default = [ ]; + description = "Convenience option to open specific TCP ports for traffic from the network."; + }; + allowedUDPPorts = mkOption { + type = types.listOf types.port; + default = [ ]; + description = "Convenience option to open specific UDP ports for traffic from the network."; + }; + allowedTCPPortRanges = mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.port); + default = [ ]; + description = "Convenience option to open specific TCP port ranges for traffic from another node."; + }; + allowedUDPPortRanges = mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.port); + default = [ ]; + description = "Convenience option to open specific UDP port ranges for traffic from another node."; + }; + }; + + networkOptions = netSubmod: { + cidrv4 = mkOption { + type = types.nullOr types.net.cidrv4; + description = "The CIDRv4 of this network"; + default = null; + }; + + subnetMask4 = mkOption { + type = types.nullOr types.net.ipv4; + description = "The dotted decimal form of the subnet mask of this network"; + readOnly = true; + default = lib.swarselsystems.cidrToSubnetMask netSubmod.config.cidrv4; + }; + + cidrv6 = mkOption { + type = types.nullOr types.net.cidrv6; + description = "The CIDRv6 of this network"; + default = null; + }; + + firewallRuleForAll = mkOption { + default = { }; + description = '' + If this is a wireguard network: Allows you to set specific firewall rules for traffic originating from any participant in this + wireguard network. A corresponding rule `-to-` will be created to easily expose + services to the network. + ''; + type = types.submodule { + options = firewallOptions; + }; + }; + + + + hosts = mkOption { + default = { }; + type = types.attrsOf ( + types.submodule (hostSubmod: { + options = { + id = mkOption { + type = types.int; + description = "The id of this host in the network"; + }; + + mac = mkOption { + type = types.nullOr types.net.mac; + description = "The MAC of the interface on this host that belongs to this network."; + default = null; + }; + + ipv4 = mkOption { + type = types.nullOr types.net.ipv4; + description = "The IPv4 of this host in this network"; + readOnly = true; + default = + if netSubmod.config.cidrv4 == null then + null + else + lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv4; + }; + + ipv6 = mkOption { + type = types.nullOr types.net.ipv6; + description = "The IPv6 of this host in this network"; + readOnly = true; + default = + if netSubmod.config.cidrv6 == null then + null + else + lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv6; + }; + + cidrv4 = mkOption { + type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part + description = "The IPv4 of this host in this network, including CIDR mask"; + readOnly = true; + default = + if netSubmod.config.cidrv4 == null then + null + else + lib.net.cidr.hostCidr hostSubmod.config.id netSubmod.config.cidrv4; + }; + + cidrv6 = mkOption { + type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part + description = "The IPv6 of this host in this network, including CIDR mask"; + readOnly = true; + default = + if netSubmod.config.cidrv6 == null then + null + else + # if we use the /32 wan address as local address directly, do not use the network address in ipv6 + lib.net.cidr.hostCidr (if hostSubmod.config.id == 0 then 1 else hostSubmod.config.id) netSubmod.config.cidrv6; + }; + + firewallRuleForNode = mkOption { + default = { }; + description = '' + If this is a wireguard network: Allows you to set specific firewall rules just for traffic originating from another network node. + A corresponding rule `-node--to-` will be created to easily expose + services to that node. + ''; + type = types.attrsOf ( + types.submodule { + options = firewallOptions; + } + ); + }; + }; + }) + ); + }; + }; +in +{ + options = { + globals = mkOption { + default = { }; + type = types.submodule { + options = { + root = { + hashedPassword = mkOption { + type = types.str; + }; + }; + + user = { + name = mkOption { + type = types.str; + }; + work = mkOption { + type = types.str; + }; + }; + + + services = mkOption { + type = types.attrsOf ( + types.submodule (serviceSubmod: { + options = { + domain = mkOption { + type = types.str; + }; + subDomain = mkOption { + readOnly = true; + type = types.str; + default = lib.swarselsystems.getSubDomain serviceSubmod.config.domain; + }; + baseDomain = mkOption { + readOnly = true; + type = types.str; + default = lib.swarselsystems.getBaseDomain serviceSubmod.config.domain; + }; + proxyAddress4 = mkOption { + type = types.nullOr types.str; + default = null; + }; + proxyAddress6 = mkOption { + type = types.nullOr types.str; + default = null; + }; + serviceAddress = mkOption { + type = types.nullOr types.str; + default = null; + }; + homeServiceAddress = mkOption { + type = types.nullOr types.str; + default = null; + }; + isHome = mkOption { + type = types.bool; + default = false; + }; + }; + }) + ); + }; + + networks = mkOption { + default = { }; + type = types.attrsOf ( + types.submodule (netSubmod: { + options = networkOptions netSubmod // { + vlans = mkOption { + default = { }; + type = types.attrsOf ( + types.submodule (vlanNetSubmod: { + options = networkOptions vlanNetSubmod // { + id = mkOption { + type = types.ints.between 1 4094; + description = "The VLAN id"; + }; + + name = mkOption { + description = "The name of this VLAN"; + default = vlanNetSubmod.config._module.args.name; + type = types.str; + }; + }; + }) + ); + }; + }; + }) + ); + }; + + hosts = mkOption { + type = types.attrsOf ( + types.submodule { + options = { + defaultGateway4 = mkOption { + type = types.nullOr types.net.ipv4; + }; + defaultGateway6 = mkOption { + type = types.nullOr types.net.ipv6; + }; + wanAddress4 = mkOption { + type = types.nullOr types.net.ipv4; + }; + wanAddress6 = mkOption { + type = types.nullOr types.net.ipv6; + }; + isHome = mkOption { + type = types.bool; + }; + }; + } + ); + }; + + domains = { + main = mkOption { + type = types.str; + }; + externalDns = mkOption { + type = types.listOf types.str; + description = "List of external dns nameservers"; + }; + }; + + general = lib.mkOption { + type = types.submodule { + freeformType = types.unspecified; + }; + }; + + }; + + }; + }; + + _globalsDefs = mkOption { + type = types.unspecified; + default = options.globals.definitions; + readOnly = true; + internal = true; + }; + }; +} diff --git a/modules-clone/nixos/common/home-manager-secrets.nix b/modules-clone/nixos/common/home-manager-secrets.nix new file mode 100644 index 0000000..45af43b --- /dev/null +++ b/modules-clone/nixos/common/home-manager-secrets.nix @@ -0,0 +1,42 @@ +{ lib, config, globals, withHomeManager, ... }: +let + inherit (config.swarselsystems) mainUser homeDir; + inherit (config.repo.secrets.common.emacs) radicaleUser; +in +{ + config = { } // lib.optionalAttrs withHomeManager { + sops = + let + modules = config.home-manager.users.${mainUser}.swarselmodules; + in + { + secrets = (lib.optionalAttrs modules.mail { + address1-token = { owner = mainUser; }; + address2-token = { owner = mainUser; }; + address3-token = { owner = mainUser; }; + address4-token = { owner = mainUser; }; + }) // (lib.optionalAttrs modules.waybar { + github-notifications-token = { owner = mainUser; }; + }) // (lib.optionalAttrs modules.emacs { + fever-pw = { path = "${homeDir}/.emacs.d/.fever"; owner = mainUser; }; + }) // (lib.optionalAttrs modules.emacs { + emacs-radicale-pw = { owner = mainUser; }; + github-forge-token = { owner = mainUser; }; + }) // (lib.optionalAttrs (modules ? optional-noctalia) { + radicale-token = { owner = mainUser; }; + }) // (lib.optionalAttrs modules.anki { + anki-user = { owner = mainUser; }; + anki-pw = { owner = mainUser; }; + }); + templates = { + authinfo = lib.mkIf modules.emacs { + path = "${homeDir}/.emacs.d/.authinfo"; + content = '' + machine ${globals.services.radicale.domain} login ${radicaleUser} password ${config.sops.placeholder.emacs-radicale-pw} + ''; + owner = mainUser; + }; + }; + }; + }; +} diff --git a/modules-clone/nixos/common/home-manager.nix b/modules-clone/nixos/common/home-manager.nix new file mode 100644 index 0000000..711883a --- /dev/null +++ b/modules-clone/nixos/common/home-manager.nix @@ -0,0 +1,39 @@ +{ self, inputs, config, lib, homeLib, outputs, globals, nodes, minimal, configName, arch, type, withHomeManager, ... }: +{ + options.swarselmodules.home-manager = lib.mkEnableOption "home-manager"; + config = lib.mkIf config.swarselmodules.home-manager { + home-manager = lib.mkIf withHomeManager { + useGlobalPkgs = true; + useUserPackages = true; + verbose = true; + backupFileExtension = "hm-bak"; + overwriteBackup = true; + users.${config.swarselsystems.mainUser}.imports = [ + inputs.nix-index-database.homeModules.nix-index + # inputs.sops.homeManagerModules.sops # this is not needed!! we add these secrets in nixos scope + inputs.spicetify-nix.homeManagerModules.default + inputs.swarsel-nix.homeModules.default + { + imports = [ + "${self}/profiles/home" + "${self}/modules/home" + { + swarselprofiles = { + minimal = lib.mkIf minimal true; + }; + } + ]; + # node = { + # secretsDir = if (!config.swarselsystems.isNixos) then ../../../hosts/home/${configName}/secrets else ../../../hosts/nixos/${configName}/secrets; + # }; + home.stateVersion = lib.mkDefault config.system.stateVersion; + } + ]; + extraSpecialArgs = { + inherit (inputs) self nixgl; + inherit inputs outputs globals nodes minimal configName arch type; + lib = homeLib; + }; + }; + }; +} diff --git a/modules-clone/nixos/common/impermanence.nix b/modules-clone/nixos/common/impermanence.nix new file mode 100644 index 0000000..2e9b437 --- /dev/null +++ b/modules-clone/nixos/common/impermanence.nix @@ -0,0 +1,95 @@ +{ config, lib, ... }: +let + mapperTarget = lib.swarselsystems.mkIfElse config.swarselsystems.isCrypted "/dev/mapper/cryptroot" "/dev/disk/by-label/nixos"; + inherit (config.swarselsystems) isImpermanence isCrypted isBtrfs; +in +{ + options.swarselmodules.impermanence = lib.mkEnableOption "impermanence config"; + config = lib.mkIf config.swarselmodules.impermanence { + + + security.sudo.extraConfig = lib.mkIf isImpermanence '' + # rollback results in sudo lectures after each reboot + Defaults lecture = never + ''; + + # This script does the actual wipe of the system + # So if it doesn't run, the btrfs system effectively acts like a normal system + # Taken from https://github.com/NotAShelf/nyx/blob/2a8273ed3f11a4b4ca027a68405d9eb35eba567b/modules/core/common/system/impermanence/default.nix + boot.tmp.useTmpfs = lib.mkIf (!isImpermanence) true; + boot.initrd.systemd = lib.mkIf (isImpermanence && isBtrfs) { + enable = true; + services.rollback = { + description = "Rollback BTRFS root subvolume to a pristine state"; + wantedBy = [ "initrd.target" ]; + # make sure it's done after encryption + # i.e. LUKS/TPM process + after = lib.swarselsystems.mkIfElseList isCrypted [ "systemd-cryptsetup@cryptroot.service" ] [ "dev-disk-by\\x2dlabel-nixos.device" ]; + requires = lib.mkIf (!isCrypted) [ "dev-disk-by\\x2dlabel-nixos.device" ]; + # mount the root fs before clearing + before = [ "sysroot.mount" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig.Type = "oneshot"; + script = '' + mkdir -p /mnt + + # We first mount the btrfs root to /mnt + # so we can manipulate btrfs subvolumes. + mount -o subvolid=5 -t btrfs ${mapperTarget} /mnt + btrfs subvolume list -o /mnt/root + + # While we're tempted to just delete /root and create + # a new snapshot from /root-blank, /root is already + # populated at this point with a number of subvolumes, + # which makes `btrfs subvolume delete` fail. + # So, we remove them first. + # + # /root contains subvolumes: + # - /root/var/lib/portables + # - /root/var/lib/machines + + btrfs subvolume list -o /mnt/root | + cut -f9 -d' ' | + while read subvolume; do + echo "deleting /$subvolume subvolume..." + btrfs subvolume delete "/mnt/$subvolume" + done && + echo "deleting /root subvolume..." && + btrfs subvolume delete /mnt/root + + echo "restoring blank /root subvolume..." + btrfs subvolume snapshot /mnt/root-blank /mnt/root + + # Once we're done rolling back to a blank snapshot, + # we can unmount /mnt and continue on the boot process. + umount /mnt + ''; + }; + }; + + + environment.persistence."/persist" = lib.mkIf isImpermanence { + hideMounts = true; + directories = + [ + "/root/.dotfiles" + "/etc/nix" + "/etc/NetworkManager/system-connections" + "/var/lib/nixos" + "/var/tmp" + { + directory = "/var/tmp/nix-import-encrypted"; # Decrypted repo-secrets can be kept + mode = "1777"; + } + # "/etc/secureboot" + ]; + + files = [ + "/etc/ssh/ssh_host_ed25519_key" + "/etc/ssh/ssh_host_ed25519_key.pub" + "/etc/machine-id" + ]; + }; + }; + +} diff --git a/modules-clone/nixos/common/lanzaboote.nix b/modules-clone/nixos/common/lanzaboote.nix new file mode 100644 index 0000000..41204ff --- /dev/null +++ b/modules-clone/nixos/common/lanzaboote.nix @@ -0,0 +1,29 @@ +{ lib, pkgs, config, minimal, ... }: +let + inherit (config.swarselsystems) isSecureBoot isImpermanence; +in +{ + options.swarselmodules.lanzaboote = lib.mkEnableOption "lanzaboote config"; + config = lib.mkIf config.swarselmodules.lanzaboote { + + environment.systemPackages = lib.mkIf isSecureBoot [ + pkgs.sbctl + ]; + + environment.persistence."/persist" = lib.mkIf (isImpermanence && isSecureBoot) { + directories = [{ directory = "/var/lib/sbctl"; }]; + }; + + boot = { + loader = { + efi.canTouchEfiVariables = true; + systemd-boot.enable = lib.swarselsystems.mkIfElse (minimal || !isSecureBoot) (lib.mkForce true) (lib.mkForce false); + }; + lanzaboote = lib.mkIf (!minimal && isSecureBoot) { + enable = true; + pkiBundle = "/var/lib/sbctl"; + configurationLimit = 6; + }; + }; + }; +} diff --git a/modules-clone/nixos/common/nodes.nix b/modules-clone/nixos/common/nodes.nix new file mode 100644 index 0000000..1d1d7de --- /dev/null +++ b/modules-clone/nixos/common/nodes.nix @@ -0,0 +1,69 @@ +# adapted from https://github.com/oddlama/nix-config/blob/main/modules/distributed-config.nix +{ config, lib, nodes, ... }: +let + nodeName = config.node.name; + mkForwardedOption = + path: + lib.mkOption { + type = lib.mkOptionType { + name = "Same type that the receiving option `${lib.concatStringsSep "." path}` normally accepts."; + merge = + _loc: defs: + builtins.filter (x: builtins.isAttrs x -> ((x._type or "") != "__distributed_config_empty")) ( + map (x: x.value) defs + ); + }; + default = { + _type = "__distributed_config_empty"; + }; + description = '' + Anything specified here will be forwarded to `${lib.concatStringsSep "." path}` + on the given node. Forwarding happens as-is to the raw values, + so validity can only be checked on the receiving node. + ''; + }; + + expandOptions = basePath: optionNames: map (option: basePath ++ [ option ]) optionNames; + splitPath = path: lib.splitString "." path; + + forwardedOptions = [ + (splitPath "boot.kernel.sysctl") + (splitPath "networking.nftables.chains.postrouting") + (splitPath "services.kanidm.provision.groups") + (splitPath "services.kanidm.provision.systems.oauth2") + (splitPath "sops.secrets") + (splitPath "swarselsystems.server.dns") + (splitPath "topology.self.services") + (splitPath "environment.persistence") + ] + ++ expandOptions (splitPath "networking.nftables.firewall") [ "zones" "rules" ] + ++ expandOptions (splitPath "services.firezone.gateway") [ "enable" "name" "apiUrl" "tokenFile" "package" "logLevel" ] + ++ expandOptions (splitPath "services.nginx") [ "upstreams" "virtualHosts" ] + ; + + attrsForEachOption = + f: lib.foldl' (acc: path: lib.recursiveUpdate acc (lib.setAttrByPath path (f path))) { } forwardedOptions; +in +{ + options.nodes = lib.mkOption { + description = "Options forwarded to the given node."; + default = { }; + type = lib.types.attrsOf ( + lib.types.submodule { + options = attrsForEachOption mkForwardedOption; + } + ); + }; + + config = + let + getConfig = + path: otherNode: + let + cfg = nodes.${otherNode}.config.nodes.${nodeName} or null; + in + lib.optionals (cfg != null) (lib.getAttrFromPath path cfg); + mergeConfigFromOthers = path: lib.mkMerge (lib.concatMap (getConfig path) (lib.attrNames nodes)); + in + attrsForEachOption mergeConfigFromOthers; +} diff --git a/modules-clone/nixos/common/settings.nix b/modules-clone/nixos/common/settings.nix new file mode 100644 index 0000000..171bd21 --- /dev/null +++ b/modules-clone/nixos/common/settings.nix @@ -0,0 +1,146 @@ +{ self, lib, pkgs, config, outputs, inputs, minimal, globals, withHomeManager, ... }: +let + inherit (config.swarselsystems) mainUser; + inherit (config.repo.secrets.common) atticPublicKey; + settings = if minimal then { } else { + environment.etc."nixos/configuration.nix".source = pkgs.writeText "configuration.nix" '' + assert builtins.trace "This location is not used. The config is found in ${config.swarselsystems.flakePath}!" false; + { } + ''; + + nix = + let + flakeInputs = lib.filterAttrs (_: lib.isType "flake") inputs; + in + { + settings = { + connect-timeout = 5; + bash-prompt-prefix = "$SHLVL:\\w "; + bash-prompt = "$(if [[ $? -gt 0 ]]; then printf \"\"; else printf \"\"; fi)λ "; + fallback = true; + min-free = 128000000; + max-free = 1000000000; + flake-registry = ""; + auto-optimise-store = true; + warn-dirty = false; + max-jobs = 1; + use-cgroups = lib.mkIf config.swarselsystems.isLinux true; + }; + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 10d"; + }; + optimise = { + automatic = true; + dates = "weekly"; + }; + channel.enable = false; + registry = rec { + nixpkgs.flake = inputs.nixpkgs; + # swarsel.flake = inputs.swarsel; + swarsel.flake = self; + n = nixpkgs; + s = swarsel; + }; + nixPath = lib.mapAttrsToList (n: _: "${n}=flake:${n}") flakeInputs; + }; + + services.dbus.implementation = "broker"; + + systemd.services.nix-daemon = { + environment.TMPDIR = "/var/tmp"; + }; + + }; +in +{ + options.swarselmodules.general = lib.mkEnableOption "general nix settings"; + config = lib.mkIf config.swarselmodules.general + (lib.recursiveUpdate + { + sops.secrets = lib.mkIf (!minimal) { + github-api-token = { owner = mainUser; }; + }; + + nix = + let + nix-version = "2_30"; + in + { + package = pkgs.nixVersions."nix_${nix-version}"; + settings = { + experimental-features = [ + "nix-command" + "flakes" + "ca-derivations" + "cgroups" + "pipe-operators" + ]; + substituters = [ + "https://${globals.services.attic.domain}/${mainUser}" + ]; + trusted-public-keys = [ + atticPublicKey + ]; + trusted-users = [ + "@wheel" + "${config.swarselsystems.mainUser}" + (lib.mkIf config.swarselmodules.server.ssh-builder "builder") + ]; + }; + # extraOptions = '' + # plugin-files = ${pkgs.dev.nix-plugins}/lib/nix/plugins + # extra-builtins-file = ${self + /nix/extra-builtins.nix} + # '' + lib.optionalString (!minimal) '' + # !include ${config.sops.secrets.github-api-token.path} + # ''; + # extraOptions = '' + # plugin-files = ${pkgs.nix-plugins.overrideAttrs (o: { + # buildInputs = [config.nix.package pkgs.boost]; + # patches = o.patches or []; + # })}/lib/nix/plugins + # extra-builtins-file = ${self + /nix/extra-builtins.nix} + # ''; + + extraOptions = + let + nix-plugins = pkgs.nix-plugins.override { + nixComponents = pkgs.nixVersions."nixComponents_${nix-version}"; + }; + in + '' + plugin-files = ${nix-plugins}/lib/nix/plugins + extra-builtins-file = ${self + /files/nix/extra-builtins.nix} + '' + lib.optionalString (!minimal) '' + !include ${config.sops.secrets.github-api-token.path} + ''; + }; + + system.stateVersion = lib.mkDefault "23.05"; + + nixpkgs = { + overlays = [ + outputs.overlays.default + outputs.overlays.stables + outputs.overlays.modifications + ] ++ lib.optionals withHomeManager [ + (final: prev: + let + additions = final: _: import "${self}/pkgs/config" { + inherit self config lib; + pkgs = final; + homeConfig = config.home-manager.users.${config.swarselsystems.mainUser} or { }; + }; + in + additions final prev + ) + ]; + config = lib.mkIf (!config.swarselsystems.isMicroVM) { + allowUnfree = true; + }; + }; + + } + settings); +} diff --git a/modules-clone/nixos/common/time.nix b/modules-clone/nixos/common/time.nix new file mode 100644 index 0000000..10e21b4 --- /dev/null +++ b/modules-clone/nixos/common/time.nix @@ -0,0 +1,25 @@ +{ lib, config, ... }: +{ + options.swarselmodules.time = lib.mkEnableOption "time config"; + config = lib.mkIf config.swarselmodules.time { + time = { + timeZone = "Europe/Vienna"; + # hardwareClockInLocalTime = true; + }; + + i18n = { + defaultLocale = "en_US.UTF-8"; + extraLocaleSettings = { + LC_ADDRESS = "de_AT.UTF-8"; + LC_IDENTIFICATION = "de_AT.UTF-8"; + LC_MEASUREMENT = "de_AT.UTF-8"; + LC_MONETARY = "de_AT.UTF-8"; + LC_NAME = "de_AT.UTF-8"; + LC_NUMERIC = "de_AT.UTF-8"; + LC_PAPER = "de_AT.UTF-8"; + LC_TELEPHONE = "de_AT.UTF-8"; + LC_TIME = "de_AT.UTF-8"; + }; + }; + }; +} diff --git a/modules-clone/nixos/common/topology.nix b/modules-clone/nixos/common/topology.nix new file mode 100644 index 0000000..1a298a5 --- /dev/null +++ b/modules-clone/nixos/common/topology.nix @@ -0,0 +1,14 @@ +{ lib, config, ... }: +{ + options.swarselsystems.info = lib.mkOption { + type = lib.types.str; + default = ""; + }; + config.topology = { + id = config.node.name; + self = { + hardware.info = config.swarselsystems.info; + icon = lib.mkIf config.swarselsystems.isLaptop "devices.laptop"; + }; + }; +} diff --git a/modules-clone/nixos/common/xserver.nix b/modules-clone/nixos/common/xserver.nix new file mode 100644 index 0000000..556011e --- /dev/null +++ b/modules-clone/nixos/common/xserver.nix @@ -0,0 +1,12 @@ +{ lib, config, ... }: +{ + options.swarselmodules.xserver = lib.mkEnableOption "xserver keymap"; + config = lib.mkIf config.swarselmodules.packages { + services.xserver = { + xkb = { + layout = "us"; + variant = "altgr-intl"; + }; + }; + }; +} diff --git a/modules-clone/nixos/darwin/default.nix b/modules-clone/nixos/darwin/default.nix new file mode 100644 index 0000000..3ba39c4 --- /dev/null +++ b/modules-clone/nixos/darwin/default.nix @@ -0,0 +1,32 @@ +{ self, lib, config, outputs, globals, withHomeManager, ... }: +let + macUser = globals.user.work; +in +{ + imports = [ + ]; + + options.swarselmodules.optional.darwin = lib.mkEnableOption "optional darwin settings"; + config = lib.mkIf config.swarselmodules.optional.darwin + { + nix.settings.experimental-features = "nix-command flakes"; + nixpkgs = { + hostPlatform = "x86_64-darwin"; + overlays = [ + outputs.overlays.default + outputs.overlays.stables + outputs.overlays.modifications + ]; + config = { + allowUnfree = true; + }; + }; + + system.stateVersion = 4; + } // lib.optionalAttrs withHomeManager { + home-manager.users."${macUser}".imports = [ + "${self}/modules-clone/home/darwin" + ]; + + }; +} diff --git a/modules-clone/nixos/default.nix b/modules-clone/nixos/default.nix new file mode 100644 index 0000000..b370678 --- /dev/null +++ b/modules-clone/nixos/default.nix @@ -0,0 +1,8 @@ +# @ future me: dont panic, optionals and darwin are not read in by readNix +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/nixos"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/nixos"; +} diff --git a/modules-clone/nixos/optional/amdcpu.nix b/modules-clone/nixos/optional/amdcpu.nix new file mode 100644 index 0000000..64ea60d --- /dev/null +++ b/modules-clone/nixos/optional/amdcpu.nix @@ -0,0 +1,8 @@ +_: +{ + config = { + hardware = { + cpu.amd.updateMicrocode = true; + }; + }; +} diff --git a/modules-clone/nixos/optional/amdgpu.nix b/modules-clone/nixos/optional/amdgpu.nix new file mode 100644 index 0000000..f81461c --- /dev/null +++ b/modules-clone/nixos/optional/amdgpu.nix @@ -0,0 +1,15 @@ +_: +{ + config = { + hardware = { + amdgpu = { + opencl.enable = true; + initrd.enable = true; + # amdvlk = { + # enable = true; + # support32Bit.enable = true; + # }; + }; + }; + }; +} diff --git a/modules-clone/nixos/optional/default.nix b/modules-clone/nixos/optional/default.nix new file mode 100644 index 0000000..1092788 --- /dev/null +++ b/modules-clone/nixos/optional/default.nix @@ -0,0 +1,8 @@ +# @ future me: dont panic, this file is not read in by readNix +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/nixos/optional"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/nixos/optional"; +} diff --git a/modules-clone/nixos/optional/framework.nix b/modules-clone/nixos/optional/framework.nix new file mode 100644 index 0000000..929441f --- /dev/null +++ b/modules-clone/nixos/optional/framework.nix @@ -0,0 +1,34 @@ +{ self, lib, config, withHomeManager, ... }: +{ + config = { + + + services = { + fwupd = { + enable = true; + # framework also uses lvfs-testing, but I do not want to use it + extraRemotes = [ "lvfs" ]; + }; + udev.extraRules = '' + # disable Wakeup on Framework Laptop 16 Keyboard (ANSI) + ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0012", ATTR{power/wakeup}="disabled" + # disable Wakeup on Framework Laptop 16 Numpad Module + ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0014", ATTR{power/wakeup}="disabled" + # disable Wakeup on Framework Laptop 16 Trackpad + ACTION=="add", SUBSYSTEM=="i2c", DRIVERS=="i2c_hid_acpi", ATTRS{name}=="PIXA3854:00", ATTR{power/wakeup}="disabled" + ''; + }; + hardware.fw-fanctrl = { + enable = true; + config = { + defaultStrategy = "lazy"; + }; + }; + } // lib.optionalAttrs withHomeManager { + home-manager.users."${config.swarselsystems.mainUser}" = { + imports = [ + "${self}/modules/home/optional/framework.nix" + ]; + }; + }; +} diff --git a/modules-clone/nixos/optional/gaming.nix b/modules-clone/nixos/optional/gaming.nix new file mode 100644 index 0000000..c692cd6 --- /dev/null +++ b/modules-clone/nixos/optional/gaming.nix @@ -0,0 +1,46 @@ +{ self, lib, pkgs, config, withHomeManager, ... }: +{ + config = { + + programs.steam = { + enable = true; + package = pkgs.steam; + extraCompatPackages = [ + pkgs.proton-ge-bin + ]; + }; + # specialisation = { + # gaming.configuration = { + # networking = { + # firewall.enable = lib.mkForce false; + # firewall = { + # allowedUDPPorts = [ 4380 27036 14242 34197 ]; # 34197: factorio; 4380 27036 14242: barotrauma; + # allowedTCPPorts = [ ]; # 34197: factorio; 4380 27036 14242: barotrauma; 51820: wireguard + # allowedTCPPortRanges = [ + # { from = 27015; to = 27030; } # barotrauma + # { from = 27036; to = 27037; } # barotrauma + # ]; + # allowedUDPPortRanges = [ + # { from = 27000; to = 27031; } # barotrauma + # { from = 58962; to = 58964; } # barotrauma + # ]; + # }; + # }; + + + # hardware.xone.enable = true; + + # environment.systemPackages = [ + # pkgs.linuxKernel.packages.linux_6_12.xone + # ]; + # }; + # }; + } // lib.optionalAttrs withHomeManager { + home-manager.users."${config.swarselsystems.mainUser}" = { + imports = [ + "${self}/modules/home/optional/gaming.nix" + ]; + }; + }; + +} diff --git a/modules-clone/nixos/optional/hibernation.nix b/modules-clone/nixos/optional/hibernation.nix new file mode 100644 index 0000000..342afbf --- /dev/null +++ b/modules-clone/nixos/optional/hibernation.nix @@ -0,0 +1,30 @@ +{ lib, config, ... }: +{ + options.swarselsystems = { + hibernation = { + offset = lib.mkOption { + type = lib.types.int; + default = 0; + }; + resumeDevice = lib.mkOption { + type = lib.types.str; + default = "/dev/disk/by-label/nixos"; + }; + }; + }; + config = { + boot = { + kernelParams = [ + "resume_offset=${builtins.toString config.swarselsystems.hibernation.offset}" + # "mem_sleep_default=deep" + ]; + inherit (config.swarselsystems.hibernation) resumeDevice; + }; + systemd.services."systemd-suspend-then-hibernate".aliases = [ "systemd-suspend.service" ]; + powerManagement.enable = true; + systemd.sleep.settings.Sleep = { + HibernateDelaySec = "120m"; + SuspendState = "freeze"; + }; + }; +} diff --git a/modules-clone/nixos/optional/microvm-guest-shares.nix b/modules-clone/nixos/optional/microvm-guest-shares.nix new file mode 100644 index 0000000..9478898 --- /dev/null +++ b/modules-clone/nixos/optional/microvm-guest-shares.nix @@ -0,0 +1,15 @@ +{ lib, config, microVMParent, nodes, ... }: +{ + config = { + microvm = { + shares = [ + { + tag = "persist"; + source = "${lib.optionalString nodes.${microVMParent}.config.swarselsystems.isImpermanence "/persist"}/microvms/${config.networking.hostName}"; + mountPoint = "/persist"; + proto = "virtiofs"; + } + ]; + }; + }; +} diff --git a/modules-clone/nixos/optional/microvm-guest.nix b/modules-clone/nixos/optional/microvm-guest.nix new file mode 100644 index 0000000..1d0ce8f --- /dev/null +++ b/modules-clone/nixos/optional/microvm-guest.nix @@ -0,0 +1,63 @@ +{ self, config, inputs, ... }: +{ + imports = [ + inputs.disko.nixosModules.disko + inputs.home-manager.nixosModules.home-manager + inputs.impermanence.nixosModules.impermanence + inputs.lanzaboote.nixosModules.lanzaboote + inputs.microvm.nixosModules.microvm + inputs.nix-index-database.nixosModules.nix-index + inputs.nix-minecraft.nixosModules.minecraft-servers + inputs.nix-topology.nixosModules.default + inputs.nswitch-rcm-nix.nixosModules.nswitch-rcm + inputs.simple-nixos-mailserver.nixosModules.default + inputs.sops.nixosModules.sops + inputs.stylix.nixosModules.stylix + inputs.swarsel-nix.nixosModules.default + inputs.nixos-nftables-firewall.nixosModules.default + inputs.pia.nixosModules.default + + (inputs.nixos-extra-modules + "/modules/interface-naming.nix") + + "${self}/modules/shared/meta.nix" + ]; + + config = { + _module.args.dns = inputs.dns; + + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug"; + # NOTE: this is needed, we dont import sevrer network module for microvms + globals.hosts.${config.node.name}.isHome = true; + + systemd.network.networks."10-vlan-services" = { + dhcpV6Config = { + WithoutRA = "solicit"; + # duid-en is nice in principle, but I already have MAC info anyways for reservations + DUIDType = "link-layer"; + }; + # networkConfig = { + # IPv6PrivacyExtensions = "no"; + # IPv6AcceptRA = false; + # }; + ipv6AcceptRAConfig = { + DHCPv6Client = "always"; + }; + }; + + # microvm = { + # mount the writeable overlay so that we can use nix shells inside the microvm + # volumes = [ + # { + # image = "/tmp/nix-store-overlay-${config.networking.hostName}.img"; + # autoCreate = true; + # mountPoint = config.microvm.writableStoreOverlay; + # size = 1024; + # } + # ]; + # }; + }; +} diff --git a/modules-clone/nixos/optional/microvm-host.nix b/modules-clone/nixos/optional/microvm-host.nix new file mode 100644 index 0000000..5d5c503 --- /dev/null +++ b/modules-clone/nixos/optional/microvm-host.nix @@ -0,0 +1,23 @@ +{ config, lib, confLib, ... }: +{ + config = lib.mkIf (config.guests != { }) { + + systemd.tmpfiles.settings."15-microvms" = builtins.listToAttrs ( + map + (path: { + name = "${lib.optionalString config.swarselsystems.isImpermanence "/persist"}/microvms/${path}"; + value = { + d = { + group = "kvm"; + user = "microvm"; + mode = "0750"; + }; + }; + }) + (builtins.attrNames config.guests) + ); + + users.persistentIds.microvm = confLib.mkIds 999; + + }; +} diff --git a/modules-clone/nixos/optional/niri.nix b/modules-clone/nixos/optional/niri.nix new file mode 100644 index 0000000..b2d6f94 --- /dev/null +++ b/modules-clone/nixos/optional/niri.nix @@ -0,0 +1,31 @@ +{ self, inputs, config, pkgs, ... }: +{ + imports = [ + inputs.niri-flake.nixosModules.niri + ]; + config = { + + niri-flake.cache.enable = true; + home-manager.users.${config.swarselsystems.mainUser}.imports = [ + "${self}/modules/home/optional/niri.nix" + ]; + + environment.systemPackages = with pkgs; [ + wl-clipboard + wayland-utils + libsecret + cage + gamescope + xwayland-satellite-unstable + ]; + + services.niritiling.enable = true; + + programs = { + niri = { + enable = true; + package = pkgs.niri-stable; # the actual niri that will be installed and used + }; + }; + }; +} diff --git a/modules-clone/nixos/optional/nix-topology-self.nix b/modules-clone/nixos/optional/nix-topology-self.nix new file mode 100644 index 0000000..e713893 --- /dev/null +++ b/modules-clone/nixos/optional/nix-topology-self.nix @@ -0,0 +1,25 @@ +{ lib, config, globals, confLib, ... }: +let + inherit (confLib.static) webProxy; +in +{ + topology.self = { + icon = lib.mkIf config.swarselsystems.isCloud "devices.cloud-server"; + interfaces = { + wan = lib.mkIf (config.swarselsystems.isCloud && config.swarselsystems.server.localNetwork == "wan") { }; + lan = lib.mkIf (config.swarselsystems.isCloud && config.swarselsystems.server.localNetwork == "lan") { }; + wgProxy = lib.mkIf (config.swarselsystems.server.wireguard ? wgHome) { + addresses = [ globals.networks."${webProxy}-wg.hosts".${config.node.name}.ipv4 ]; + renderer.hidePhysicalConnections = true; + virtual = true; + type = "wireguard"; + }; + wgHome = lib.mkIf (config.swarselsystems.server.wireguard ? wgHome) { + addresses = [ globals.networks.home-wgHome.hosts.${config.node.name}.ipv4 ]; + renderer.hidePhysicalConnections = true; + virtual = true; + type = "wireguard"; + }; + }; + }; +} diff --git a/modules-clone/nixos/optional/noctalia.nix b/modules-clone/nixos/optional/noctalia.nix new file mode 100644 index 0000000..c79aa8e --- /dev/null +++ b/modules-clone/nixos/optional/noctalia.nix @@ -0,0 +1,26 @@ +{ self, inputs, config, ... }: +{ + disabledModules = [ "programs/gpu-screen-recorder.nix" ]; + imports = [ + "${inputs.nixpkgs-dev}/nixos/modules/programs/gpu-screen-recorder.nix" + ]; + config = { + home-manager.users.${config.swarselsystems.mainUser}.imports = [ + "${self}/modules/home/optional/noctalia.nix" + ]; + services = { + upower.enable = true; # needed for battery percentage + gnome.evolution-data-server.enable = true; # needed for calendar integration + + noctoggle = { + enable = true; + # noctaliaPackage = pkgs.noctalia-shell; + }; + + }; + programs = { + gpu-screen-recorder.enable = true; + evolution.enable = true; + }; + }; +} diff --git a/modules-clone/nixos/optional/nswitch-rcm.nix b/modules-clone/nixos/optional/nswitch-rcm.nix new file mode 100644 index 0000000..00fb2c1 --- /dev/null +++ b/modules-clone/nixos/optional/nswitch-rcm.nix @@ -0,0 +1,12 @@ +{ pkgs, ... }: +{ + config = { + services.nswitch-rcm = { + enable = true; + package = pkgs.fetchurl { + url = "https://github.com/Atmosphere-NX/Atmosphere/releases/download/1.3.2/fusee.bin"; + hash = "sha256-5AXzNsny45SPLIrvWJA9/JlOCal5l6Y++Cm+RtlJppI="; + }; + }; + }; +} diff --git a/modules-clone/nixos/optional/systemd-networkd-base.nix b/modules-clone/nixos/optional/systemd-networkd-base.nix new file mode 100644 index 0000000..238a1ff --- /dev/null +++ b/modules-clone/nixos/optional/systemd-networkd-base.nix @@ -0,0 +1,13 @@ +{ lib, config, ... }: +{ + networking = { + useDHCP = lib.mkForce false; + useNetworkd = true; + dhcpcd.enable = lib.mkIf (!config.swarselsystems.isMicroVM) false; + renameInterfacesByMac = lib.mkIf (!config.swarselsystems.isMicroVM) (lib.mapAttrs (_: v: if (v ? mac) then v.mac else "") ( + config.repo.secrets.local.networking.networks or { } + )); + }; + + systemd.network.enable = true; +} diff --git a/modules-clone/nixos/optional/systemd-networkd-server-home.nix b/modules-clone/nixos/optional/systemd-networkd-server-home.nix new file mode 100644 index 0000000..0204411 --- /dev/null +++ b/modules-clone/nixos/optional/systemd-networkd-server-home.nix @@ -0,0 +1,146 @@ +{ self, lib, config, globals, ... }: +let + inherit (globals.general) routerServer; + inherit (config.swarselsystems) withMicroVMs isCrypted initrdVLAN; + + isRouter = config.node.name == routerServer; + localVLANsList = config.swarselsystems.localVLANs; + localVLANs = lib.genAttrs localVLANsList (x: globals.networks.home-lan.vlans.${x}); +in +{ + imports = [ + "${self}/modules/nixos/optional/systemd-networkd-server.nix" + ]; + config = { + assertions = [ + { + assertion = ((localVLANsList != [ ]) && (initrdVLAN != null)) || (localVLANsList == [ ]) || (!isCrypted); + message = "This host uses VLANs and disk encryption, thus a VLAN must be specified for initrd or disk encryption must be removed."; + } + ]; + + boot.initrd = lib.mkIf (isCrypted && (localVLANsList != [ ]) && (!isRouter)) { + availableKernelModules = [ "8021q" ]; + kernelModules = [ "8021q" ]; # at least summers needs this to actually find the interfaces + systemd.network = { + enable = true; + netdevs."30-vlan-${initrdVLAN}" = { + netdevConfig = { + Kind = "vlan"; + Name = "vlan-${initrdVLAN}"; + }; + vlanConfig.Id = globals.networks.home-lan.vlans.${initrdVLAN}.id; + }; + networks = { + "10-lan" = { + matchConfig.Name = "lan"; + # This interface should only be used from attached vlans. + # So don't acquire a link local address and only wait for + # this interface to gain a carrier. + networkConfig.LinkLocalAddressing = "no"; + linkConfig.RequiredForOnline = "carrier"; + vlan = [ "vlan-${initrdVLAN}" ]; + }; + "30-vlan-${initrdVLAN}" = { + address = [ + globals.networks.home-lan.vlans.${initrdVLAN}.hosts.${config.node.name}.cidrv4 + globals.networks.home-lan.vlans.${initrdVLAN}.hosts.${config.node.name}.cidrv6 + ]; + matchConfig.Name = "vlan-${initrdVLAN}"; + networkConfig = { + IPv6PrivacyExtensions = "yes"; + }; + linkConfig.RequiredForOnline = "routable"; + }; + }; + }; + }; + + topology.self.interfaces = (lib.mapAttrs' + (vlanName: _: + lib.nameValuePair "vlan-${vlanName}" { + network = lib.mkForce vlanName; + } + ) + localVLANs) // (lib.mapAttrs' + (vlanName: _: + lib.nameValuePair "me-${vlanName}" { + network = lib.mkForce vlanName; + } + ) + localVLANs); + + systemd.network = { + netdevs = lib.flip lib.concatMapAttrs localVLANs ( + vlanName: vlanCfg: { + "30-vlan-${vlanName}" = { + netdevConfig = { + Kind = "vlan"; + Name = "vlan-${vlanName}"; + }; + vlanConfig.Id = vlanCfg.id; + }; + # Create a MACVTAP for ourselves too, so that we can communicate with + # our guests on the same interface. + "40-me-${vlanName}" = lib.mkIf withMicroVMs { + netdevConfig = { + Name = "me-${vlanName}"; + Kind = "macvlan"; + }; + extraConfig = '' + [MACVLAN] + Mode=bridge + ''; + }; + } + ); + networks = { + "10-lan" = lib.mkIf (!isRouter) { + matchConfig.Name = "lan"; + # This interface should only be used from attached vlans. + # So don't acquire a link local address and only wait for + # this interface to gain a carrier. + networkConfig.LinkLocalAddressing = "no"; + linkConfig.RequiredForOnline = "carrier"; + vlan = map (name: "vlan-${name}") (builtins.attrNames localVLANs); + }; + # Remaining macvtap interfaces should not be touched. + "90-macvtap-ignore" = lib.mkIf withMicroVMs { + matchConfig.Kind = "macvtap"; + linkConfig.ActivationPolicy = "manual"; + linkConfig.Unmanaged = "yes"; + }; + } + // lib.flip lib.concatMapAttrs localVLANs ( + vlanName: vlanCfg: + let + me = { + address = [ + vlanCfg.hosts.${config.node.name}.cidrv4 + vlanCfg.hosts.${config.node.name}.cidrv6 + ]; + gateway = lib.optionals (vlanName == "services") [ vlanCfg.hosts.${routerServer}.ipv4 vlanCfg.hosts.${routerServer}.ipv6 ]; + matchConfig.Name = "${if withMicroVMs then "me" else "vlan"}-${vlanName}"; + networkConfig.IPv6PrivacyExtensions = "yes"; + linkConfig.RequiredForOnline = "routable"; + }; + + in + { + "30-vlan-${vlanName}" = if (!withMicroVMs) then me else { + matchConfig.Name = "vlan-${vlanName}"; + # This interface should only be used from attached macvlans. + # So don't acquire a link local address and only wait for + # this interface to gain a carrier. + networkConfig.LinkLocalAddressing = "no"; + networkConfig.MACVLAN = "me-${vlanName}"; + linkConfig.RequiredForOnline = if isRouter then "no" else "carrier"; + }; + "40-me-${vlanName}" = lib.mkIf withMicroVMs (lib.mkDefault me); + } + ); + }; + + }; + +} diff --git a/modules-clone/nixos/optional/systemd-networkd-server.nix b/modules-clone/nixos/optional/systemd-networkd-server.nix new file mode 100644 index 0000000..2ca59f5 --- /dev/null +++ b/modules-clone/nixos/optional/systemd-networkd-server.nix @@ -0,0 +1,53 @@ +{ self, lib, config, globals, ... }: +let + inherit (config.swarselsystems) isCrypted localVLANs; + inherit (globals.general) routerServer; + + isRouter = config.node.name == routerServer; + ifName = config.swarselsystems.server.localNetwork; +in +{ + imports = [ + "${self}/modules/nixos/optional/systemd-networkd-base.nix" + ]; + + boot.initrd.systemd.network = lib.mkIf (isCrypted && ((localVLANs == [ ]) || isRouter)) { + enable = true; + networks."10-${ifName}" = config.systemd.network.networks."10-${ifName}"; + }; + + systemd = { + network = { + wait-online.enable = false; + networks = + let + netConfig = config.repo.secrets.local.networking; + in + { + "10-${ifName}" = lib.mkIf (isRouter || (localVLANs == [ ])) { + # address = lib.optionals (isRouter || (localVLANs == [ ])) [ + address = [ + "${globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.cidrv4}" + "${globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.cidrv6}" + ]; + routes = [ + { + Gateway = netConfig.defaultGateway6; + GatewayOnLink = true; + } + { + Gateway = netConfig.defaultGateway4; + GatewayOnLink = true; + } + ]; + networkConfig = { + IPv6PrivacyExtensions = true; + IPv6AcceptRA = false; + }; + matchConfig.MACAddress = netConfig.networks.${config.swarselsystems.server.localNetwork}.mac; + linkConfig.RequiredForOnline = "routable"; + }; + }; + }; + }; +} diff --git a/modules-clone/nixos/optional/uni.nix b/modules-clone/nixos/optional/uni.nix new file mode 100644 index 0000000..831ea58 --- /dev/null +++ b/modules-clone/nixos/optional/uni.nix @@ -0,0 +1,11 @@ +{ self, config, withHomeManager, ... }: +{ + config = { } // lib.optionalAttrs withHomeManager { + + home-manager.users."${config.swarselsystems.mainUser}" = { + imports = [ + "${self}/modules/home/optional/work.nix" + ]; + }; + }; +} diff --git a/modules-clone/nixos/optional/virtualbox.nix b/modules-clone/nixos/optional/virtualbox.nix new file mode 100644 index 0000000..478afba --- /dev/null +++ b/modules-clone/nixos/optional/virtualbox.nix @@ -0,0 +1,35 @@ +{ lib, config, pkgs, ... }: +{ + config = { + # specialisation = { + # VBox.configuration = { + virtualisation.virtualbox = { + host = { + enable = true; + enableKvm = true; + addNetworkInterface = lib.mkIf config.virtualisation.virtualbox.host.enableKvm false; + package = pkgs.virtualbox; + enableExtensionPack = true; + }; + # leaving this here for future notice. setting guest.enable = true will make 'restarting sysinit-reactivation.target' take till timeout on nixos-rebuild switch + guest = { + enable = false; + }; + }; + # run an older kernel to provide compatibility with windows vm + # boot = { + # kernelPackages = lib.mkForce pkgs.stable24_05.linuxPackages; + # # kernelParams = [ + # # "amd_iommu=on" + # # ]; + # }; + + + # fixes the issue of running together with QEMU + # NOTE: once you start a QEMU VM (use kvm) VirtualBox will fail to start VMs + # boot.kernelParams = [ "kvm.enable_virt_at_load=0" ]; + # }; + # }; + }; + +} diff --git a/modules-clone/nixos/optional/vmware.nix b/modules-clone/nixos/optional/vmware.nix new file mode 100644 index 0000000..d79ff04 --- /dev/null +++ b/modules-clone/nixos/optional/vmware.nix @@ -0,0 +1,8 @@ +_: +{ + + config = { + virtualisation.vmware.host.enable = true; + virtualisation.vmware.guest.enable = true; + }; +} diff --git a/modules-clone/nixos/server/acme.nix b/modules-clone/nixos/server/acme.nix new file mode 100644 index 0000000..e36bdf2 --- /dev/null +++ b/modules-clone/nixos/server/acme.nix @@ -0,0 +1,48 @@ +{ self, pkgs, lib, config, globals, confLib, ... }: +let + inherit (config.repo.secrets.common) dnsProvider dnsBase dnsMail; + + sopsFile = self + "/secrets/nginx/acme.json"; +in +{ + options.swarselmodules.server.acme = lib.mkEnableOption "enable acme on server"; + config = lib.mkIf config.swarselmodules.server.acme { + environment.systemPackages = with pkgs; [ + lego + ]; + + sops = { + secrets = { + acme-creds = { format = "json"; key = ""; group = "acme"; inherit sopsFile; mode = "0660"; }; + }; + templates."certs.secret".content = '' + ACME_DNS_API_BASE = ${dnsBase} + ACME_DNS_STORAGE_PATH=${config.sops.secrets.acme-creds.path} + ''; + }; + + users = { + persistentIds.acme = confLib.mkIds 967; + groups.acme.members = lib.mkIf config.swarselmodules.server.nginx [ "nginx" ]; + }; + + security.acme = { + acceptTerms = true; + defaults = { + inherit dnsProvider; + email = dnsMail; + environmentFile = "${config.sops.templates."certs.secret".path}"; + reloadServices = [ "nginx" ]; + dnsPropagationCheck = true; + }; + certs."${globals.domains.main}" = { + domain = "*.${globals.domains.main}"; + }; + }; + + environment.persistence."/persist" = lib.mkIf config.swarselsystems.isImpermanence { + directories = [{ directory = "/var/lib/acme"; }]; + }; + + }; +} diff --git a/modules-clone/nixos/server/adguardhome.nix b/modules-clone/nixos/server/adguardhome.nix new file mode 100644 index 0000000..3f0acc2 --- /dev/null +++ b/modules-clone/nixos/server/adguardhome.nix @@ -0,0 +1,113 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "adguardhome"; port = 3000; }) serviceName servicePort serviceAddress serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied homeProxyIf webProxy webProxyIf homeWebProxy dnsServer homeDnsServer homeServiceAddress nginxAccessRules; + + homeServices = lib.attrNames (lib.filterAttrs (_: serviceCfg: serviceCfg.isHome) globals.services); + homeDomains = map (name: globals.services.${name}.domain) homeServices; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + networking.firewall = { + allowedTCPPorts = [ 53 ]; + allowedUDPPorts = [ 53 ]; + }; + + services.adguardhome = { + enable = true; + mutableSettings = false; + host = "0.0.0.0"; + port = servicePort; + settings = { + dns = { + bind_hosts = [ + globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv4 + globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv6 + ]; + ratelimit = 300; + upstream_dns = [ + "https://dns.cloudflare.com/dns-query" + "https://dns.google/dns-query" + "https://doh.mullvad.net/dns-query" + ]; + bootstrap_dns = [ + "1.1.1.1" + "2606:4700:4700::1111" + "8.8.8.8" + "2001:4860:4860::8844" + ]; + dhcp.enabled = false; + }; + filtering.rewrites = (map + (domain: { + inherit domain; + # FIXME: change to homeWebProxy once that is setup + answer = globals.networks.home-lan.vlans.services.hosts.${homeWebProxy}.ipv4; + # answer = globals.hosts.${webProxy}.wanAddress4; + enabled = true; + }) + homeDomains) ++ [ + { + domain = "smb.${globals.domains.main}"; + answer = globals.networks.home-lan.vlans.services.hosts.summers-storage.ipv4; + enabled = true; + } + ]; + filters = [ + { + name = "AdGuard DNS filter"; + url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"; + enabled = true; + } + { + name = "AdAway Default Blocklist"; + url = "https://adaway.org/hosts.txt"; + enabled = true; + } + { + name = "OISD (Big)"; + url = "https://big.oisd.nl"; + enabled = true; + } + ]; + user_rules = config.repo.secrets.local.adguardUserRules; + }; + }; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { + directory = "/var/lib/private/AdGuardHome"; + mode = "0700"; + } + ]; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; proxyWebsockets = true; oauth2 = true; oauth2Groups = [ "adguardhome_access" ]; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; proxyWebsockets = true; oauth2 = true; oauth2Groups = [ "adguardhome_access" ]; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + }; +} diff --git a/modules-clone/nixos/server/ankisync.nix b/modules-clone/nixos/server/ankisync.nix new file mode 100644 index 0000000..a1b483b --- /dev/null +++ b/modules-clone/nixos/server/ankisync.nix @@ -0,0 +1,65 @@ +{ self, lib, config, globals, dns, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "ankisync"; port = 27701; }) servicePort serviceName serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + ankiUser = globals.user.name; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + sops.secrets.anki-pw = { inherit sopsFile; owner = "root"; }; + + topology.self.services.anki = { + name = lib.mkForce "Anki Sync Server"; + icon = lib.mkForce "${self}/files/topology-images/${serviceName}.png"; + info = "https://${serviceDomain}"; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/private/anki-sync-server"; }]; + }; + + services.anki-sync-server = { + enable = true; + port = servicePort; + address = "0.0.0.0"; + # openFirewall = true; + users = [ + { + username = ankiUser; + passwordFile = config.sops.secrets.anki-pw.path; + } + ]; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/attic-setup.nix b/modules-clone/nixos/server/attic-setup.nix new file mode 100644 index 0000000..8acf9cf --- /dev/null +++ b/modules-clone/nixos/server/attic-setup.nix @@ -0,0 +1,54 @@ +{ lib, config, pkgs, globals, ... }: + +{ + options.swarselmodules.server.attic-setup = lib.mkEnableOption "enable attic setup"; + config = lib.mkIf config.swarselmodules.server.attic-setup { + + environment.systemPackages = with pkgs; [ + attic-client + ]; + + sops = { + secrets = { + attic-cache-key = { }; + }; + templates = { + "attic-env".content = '' + DOMAIN=https://${globals.services.attic.domain} + TOKEN=${config.sops.placeholder.attic-cache-key} + ''; + }; + }; + + systemd.services.attic-cache-setup = { + description = "Ensure attic is authenticated to cache"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + serviceConfig = { + Type = "oneshot"; + EnvironmentFile = [ + config.sops.templates.attic-env.path + ]; + }; + script = + let + attic = lib.getExe pkgs.attic-client; + in + '' + set -eu + if ${attic} cache info ${config.swarselsystems.mainUser} >/dev/null 2>&1; then + echo "cache already authenticated" + exit 0 + fi + echo "cache not authenticated, attempting login..." + ${attic} login ${config.swarselsystems.mainUser} "$DOMAIN" "$TOKEN" --set-default + ${attic} use ${config.swarselsystems.mainUser} + ''; + + }; + + }; + +} diff --git a/modules-clone/nixos/server/attic.nix b/modules-clone/nixos/server/attic.nix new file mode 100644 index 0000000..e4d6c30 --- /dev/null +++ b/modules-clone/nixos/server/attic.nix @@ -0,0 +1,158 @@ +{ lib, config, pkgs, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "attic"; port = 8091; }) serviceName serviceDir servicePort serviceAddress serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + inherit (config.swarselsystems) mainUser isPublic sopsFile; + serviceDB = "atticd"; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + topology.self.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + # attic does not have a logo + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + sops = lib.mkIf (!isPublic) { + secrets = { + attic-server-token = { inherit sopsFile; }; + attic-garage-access-key = { inherit sopsFile; }; + attic-garage-secret-key = { inherit sopsFile; }; + }; + templates = { + "attic.env" = { + content = '' + ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=${config.sops.placeholder.attic-server-token} + AWS_ACCESS_KEY_ID=${config.sops.placeholder.attic-garage-access-key} + AWS_SECRET_ACCESS_KEY=${config.sops.placeholder.attic-garage-secret-key} + ''; + }; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + services.atticd = { + enable = true; + # NOTE: remove once https://github.com/zhaofengli/attic/pull/268 is merged + package = pkgs.attic-server.overrideAttrs + (oldAttrs: { + patches = (oldAttrs.patches or [ ]) ++ [ + (pkgs.writeText "remove-s3-checksums.patch" '' + diff --git a/server/src/storage/s3.rs b/server/src/storage/s3.rs + index 1d5719f3..036f3263 100644 + --- a/server/src/storage/s3.rs + +++ b/server/src/storage/s3.rs + @@ -278,10 +278,6 @@ impl StorageBackend for S3Backend { + CompletedPart::builder() + .set_e_tag(part.e_tag().map(str::to_string)) + .set_part_number(Some(part_number as i32)) + - .set_checksum_crc32(part.checksum_crc32().map(str::to_string)) + - .set_checksum_crc32_c(part.checksum_crc32_c().map(str::to_string)) + - .set_checksum_sha1(part.checksum_sha1().map(str::to_string)) + - .set_checksum_sha256(part.checksum_sha256().map(str::to_string)) + .build() + }) + .collect::>(); + '') + ]; + }); + environmentFile = config.sops.templates."attic.env".path; + settings = { + listen = "[::]:${builtins.toString servicePort}"; + api-endpoint = "https://${serviceDomain}/"; + allowed-hosts = [ + serviceDomain + ]; + require-proof-of-possession = false; + compression = { + type = "zstd"; + level = 3; + }; + database.url = "postgresql:///atticd?host=/run/postgresql"; + + storage = + if config.swarselmodules.server.garage then { + type = "s3"; + region = mainUser; + bucket = serviceName; + # attic must be patched to never serve pre-signed s3 urls directly + # otherwise it will redirect clients to this localhost endpoint + endpoint = "http://127.0.0.1:3900"; # garage port + } else { + type = "local"; + path = serviceDir; + }; + + garbage-collection = { + interval = "1 day"; + default-retention-period = "3 months"; + }; + + chunking = { + nar-size-threshold = if config.swarselmodules.server.garage then 0 else 64 * 1024; # garage using s3 + + min-size = 16 * 1024; + avg-size = 64 * 1024; + max-size = 256 * 1024; + }; + }; + }; + + services.postgresql = { + enable = true; + enableTCPIP = true; + ensureDatabases = [ serviceDB ]; + ensureUsers = [ + { + name = serviceDB; + ensureDBOwnership = true; + } + ]; + }; + + systemd.services.atticd = lib.mkIf config.swarselmodules.server.garage { + requires = [ "garage.service" ]; + after = [ "garage.service" ]; + }; + + nodes = + let + extraConfigLoc = '' + client_body_timeout 600s; + proxy_connect_timeout 600s; + proxy_send_timeout 600s; + proxy_read_timeout 600s; + proxy_request_buffering off; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/atuin.nix b/modules-clone/nixos/server/atuin.nix new file mode 100644 index 0000000..351139f --- /dev/null +++ b/modules-clone/nixos/server/atuin.nix @@ -0,0 +1,50 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "atuin"; port = 8888; }) servicePort serviceName serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + swarselmodules.server = { + postgresql = true; + }; + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.${serviceName} = { + enable = true; + host = "0.0.0.0"; + port = servicePort; + # openFirewall = true; + openRegistration = false; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; + +} diff --git a/modules-clone/nixos/server/bastion.nix b/modules-clone/nixos/server/bastion.nix new file mode 100644 index 0000000..d1b31e9 --- /dev/null +++ b/modules-clone/nixos/server/bastion.nix @@ -0,0 +1,70 @@ +{ self, lib, config, withHomeManager, confLib, ... }: +{ + options.swarselmodules.server.bastion = lib.mkEnableOption "enable bastion on server"; + config = lib.mkIf config.swarselmodules.server.bastion ({ + + users = { + persistentIds.jump = confLib.mkIds 1001; + groups = { + jump = { }; + }; + users = { + jump = { + autoSubUidGidRange = false; + isNormalUser = true; + useDefaultShell = true; + group = lib.mkForce "jump"; + createHome = lib.mkForce true; + openssh.authorizedKeys.keyFiles = [ + (self + /secrets/public/ssh/yubikey.pub) + (self + /secrets/public/ssh/magicant.pub) + (self + /secrets/public/ssh/builder.pub) + ]; + }; + }; + }; + + + services.openssh = { + enable = true; + startWhenNeeded = lib.mkForce false; + authorizedKeysInHomedir = false; + extraConfig = '' + Match User jump + PermitTTY no + X11Forwarding no + PermitTunnel no + GatewayPorts no + AllowAgentForwarding no + ''; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + PermitRootLogin = lib.mkDefault "no"; + AllowUsers = [ + "jump" + ]; + }; + hostKeys = lib.mkIf (!config.swarselmodules.server.ssh) [ + { + path = "/etc/ssh/ssh_host_ed25519_key"; + type = "ed25519"; + } + ]; + }; + } // lib.optionalAttrs withHomeManager { + + home-manager.users.jump.config = { + home.stateVersion = lib.mkDefault "23.05"; + programs.ssh = { + enable = true; + enableDefaultConfig = false; + matchBlocks = { + "*" = { + forwardAgent = false; + }; + } // config.repo.secrets.local.ssh.hosts; + }; + }; + }); +} diff --git a/modules-clone/nixos/server/btrfs.nix b/modules-clone/nixos/server/btrfs.nix new file mode 100644 index 0000000..bc71a74 --- /dev/null +++ b/modules-clone/nixos/server/btrfs.nix @@ -0,0 +1,9 @@ +{ lib, config, ... }: +{ + options.swarselmodules.btrfs = lib.mkEnableOption "optional btrfs settings"; + config = lib.mkIf config.swarselmodules.btrfs { + boot = { + supportedFilesystems = lib.mkIf config.swarselsystems.isBtrfs [ "btrfs" ]; + }; + }; +} diff --git a/modules-clone/nixos/server/croc.nix b/modules-clone/nixos/server/croc.nix new file mode 100644 index 0000000..c5b4f5c --- /dev/null +++ b/modules-clone/nixos/server/croc.nix @@ -0,0 +1,75 @@ +{ self, lib, config, pkgs, dns, globals, confLib, ... }: +let + inherit (confLib.gen { name = "croc"; proxy = config.node.name; }) serviceName serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome dnsServer; + servicePorts = [ + 9009 + 9010 + 9011 + 9012 + 9013 + ]; + + inherit (config.swarselsystems) sopsFile; + + cfg = config.services.croc; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + nodes.${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + + sops = { + secrets = { + croc-password = { inherit sopsFile; }; + }; + + templates = { + "croc-env" = { + content = '' + CROC_PASS="${config.sops.placeholder.croc-password}" + ''; + }; + }; + }; + + + topology.self.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + globals.services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome; + }; + + services.${serviceName} = { + enable = true; + ports = servicePorts; + pass = config.sops.secrets.croc-password.path; + openFirewall = true; + }; + + + systemd.services = { + ${serviceName} = { + serviceConfig = { + ExecStart = lib.mkForce "${pkgs.croc}/bin/croc ${lib.optionalString cfg.debug "--debug"} relay --ports ${ + lib.concatMapStringsSep "," toString cfg.ports}"; + EnvironmentFile = [ + config.sops.templates.croc-env.path + ]; + }; + }; + }; + + # ports are opened on the firewall for croc, no nginx config + + }; + +} diff --git a/modules-clone/nixos/server/default.nix b/modules-clone/nixos/server/default.nix new file mode 100644 index 0000000..4c2e61f --- /dev/null +++ b/modules-clone/nixos/server/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + importNames = lib.swarselsystems.readNix "modules-clone/nixos/server"; +in +{ + imports = lib.swarselsystems.mkImports importNames "modules-clone/nixos/server"; +} diff --git a/modules-clone/nixos/server/disk-encrypt.nix b/modules-clone/nixos/server/disk-encrypt.nix new file mode 100644 index 0000000..330d98f --- /dev/null +++ b/modules-clone/nixos/server/disk-encrypt.nix @@ -0,0 +1,70 @@ +{ self, pkgs, lib, config, minimal, ... }: +let + + hostKeyPathBase = "/etc/secrets/initrd/ssh_host_ed25519_key"; + hostKeyPath = + if config.swarselsystems.isImpermanence then + "/persist/${hostKeyPathBase}" + else + "${hostKeyPathBase}"; + + # this key is only used only for ssh to stage 1 in initial provisioning (in nix store) + generatedHostKey = pkgs.runCommand "initrd-hostkey" { } '' + ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f $out + ''; +in +{ + options.swarselmodules.server.diskEncryption = lib.mkEnableOption "enable disk encryption config"; + options.swarselsystems.networkKernelModules = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + config = lib.mkIf (config.swarselmodules.server.diskEncryption && config.swarselsystems.isCrypted) { + + + # as soon as we hit a stable system, we will use a persisted key + # @future me: dont mkIf this to minimal, we need to create this as soon as possible + system.activationScripts.ensureInitrdHostkey = { + text = '' + [[ -e ${hostKeyPath} ]] || ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f ${hostKeyPath} + ''; + deps = [ + "users" + ] ++ lib.optional config.swarselsystems.isImpermanence "createPersistentStorageDirs"; + }; + + environment.persistence."/persist" = lib.mkIf (config.swarselsystems.isImpermanence && (config.swarselprofiles.server || minimal)) { + files = [ hostKeyPathBase ]; + }; + + boot = lib.mkIf (!config.swarselsystems.isClient) { + # kernelParams = lib.mkIf (!config.swarselsystems.isCloud && ((config.swarselsystems.localVLANs == []) || isRouter)) [ + # "ip=${localIp}::${gatewayIp}:${subnetMask}:${config.networking.hostName}::none" + # ]; + initrd = { + secrets."/tmp${hostKeyPathBase}" = if minimal then (lib.mkForce generatedHostKey) else (lib.mkForce hostKeyPath); # need to mkForce this or it behaves stateful + availableKernelModules = config.swarselsystems.networkKernelModules; + kernelModules = config.swarselsystems.networkKernelModules; # at least summers needs this to actually find the interfaces + network = { + enable = true; + flushBeforeStage2 = true; + ssh = { + enable = true; + port = 2222; # avoid hostkey changed nag + authorizedKeys = [ + ''command="/bin/systemctl default" ${builtins.readFile "${self}/secrets/public/ssh/yubikey.pub"}'' + ''command="/bin/systemctl default" ${builtins.readFile "${self}/secrets/public/ssh/magicant.pub"}'' + ]; + hostKeys = [ "/tmp${hostKeyPathBase}" ]; # use a tmp file otherwise persist mount will be unhappy + }; + }; + systemd = { + initrdBin = with pkgs; [ + cryptsetup + ]; + }; + }; + }; + }; + +} diff --git a/modules-clone/nixos/server/dns-home.nix b/modules-clone/nixos/server/dns-home.nix new file mode 100644 index 0000000..cda9148 --- /dev/null +++ b/modules-clone/nixos/server/dns-home.nix @@ -0,0 +1,17 @@ +{ lib, config, globals, confLib, ... }: +let + inherit (confLib.gen { name = "dns-home"; }) serviceName; + inherit (confLib.static) homeProxy; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + networking.hosts = { + ${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv4} = [ "server.${homeProxy}.${globals.domains.main}" ]; + ${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv6} = [ "server.${homeProxy}.${globals.domains.main}" ]; + }; + + }; + +} diff --git a/modules-clone/nixos/server/dns-hostrecord.nix b/modules-clone/nixos/server/dns-hostrecord.nix new file mode 100644 index 0000000..e71e452 --- /dev/null +++ b/modules-clone/nixos/server/dns-hostrecord.nix @@ -0,0 +1,14 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "dns-hostrecord"; proxy = config.node.name; }) serviceName proxyAddress4 proxyAddress6; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf (config.swarselmodules.server.${serviceName} && config.swarselsystems.isCloud) { + + nodes.stoicclub.swarselsystems.server.dns.${globals.domains.main}.subdomainRecords = { + "server.${config.node.name}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + + }; +} diff --git a/modules-clone/nixos/server/emacs.nix b/modules-clone/nixos/server/emacs.nix new file mode 100644 index 0000000..311658d --- /dev/null +++ b/modules-clone/nixos/server/emacs.nix @@ -0,0 +1,19 @@ +{ lib, config, confLib, ... }: +let + inherit (confLib.gen { name = "emacs"; port = 9812; }) servicePort serviceName; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} server on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + networking.firewall.allowedTCPPorts = [ servicePort ]; + + services.${serviceName} = { + enable = true; + install = true; + startWithGraphical = false; + }; + + }; + +} diff --git a/modules-clone/nixos/server/firefly-iii.nix b/modules-clone/nixos/server/firefly-iii.nix new file mode 100644 index 0000000..808008f --- /dev/null +++ b/modules-clone/nixos/server/firefly-iii.nix @@ -0,0 +1,133 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "firefly-iii"; port = 80; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome dnsServer webProxy homeWebProxy homeServiceAddress nginxAccessRules; + + nginxGroup = "nginx"; + + inherit (config.swarselsystems) sopsFile; + cfg = config.services.firefly-iii; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds = { + firefly-iii = confLib.mkIds 983; + }; + groups.${serviceGroup} = { }; + users.${serviceUser} = { + group = lib.mkForce serviceGroup; + extraGroups = lib.mkIf cfg.enableNginx [ nginxGroup ]; + isSystemUser = true; + }; + }; + + sops = { + secrets = { + "firefly-iii-app-key" = { inherit sopsFile; owner = serviceUser; group = if cfg.enableNginx then nginxGroup else serviceGroup; mode = "0440"; }; + }; + }; + + # topology.self.services.${serviceName} = { + # name = "Firefly-III"; + # info = "https://${serviceDomain}"; + # icon = "${self}/files/topology-images/${serviceName}.png"; + # }; + + globals.services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services = { + ${serviceName} = { + enable = true; + user = serviceUser; + group = if cfg.enableNginx then nginxGroup else serviceGroup; + dataDir = "/var/lib/${serviceName}"; + settings = { + TZ = config.repo.secrets.common.location.timezone; + APP_URL = "https://${serviceDomain}"; + APP_KEY_FILE = config.sops.secrets.firefly-iii-app-key.path; + APP_ENV = "local"; + DB_CONNECTION = "sqlite"; + TRUSTED_PROXIES = "**"; + # turning these on breaks api access using the waterfly app + # AUTHENTICATION_GUARD = "remote_user_guard"; + # AUTHENTICATION_GUARD_HEADER = "X-User"; + # AUTHENTICATION_GUARD_EMAIL = "X-Email"; + }; + enableNginx = true; + virtualHost = serviceDomain; + }; + + nginx = { + virtualHosts = { + "${serviceDomain}" = { + locations = { + "/api" = { + setOauth2Headers = false; + extraConfig = '' + index index.php; + try_files $uri $uri/ /index.php?$query_string; + add_header Access-Control-Allow-Methods 'GET, POST, HEAD, OPTIONS'; + ''; + }; + }; + }; + }; + }; + }; + + nodes = + let + genNginx = toAddress: extraConfig: { + upstreams = { + ${serviceName} = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + + forceSSL = true; + acmeRoot = null; + oauth2 = { + enable = true; + allowedGroups = [ "firefly_access" ]; + }; + inherit extraConfig; + locations = { + "/" = { + proxyPass = "http://${serviceName}"; + }; + "/api" = { + proxyPass = "http://${serviceName}"; + setOauth2Headers = false; + bypassAuth = true; + }; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = genNginx homeServiceAddress nginxAccessRules; + }; + + }; +} diff --git a/modules-clone/nixos/server/firezone.nix b/modules-clone/nixos/server/firezone.nix new file mode 100644 index 0000000..857ff3d --- /dev/null +++ b/modules-clone/nixos/server/firezone.nix @@ -0,0 +1,407 @@ +{ self, lib, pkgs, config, globals, confLib, dns, nodes, ... }: +let + inherit (confLib.gen { name = "firezone"; dir = "/var/lib/private/firezone"; }) serviceName serviceDir serviceAddress serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied homeProxy webProxy homeWebProxy homeProxyIf webProxyIf idmServer dnsServer homeServiceAddress nginxAccessRules; + inherit (config.swarselsystems) sopsFile; + + apiPort = 8081; + webPort = 8080; + relayPort = 3478; + domainPort = 9003; + + homeServices = lib.attrNames (lib.filterAttrs (_: serviceCfg: serviceCfg.isHome) globals.services); + homeDomains = map (name: globals.services.${name}.domain) homeServices; + allow = group: resource: { + "${group}@${resource}" = { + inherit group resource; + description = "Allow ${group} access to ${resource}"; + }; + }; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy} = { + allowedTCPPorts = [ apiPort webPort domainPort ]; + allowedUDPPorts = [ relayPort ]; + allowedUDPPortRanges = [ + { + from = config.services.firezone.relay.lowestPort; + to = config.services.firezone.relay.highestPort; + } + ]; + }; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy} = { + allowedTCPPorts = [ apiPort webPort domainPort ]; + allowedUDPPorts = [ relayPort ]; + allowedUDPPortRanges = [ + { + from = config.services.firezone.relay.lowestPort; + to = config.services.firezone.relay.highestPort; + } + ]; + }; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + topology.self.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + sops = { + secrets = { + kanidm-firezone-client = { inherit sopsFile; mode = "0400"; }; + firezone-relay-token = { inherit sopsFile; mode = "0400"; }; + firezone-smtp-password = { inherit sopsFile; mode = "0440"; }; + firezone-adapter-config = { inherit sopsFile; mode = "0440"; }; + }; + }; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = serviceDir; mode = "0700"; } + ]; + + services.firezone = { + server = { + enable = true; + enableLocalDB = true; + + smtp = { + inherit (config.repo.secrets.local.firezone.mail) from username; + host = globals.services.mailserver.domain; + port = 465; + implicitTls = true; + passwordFile = config.sops.secrets.firezone-smtp-password.path; + }; + + provision = { + enable = true; + accounts.main = { + name = "Home"; + relayGroups.relays.name = "Relays"; + gatewayGroups.home.name = "Home"; + actors.admin = { + type = "account_admin_user"; + name = "Admin"; + email = "admin@${globals.domains.main}"; + }; + groups.anyone = { + name = "anyone"; + members = [ + "admin" + ]; + }; + + auth.oidc = + let + client_id = "firezone"; + in + { + name = "Kanidm"; + adapter = "openid_connect"; + adapter_config = { + scope = "openid email profile"; + response_type = "code"; + inherit client_id; + discovery_document_uri = "https://${globals.services.kanidm.domain}/oauth2/openid/${client_id}/.well-known/openid-configuration"; + clientSecretFile = config.sops.secrets.kanidm-firezone-client.path; + }; + }; + + resources = + lib.genAttrs homeDomains + (domain: { + type = "dns"; + name = domain; + address = domain; + gatewayGroups = [ "home" ]; + filters = [ + { protocol = "icmp"; } + { + protocol = "tcp"; + ports = [ + 443 + 80 + ]; + } + { + protocol = "udp"; + ports = [ 443 ]; + } + ]; + }) + // { + "home.vlan-services.v4" = { + type = "cidr"; + name = "home.vlan-services.v4"; + address = globals.networks.home-lan.vlans.services.cidrv4; + gatewayGroups = [ "home" ]; + }; + "home.vlan-services.v6" = { + type = "cidr"; + name = "home.vlan-services.v6"; + address = globals.networks.home-lan.vlans.services.cidrv6; + gatewayGroups = [ "home" ]; + }; + }; + + policies = + { } + // allow "everyone" "home.vlan-services.v4" + // allow "anyone" "home.vlan-services.v4" + // allow "everyone" "home.vlan-services.v6" + // allow "anyone" "home.vlan-services.v6" + // lib.mergeAttrsList (map (domain: allow "everyone" domain) homeDomains) + // lib.mergeAttrsList (map (domain: allow "anyone" domain) homeDomains); + }; + }; + + domain = { + settings.ERLANG_DISTRIBUTION_PORT = domainPort; + package = pkgs.firezone-server-domain; + }; + api = { + externalUrl = "https://${serviceDomain}/api/"; + address = "0.0.0.0"; + port = apiPort; + package = pkgs.firezone-server-api; + }; + web = { + externalUrl = "https://${serviceDomain}/"; + address = "0.0.0.0"; + port = webPort; + package = pkgs.firezone-server-web; + }; + }; + + relay = { + enable = true; + port = relayPort; + inherit (config.node) name; + apiUrl = "wss://${serviceDomain}/api/"; + tokenFile = config.sops.secrets.firezone-relay-token.path; + publicIpv4 = proxyAddress4; + publicIpv6 = proxyAddress6; + openFirewall = lib.mkIf (!isProxied) true; + package = pkgs.firezone-relay; + }; + }; + # systemd.services.firezone-initialize = + # let + # generateSecrets = + # let + # requiredSecrets = lib.filterAttrs (_: v: v == null) cfg.settingsSecret; + # in + # '' + # mkdir -p secrets + # chmod 700 secrets + # '' + # + lib.concatLines ( + # lib.forEach (builtins.attrNames requiredSecrets) (secret: '' + # if [[ ! -e secrets/${secret} ]]; then + # echo "Generating ${secret}" + # # Some secrets like TOKENS_KEY_BASE require a value >=64 bytes. + # head -c 64 /dev/urandom | base64 -w 0 > secrets/${secret} + # chmod 600 secrets/${secret} + # fi + # '') + # ); + # loadSecretEnvironment = + # component: + # let + # relevantSecrets = lib.subtractLists (builtins.attrNames cfg.${component}.settings) ( + # builtins.attrNames cfg.settingsSecret + # ); + # in + # lib.concatLines ( + # lib.forEach relevantSecrets ( + # secret: + # ''export ${secret}=$(< ${ + # if cfg.settingsSecret.${secret} == null then + # "secrets/${secret}" + # else + # "\"$CREDENTIALS_DIRECTORY/${secret}\"" + # })'' + # ) + # ); + # in + # { + # script = lib.mkForce '' + # mkdir -p "$TZDATA_DIR" + + # # Generate and load secrets + # ${generateSecrets} + # ${loadSecretEnvironment "domain"} + + # echo "Running migrations" + # ${lib.getExe cfg.domain.package} eval "Domain.Release.migrate(manual: true)" + # ''; + # }; + + + nodes = + let + genNginx = toAddress: extraConfig: { + upstreams = { + ${serviceName} = { + servers."${toAddress}:${builtins.toString webPort}" = { }; + }; + "${serviceName}-api" = { + servers."${toAddress}:${builtins.toString apiPort}" = { }; + }; + }; + virtualHosts = { + ${serviceDomain} = { + useACMEHost = globals.domains.main; + forceSSL = true; + acmeRoot = null; + inherit extraConfig; + locations = { + "/" = { + # The trailing slash is important to strip the location prefix from the request + proxyPass = "http://${serviceName}/"; + proxyWebsockets = true; + }; + "/api/" = { + # The trailing slash is important to strip the location prefix from the request + proxyPass = "http://${serviceName}-api/"; + proxyWebsockets = true; + }; + }; + }; + }; + }; + in + { + ${homeProxy} = + let + nodeCfg = nodes.${homeProxy}.config; + nodePkgs = nodes.${homeProxy}.pkgs; + in + { + sops.secrets.firezone-gateway-token = { inherit (nodeCfg.swarselsystems) sopsFile; mode = "0400"; }; + networking.nftables = { + firewall = { + zones.firezone.interfaces = [ "tun-firezone" ]; + rules = { + # masquerade firezone traffic + masquerade-firezone = { + from = [ "firezone" ]; + to = [ "vlan-services" ]; + # masquerade = true; NOTE: custom rule below for ip4 + ip6 + late = true; # Only accept after any rejects have been processed + verdict = "accept"; + }; + # forward firezone traffic + forward-incoming-firezone-traffic = { + from = [ "firezone" ]; + to = [ "vlan-services" ]; + verdict = "accept"; + }; + + # FIXME: is this needed? conntrack should take care of it and we want to masquerade anyway + forward-outgoing-firezone-traffic = { + from = [ "vlan-services" ]; + to = [ "firezone" ]; + verdict = "accept"; + }; + }; + }; + chains.postrouting = { + masquerade-firezone = { + after = [ "hook" ]; + late = true; + rules = + lib.forEach + [ + "firezone" + ] + ( + zone: + lib.concatStringsSep " " [ + "meta protocol { ip, ip6 }" + (lib.head nodeCfg.networking.nftables.firewall.zones.${zone}.ingressExpression) + (lib.head nodeCfg.networking.nftables.firewall.zones.vlan-services.egressExpression) + "masquerade random" + ] + ); + }; + }; + }; + + environment.persistence."/persist".directories = lib.mkIf nodeCfg.swarselsystems.isImpermanence [ + { directory = "${serviceDir}-gateway"; mode = "0700"; } + ]; + + boot.kernel.sysctl = { + "net.core.wmem_max" = 16777216; + "net.core.rmem_max" = 134217728; + }; + services.firezone.gateway = { + enable = true; + # logLevel = "trace"; + inherit (nodeCfg.node) name; + apiUrl = "wss://${globals.services.firezone.domain}/api/"; + tokenFile = nodeCfg.sops.secrets.firezone-gateway-token.path; + package = nodePkgs.stable25_05.firezone-gateway; # newer versions of firezone-gateway are not compatible with server package + }; + + topology.self.services."${serviceName}-gateway" = { + name = lib.swarselsystems.toCapitalized "${serviceName} Gateway"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + }; + ${idmServer} = + let + nodeCfg = nodes.${idmServer}.config; + accountId = "3e996ad9-c100-40e8-807a-282a5c5e8b6c"; + externalId = "31e7f702-28a7-4bbc-9690-b6db9d4a162a"; + in + { + sops.secrets.kanidm-firezone = { inherit (nodeCfg.swarselsystems) sopsFile; owner = "kanidm"; group = "kanidm"; mode = "0440"; }; + services.kanidm.provision = { + groups."firezone.access" = { }; + systems.oauth2.firezone = { + displayName = "Firezone VPN"; + # NOTE: state: both uuids are runtime values + originUrl = [ + "https://${globals.services.firezone.domain}/${accountId}/sign_in/providers/${externalId}/handle_callback" + "https://${globals.services.firezone.domain}/${accountId}/settings/identity_providers/openid_connect/${externalId}/handle_callback" + ]; + originLanding = "https://${globals.services.firezone.domain}/"; + basicSecretFile = nodeCfg.sops.secrets.kanidm-firezone.path; + preferShortUsername = true; + scopeMaps."firezone.access" = [ + "openid" + "email" + "profile" + ]; + }; + + }; + }; + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (genNginx homeServiceAddress nginxAccessRules); + }; + + }; +} diff --git a/modules-clone/nixos/server/forgejo.nix b/modules-clone/nixos/server/forgejo.nix new file mode 100644 index 0000000..146ac7c --- /dev/null +++ b/modules-clone/nixos/server/forgejo.nix @@ -0,0 +1,159 @@ +{ lib, config, pkgs, globals, dns, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "forgejo"; port = 3004; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + kanidmDomain = globals.services.kanidm.domain; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + users = { + persistentIds = { + forgejo = confLib.mkIds 985; + }; + users.${serviceUser} = { + group = serviceGroup; + isSystemUser = true; + }; + }; + + users.groups.${serviceGroup} = { }; + + sops.secrets = { + kanidm-forgejo-client = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services.${serviceName} = { + enable = true; + stateDir = "/var/lib/${serviceName}"; + user = serviceUser; + group = serviceGroup; + lfs.enable = lib.mkDefault true; + settings = { + DEFAULT = { + APP_NAME = "~SwaGit~"; + }; + server = { + PROTOCOL = "http"; + HTTP_PORT = servicePort; + HTTP_ADDR = "0.0.0.0"; + DOMAIN = serviceDomain; + ROOT_URL = "https://${serviceDomain}"; + }; + # federation.ENABLED = true; + service = { + DISABLE_REGISTRATION = false; + ALLOW_ONLY_INTERNAL_REGISTRATION = false; + ALLOW_ONLY_EXTERNAL_REGISTRATION = true; + SHOW_REGISTRATION_BUTTON = false; + }; + session.COOKIE_SECURE = true; + oauth2_client = { + # Never use auto account linking with this, otherwise users cannot change + # their new user name and they could potentially overtake other users accounts + # by setting their email address to an existing account. + # With "login" linking the user must choose a non-existing username first or login + # with the existing account to link. + ACCOUNT_LINKING = "login"; + USERNAME = "nickname"; + # This does not mean that you cannot register via oauth, but just that there should + # be a confirmation dialog shown to the user before the account is actually created. + # This dialog allows changing user name and email address before creating the account. + ENABLE_AUTO_REGISTRATION = false; + REGISTER_EMAIL_CONFIRM = false; + UPDATE_AVATAR = true; + }; + }; + }; + + systemd.services.${serviceName} = { + serviceConfig.RestartSec = "60"; # Retry every minute + preStart = + let + exe = lib.getExe config.services.forgejo.package; + providerName = "kanidm"; + clientId = serviceName; + args = lib.escapeShellArgs ( + lib.concatLists [ + [ + "--name" + providerName + ] + [ + "--provider" + "openidConnect" + ] + [ + "--key" + clientId + ] + [ + "--auto-discover-url" + "https://${kanidmDomain}/oauth2/openid/${clientId}/.well-known/openid-configuration" + ] + [ + "--scopes" + "email" + ] + [ + "--scopes" + "profile" + ] + [ + "--group-claim-name" + "groups" + ] + [ + "--admin-group" + "admin" + ] + [ "--skip-local-2fa" ] + ] + ); + in + lib.mkAfter '' + provider_id=$(${exe} admin auth list | ${pkgs.gnugrep}/bin/grep -w '${providerName}' | cut -f1) + SECRET="$(< ${config.sops.secrets.kanidm-forgejo-client.path})" + if [[ -z "$provider_id" ]]; then + ${exe} admin auth add-oauth ${args} --secret "$SECRET" + else + ${exe} admin auth update-oauth --id "$provider_id" ${args} --secret "$SECRET" + fi + ''; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/freshrss.nix b/modules-clone/nixos/server/freshrss.nix new file mode 100644 index 0000000..281b088 --- /dev/null +++ b/modules-clone/nixos/server/freshrss.nix @@ -0,0 +1,128 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "freshrss"; port = 80; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome webProxy homeWebProxy dnsServer homeServiceAddress nginxAccessRules; + + inherit (config.swarselsystems) sopsFile; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds = { + freshrss = confLib.mkIds 986; + }; + users.${serviceUser} = { + extraGroups = [ "users" ]; + group = serviceGroup; + isSystemUser = true; + }; + }; + + users.groups.${serviceGroup} = { }; + + sops = { + secrets = { + freshrss-pw = { inherit sopsFile; owner = serviceUser; }; + kanidm-freshrss-client = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + # freshrss-oidc-crypto-key = { owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + # templates = { + # "freshrss-env" = { + # content = '' + # DATA_PATH=${config.services.freshrss.dataDir} + # OIDC_ENABLED=1 + # OIDC_PROVIDER_METADATA_URL=https://${kanidmDomain}/.well-known/openid-configuration + # OIDC_CLIENT_ID=freshrss + # OIDC_CLIENT_SECRET=${config.sops.placeholder.kanidm-freshrss-client} + # OIDC_CLIENT_CRYPTO_KEY=${config.sops.placeholder.oidc-crypto-key} + # OIDC_REMOTE_USER_CLAIM=preferred_username + # OIDC_SCOPES=openid groups email profile + # OIDC_X_FORWARDED_HEADERS=X-Forwarded-Host X-Forwarded-Port X-Forwarded-Proto + # ''; + # owner = "freshrss"; + # group = "freshrss"; + # mode = "0440"; + # }; + # }; + }; + + # topology.self.services.${serviceName} = { + # name = "FreshRSS"; + # info = "https://${serviceDomain}"; + # icon = "${self}/files/topology-images/${serviceName}.png"; + # }; + + globals.services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services.${serviceName} = + let + inherit (config.repo.secrets.local.freshrss) defaultUser; + in + { + inherit defaultUser; + enable = true; + virtualHost = serviceDomain; + baseUrl = "https://${serviceDomain}"; + authType = "form"; + dataDir = "/var/lib/freshrss"; + passwordFile = config.sops.secrets.freshrss-pw.path; + }; + + # systemd.services.freshrss-config.serviceConfig.EnvironmentFile = [ + # config.sops.templates.freshrss-env.path + # ]; + + nodes = + let + genNginx = toAddress: extraConfig: { + upstreams = { + ${serviceName} = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + + forceSSL = true; + acmeRoot = null; + oauth2.enable = true; + oauth2.allowedGroups = [ "ttrss_access" ]; + inherit extraConfig; + locations = { + "/" = { + proxyPass = "http://${serviceName}"; + }; + "/api" = { + proxyPass = "http://${serviceName}"; + setOauth2Headers = false; + bypassAuth = true; + }; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = genNginx homeServiceAddress nginxAccessRules; + }; + + }; +} diff --git a/modules-clone/nixos/server/garage.nix b/modules-clone/nixos/server/garage.nix new file mode 100644 index 0000000..8f08fef --- /dev/null +++ b/modules-clone/nixos/server/garage.nix @@ -0,0 +1,400 @@ +# inspired by https://github.com/atropos112/nixos/blob/7fef652006a1c939f4caf9c8a0cb0892d9cdfe21/modules/garage.nix +{ self, lib, pkgs, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "garage"; port = 3900; domain = config.repo.secrets.common.services.domains."garage-${config.node.name}"; }) servicePort serviceName specificServiceName serviceDomain subDomain baseDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + cfg = lib.recursiveUpdate config.services.${serviceName} config.swarselsystems.server.${serviceName}; + inherit (config.swarselsystems) sopsFile mainUser; + + # needs SSD + metadata_dir = "/var/lib/garage/meta"; + # metadata_dir = if config.swarselsystems.isCloud then "/var/lib/garage/meta" else "/Vault/data/garage/meta"; + + garageRpcPort = 3901; + garageWebPort = 3902; + garageAdminPort = 3903; + garageK2VPort = 3904; + + adminDomain = "${subDomain}-admin.${baseDomain}"; + webDomain = "${subDomain}-web.${baseDomain}"; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + swarselsystems.server.${serviceName} = { + data_dir = { + path = lib.mkOption { + type = lib.types.str; + description = "Directory where Garage stores its metadata"; + }; + capacity = lib.mkOption { + type = lib.types.str; + }; + }; + buckets = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "List of buckets to create"; + }; + keys = lib.mkOption { + type = lib.types.attrsOf (lib.types.listOf lib.types.str); + default = { }; + description = "Keys and their associated buckets. Each key gets full access (read/write/owner) to its listed buckets."; + example = { + my_key_name = [ "bucket1" "bucket2" ]; + my_other_key = [ "bucket2" "bucket3" ]; + }; + }; + }; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + assertions = [ + { + assertion = config.swarselsystems.server.${serviceName}.buckets != [ ]; + message = "If Garage is enabled, at least one bucket must be specified in swarselsystems.server.${serviceName}.buckets"; + } + { + assertion = builtins.length (lib.attrsToList config.swarselsystems.server.${serviceName}.keys) > 0; + message = "If Garage is enabled, at least one key must be specified in swarselsystems.server.${serviceName}.keys"; + } + { + assertion = + let + allKeyBuckets = lib.flatten (lib.attrValues config.swarselsystems.server.${serviceName}.keys); + invalidBuckets = builtins.filter (bucket: !(lib.elem bucket config.swarselsystems.server.${serviceName}.buckets)) allKeyBuckets; + in + invalidBuckets == [ ]; + message = "All buckets referenced in keys must exist in the buckets list"; + } + ]; + + # networking.firewall.allowedTCPPorts = [ servicePort 3901 3902 3903 3904 ]; + + topology.self.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + sops = { + secrets.garage-admin-token = { inherit sopsFile; }; + secrets.garage-rpc-secret = { inherit sopsFile; }; + }; + + # DynamicUser cannot read above secrets + systemd.services.${serviceName}.serviceConfig = { + DynamicUser = false; + ProtectHome = lib.mkForce false; + }; + + environment = { + persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = "/var/lib/garage"; } + (lib.mkIf config.swarselsystems.isCloud { directory = config.swarselsystems.server.${serviceName}.data_dir.path; }) + ]; + systemPackages = [ + cfg.package + ]; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort 3901 3902 3903 3904 ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort 3901 3902 3903 3904 ]; + }; + }; + services.${specificServiceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + + services.${serviceName} = { + enable = true; + package = pkgs.garage_2; + settings = { + data_dir = [ config.swarselsystems.server.${serviceName}.data_dir ]; + inherit metadata_dir; + db_engine = "lmdb"; + block_size = "128M"; + use_local_tz = false; + disable_scrub = true; + replication_factor = 1; + compression_level = "none"; + + rpc_bind_addr = "[::]:${builtins.toString garageRpcPort}"; + # we are not joining our nodes, just use the private ipv4 + rpc_public_addr = "${globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.ipv4}:${builtins.toString garageRpcPort}"; + + rpc_secret_file = config.sops.secrets.garage-rpc-secret.path; + + s3_api = { + s3_region = mainUser; + api_bind_addr = "[::]:${builtins.toString servicePort}"; + root_domain = ".${serviceDomain}"; + }; + + s3_web = { + bind_addr = "[::]:${builtins.toString garageWebPort}"; + root_domain = ".${config.repo.secrets.common.services.domains."garage-web-${config.node.name}"}"; + add_host_to_metrics = true; + }; + + admin = { + api_bind_addr = "[::]:${builtins.toString garageAdminPort}"; + admin_token_file = config.sops.secrets.garage-admin-token.path; + }; + + k2v_api = { + api_bind_addr = "[::]:${builtins.toString garageK2VPort}"; + }; + }; + }; + + + systemd.services = { + garage-buckets = { + description = "Create Garage buckets"; + after = [ "garage.service" ]; + wants = [ "garage.service" ]; + wantedBy = [ "multi-user.target" ]; + + path = [ cfg.package pkgs.gawk pkgs.coreutils ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "root"; + Group = "root"; + }; + + script = '' + garage status + + # Checking repeatedly with garage status until getting 0 exit code + while ! garage status >/dev/null 2>&1; do + echo "Garage not yet operational, waiting..." + echo "Current garage status output:" + garage status 2>&1 || true + echo "---" + sleep 5 + done + + # Now we check if garage status shows any failed nodes by checking for ==== FAILED NODES ==== + while garage status | grep -q "==== FAILED NODES ===="; do + echo "Garage has failed nodes, waiting..." + echo "Current garage status output:" + garage status 2>&1 || true + echo "---" + sleep 5 + done + + echo "Garage is operational, proceeding with bucket management." + + # Get list of existing buckets + existing_buckets=$(garage bucket list | tail -n +2 | awk '{print $3}' | grep -v '^$' || true) + + # Create buckets that should exist + ${lib.concatMapStringsSep "\n" (bucket: '' + if [[ "$(garage bucket info ${lib.escapeShellArg bucket} 2>&1 >/dev/null)" == *"Bucket not found"* ]]; then + echo "Creating bucket ${lib.escapeShellArg bucket}" + garage bucket create ${lib.escapeShellArg bucket} + else + echo "Bucket ${lib.escapeShellArg bucket} already exists" + fi + '') + cfg.buckets} + + # Remove buckets that shouldn't exist + for bucket in $existing_buckets; do + should_exist=false + ${lib.concatMapStringsSep "\n" (bucket: '' + if [[ "$bucket" == ${lib.escapeShellArg bucket} ]]; then + should_exist=true + fi + '') + cfg.buckets} + + if [[ "$should_exist" == "false" ]]; then + echo "Removing bucket $bucket" + garage bucket delete --yes "$bucket" + fi + done + ''; + }; + + garage-keys = { + description = "Create Garage keys and set permissions"; + after = [ "garage-buckets.service" ]; + wants = [ "garage-buckets.service" ]; + requires = [ "garage-buckets.service" ]; + wantedBy = [ "multi-user.target" ]; + + path = [ cfg.package pkgs.gawk pkgs.coreutils ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "root"; + Group = "root"; + }; + + script = '' + garage key list + echo "Managing keys..." + + # Get list of existing keys + existing_keys=$(garage key list | tail -n +2 | awk '{print $3}' | grep -v '^$' || true) + + # Create keys that should exist + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (keyName: _: '' + if [[ "$(garage key info ${lib.escapeShellArg keyName} 2>&1)" == *"0 matching keys"* ]]; then + echo "Creating key ${lib.escapeShellArg keyName}" + garage key create ${lib.escapeShellArg keyName} + else + echo "Key ${lib.escapeShellArg keyName} already exists" + fi + '') + cfg.keys)} + + # Set up key permissions for buckets + ${lib.concatStringsSep "\n" (lib.mapAttrsToList ( + keyName: buckets: + lib.concatMapStringsSep "\n" (bucket: '' + echo "Granting full access to key ${lib.escapeShellArg keyName} for bucket ${lib.escapeShellArg bucket}" + garage bucket allow --read --write --owner --key ${lib.escapeShellArg keyName} ${lib.escapeShellArg bucket} + '') + buckets + ) + cfg.keys)} + + # Remove permissions from buckets that are no longer associated with keys + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (keyName: buckets: '' + # Get current buckets this key has access to + current_buckets=$(garage key info ${lib.escapeShellArg keyName} | grep -A 1000 "==== BUCKETS FOR THIS KEY ====" | tail -n +3 | awk '{print $3}' | grep -v '^$' || true) + + # Remove access from buckets not in the desired list + for current_bucket in $current_buckets; do + should_have_access=false + ${lib.concatMapStringsSep "\n" (bucket: '' + if [[ "$current_bucket" == ${lib.escapeShellArg bucket} ]]; then + should_have_access=true + fi + '') + buckets} + + if [[ "$should_have_access" == "false" ]]; then + echo "Removing access for key ${lib.escapeShellArg keyName} from bucket $current_bucket" + garage bucket deny --key ${lib.escapeShellArg keyName} $current_bucket + fi + done + '') + cfg.keys)} + + # Remove keys that shouldn't exist + for key in $existing_keys; do + should_exist=false + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (keyName: _: '' + if [[ "$key" == ${lib.escapeShellArg keyName} ]]; then + should_exist=true + fi + '') + cfg.keys)} + + if [[ "$should_exist" == "false" ]]; then + echo "Removing key $key" + garage key delete --yes "$key" + fi + done + ''; + }; + }; + + nodes = + let + genNginx = toAddress: extraConfig: { + upstreams = { + ${serviceName} = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + "${serviceName}Web" = { + servers = { + "${toAddress}:${builtins.toString garageWebPort}" = { }; + }; + }; + "${serviceName}Admin" = { + servers = { + "${toAddress}:${builtins.toString garageAdminPort}" = { }; + }; + }; + }; + virtualHosts = { + "${adminDomain}" = { + useACMEHost = globals.domains.main; + forceSSL = true; + acmeRoot = null; + oauth2.enable = false; + inherit extraConfig; + locations = { + "/" = { + proxyPass = "http://${serviceName}Admin"; + }; + }; + }; + "*.${webDomain}" = { + useACMEHost = globals.domains.main; + forceSSL = true; + acmeRoot = null; + oauth2.enable = false; + inherit extraConfig; + locations = { + "/" = { + proxyPass = "http://${serviceName}Web"; + }; + }; + }; + "${serviceDomain}" = { + serverAliases = [ "*.${serviceDomain}" ]; + useACMEHost = globals.domains.main; + forceSSL = true; + acmeRoot = null; + oauth2.enable = false; + inherit extraConfig; + locations = { + "/" = { + proxyPass = "http://${serviceName}"; + extraConfig = '' + client_max_body_size 0; + client_body_timeout 600s; + proxy_connect_timeout 600s; + proxy_send_timeout 600s; + proxy_read_timeout 600s; + proxy_request_buffering off; + ''; + }; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${baseDomain}.subdomainRecords = { + "${subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + "${subDomain}-admin" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + "${subDomain}-web" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + "*.${subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + "*.${subDomain}-web" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (genNginx homeServiceAddress nginxAccessRules); + }; + + }; +} diff --git a/modules-clone/nixos/server/homebox.nix b/modules-clone/nixos/server/homebox.nix new file mode 100644 index 0000000..46f19c4 --- /dev/null +++ b/modules-clone/nixos/server/homebox.nix @@ -0,0 +1,67 @@ +{ self, lib, pkgs, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "homebox"; port = 7745; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + topology.self.services.${serviceName} = { + name = "Homebox"; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + swarselmodules.server = { + postgresql = true; + }; + + users.persistentIds = { + homebox = confLib.mkIds 981; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services.${serviceName} = { + enable = true; + package = pkgs.bisect.homebox; + database.createLocally = true; + settings = { + HBOX_WEB_PORT = builtins.toString servicePort; + HBOX_OPTIONS_ALLOW_REGISTRATION = "false"; + HBOX_STORAGE_CONN_STRING = "file:///var/lib/homebox"; + HBOX_STORAGE_PREFIX_PATH = ".data"; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/hydra.nix b/modules-clone/nixos/server/hydra.nix new file mode 100644 index 0000000..ddb3a2f --- /dev/null +++ b/modules-clone/nixos/server/hydra.nix @@ -0,0 +1,130 @@ +{ inputs, lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "hydra"; port = 8002; }) serviceName servicePort serviceUser serviceGroup serviceAddress serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + inherit (config.swarselsystems) sopsFile; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + sops = { + secrets = { + nixbuild-net-key = { mode = "0600"; }; + hydra-pw = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + templates = { + "hydra-env" = { + content = '' + HYDRA_PW="${config.sops.placeholder.hydra-pw}" + ''; + owner = serviceUser; + group = serviceGroup; + mode = "0440"; + }; + }; + }; + + services.hydra = { + enable = true; + package = inputs.hydra.packages.${config.node.arch}.hydra; + port = servicePort; + hydraURL = "https://${serviceDomain}"; + listenHost = "*"; + notificationSender = "hydra@${globals.domains.main}"; + minimumDiskFreeEvaluator = 20; # 20G + minimumDiskFree = 20; # 20G + useSubstitutes = true; + smtpHost = globals.services.mailserver.domain; + buildMachinesFiles = [ + "/etc/nix/machines" + ]; + extraConfig = '' + using_frontend_proxy 1 + ''; + }; + + systemd.services.hydra-user-setup = { + description = "Create admin user for Hydra"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "hydra"; + EnvironmentFile = [ + config.sops.templates.hydra-env.path + ]; + }; + wantedBy = [ "multi-user.target" ]; + requires = [ "hydra-init.service" ]; + after = [ "hydra-init.service" ]; + environment = lib.mkForce config.systemd.services.hydra-init.environment; + script = '' + set -eu + if [ ! -e ~hydra/.user-setup-done ]; then + /run/current-system/sw/bin/hydra-create-user admin --full-name 'admin' --email-address 'admin@${globals.domains.main}' --password "$HYDRA_PW" --role admin + touch ~hydra/.user-setup-done + fi + ''; + }; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + ]; + + nix = { + settings.builders-use-substitutes = true; + distributedBuilds = true; + buildMachines = [ + { + hostName = "localhost"; + protocol = null; + system = config.node.arch; + supportedFeatures = [ "kvm" "nixos-test" "big-parallel" "benchmark" ]; + maxJobs = 4; + } + ]; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + programs.ssh = { + extraConfig = '' + StrictHostKeyChecking no + ''; + }; + + nodes = + let + extraConfigLoc = '' + proxy_set_header X-Request-Base /hydra; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/id.nix b/modules-clone/nixos/server/id.nix new file mode 100644 index 0000000..fa67280 --- /dev/null +++ b/modules-clone/nixos/server/id.nix @@ -0,0 +1,105 @@ +{ lib, config, confLib, ... }: +let + inherit (lib) + concatLists + flip + mapAttrsToList + mkDefault + mkIf + mkOption + types + ; + + cfg = config.users.persistentIds; +in +{ + options = { + swarselmodules.server.ids = lib.mkEnableOption "enable persistent ids on server"; + users = { + persistentIds = mkOption { + default = { }; + description = '' + Maps a user or group name to its expected uid/gid values. If a user/group is + used on the system without specifying a uid/gid, this module will assign the + corresponding ids defined here, or show an error if the definition is missing. + ''; + type = types.attrsOf ( + types.submodule { + options = { + uid = mkOption { + type = types.nullOr types.int; + default = null; + description = "The uid to assign if it is missing in `users.users.`."; + }; + gid = mkOption { + type = types.nullOr types.int; + default = null; + description = "The gid to assign if it is missing in `users.groups.`."; + }; + }; + } + ); + }; + + users = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + config.uid = + let + persistentUid = cfg.${name}.uid or null; + in + mkIf (persistentUid != null) (mkDefault persistentUid); + } + ) + ); + }; + + groups = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + config.gid = + let + persistentGid = cfg.${name}.gid or null; + in + mkIf (persistentGid != null) (mkDefault persistentGid); + } + ) + ); + }; + }; + }; + config = lib.mkIf config.swarselmodules.server.ids { + assertions = + concatLists + ( + flip mapAttrsToList config.users.users ( + name: user: [ + { + assertion = user.uid != null; + message = "non-persistent uid detected for '${name}', please assign one via `users.persistentIds`"; + } + { + assertion = !user.autoSubUidGidRange; + message = "non-persistent subUids/subGids detected for: ${name}"; + } + ] + ) + ) + ++ flip mapAttrsToList config.users.groups ( + name: group: { + assertion = group.gid != null; + message = "non-persistent gid detected for '${name}', please assign one via `users.persistentIds`"; + } + ); + users.persistentIds = { + systemd-coredump = confLib.mkIds 998; + systemd-oom = confLib.mkIds 997; + polkituser = confLib.mkIds 973; + nscd = confLib.mkIds 972; + }; + }; +} diff --git a/modules-clone/nixos/server/immich.nix b/modules-clone/nixos/server/immich.nix new file mode 100644 index 0000000..3772b59 --- /dev/null +++ b/modules-clone/nixos/server/immich.nix @@ -0,0 +1,85 @@ +{ lib, pkgs, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "immich"; port = 3001; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + swarselmodules.server = { + postgresql = true; + }; + + users = { + persistentIds = { + immich = confLib.mkIds 989; + redis-immich = confLib.mkIds 977; + }; + users.${serviceUser} = { + extraGroups = [ "video" "render" "users" ]; + }; + }; + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/cache/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/lib/redis-${serviceName}"; user = "redis-${serviceUser}"; group = "redis-${serviceGroup}"; } + ]; + }; + + services.${serviceName} = { + enable = true; + package = pkgs.immich; + host = "0.0.0.0"; + port = servicePort; + # openFirewall = true; + mediaLocation = "/storage/Pictures/${serviceName}"; # dataDir + environment = { + IMMICH_MACHINE_LEARNING_URL = lib.mkForce "http://localhost:3003"; + }; + }; + + nodes = + let + extraConfigLoc = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_redirect off; + + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/jellyfin.nix b/modules-clone/nixos/server/jellyfin.nix new file mode 100644 index 0000000..6627a68 --- /dev/null +++ b/modules-clone/nixos/server/jellyfin.nix @@ -0,0 +1,70 @@ +{ pkgs, lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "jellyfin"; port = 8096; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf nginxAccessRules homeServiceAddress; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds.jellyfin = confLib.mkIds 994; + users.${serviceUser} = { + extraGroups = [ "video" "render" "users" ]; + }; + }; + # nixpkgs.config.packageOverrides = pkgs: { + # intel-vaapi-driver = pkgs.intel-vaapi-driver.override { enableHybridCodec = true; }; + # }; + + hardware.graphics = { + enable = true; + extraPackages = with pkgs; [ + intel-media-driver # LIBVA_DRIVER_NAME=iHD + # intel-vaapi-driver # LIBVA_DRIVER_NAME=i965 (older but works better for Firefox/Chromium) + libva-vdpau-driver + libvdpau-va-gl + ]; + }; + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.${serviceName} = { + enable = true; + user = serviceUser; + # openFirewall = true; # this works only for the default ports + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/cache/${serviceName}"; user = serviceUser; group = serviceGroup; } + ]; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/jenkins.nix b/modules-clone/nixos/server/jenkins.nix new file mode 100644 index 0000000..cda5315 --- /dev/null +++ b/modules-clone/nixos/server/jenkins.nix @@ -0,0 +1,48 @@ +{ pkgs, lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "jenkins"; port = 8088; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services.jenkins = { + enable = true; + withCLI = true; + port = servicePort; + packages = [ pkgs.stdenv pkgs.git pkgs.jdk17 config.programs.ssh.package pkgs.nix ]; + listenAddress = "0.0.0.0"; + home = "/var/lib/${serviceName}"; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/kanidm.nix b/modules-clone/nixos/server/kanidm.nix new file mode 100644 index 0000000..c61b115 --- /dev/null +++ b/modules-clone/nixos/server/kanidm.nix @@ -0,0 +1,436 @@ +{ self, lib, pkgs, config, globals, dns, confLib, ... }: +let + certsSopsFile = self + /secrets/repo/certs.yaml; + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "kanidm"; port = 8300; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy homeProxyIf webProxyIf dnsServer homeServiceAddress nginxAccessRules; + + oauth2ProxyDomain = globals.services.oauth2-proxy.domain; + immichDomain = globals.services.immich.domain; + paperlessDomain = globals.services.paperless.domain; + forgejoDomain = globals.services.forgejo.domain; + grafanaDomain = globals.services.grafana.domain; + nextcloudDomain = globals.services.nextcloud.domain; + + certBase = "/etc/ssl"; + certsDir = "${certBase}/certs"; + privateDir = "${certBase}/private"; + certPathBase = "${certsDir}/${serviceName}.crt"; + certPath = + if config.swarselsystems.isImpermanence then + "/persist${certPathBase}" + else + "${certPathBase}"; + keyPathBase = "${privateDir}/${serviceName}.key"; + keyPath = + if config.swarselsystems.isImpermanence then + "/persist${keyPathBase}" + else + "${keyPathBase}"; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + + users = { + persistentIds = { + kanidm = confLib.mkIds 984; + }; + users.${serviceUser} = { + group = serviceGroup; + isSystemUser = true; + }; + + groups.${serviceGroup} = { }; + }; + + sops = { + secrets = { + "kanidm-self-signed-crt" = { sopsFile = certsSopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-self-signed-key" = { sopsFile = certsSopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-admin-pw" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-idm-admin-pw" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-immich" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-paperless" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-forgejo" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-grafana" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-nextcloud" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-freshrss" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-oauth2-proxy" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + globals = { + general.idmServer = config.node.name; + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence = { + "/persist" = lib.mkIf config.swarselsystems.isImpermanence { + files = [ + certPathBase + keyPathBase + ]; + }; + + "/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + }; + + systemd.services = { + "generateSSLCert-${serviceName}" = + let + daysValid = 3650; + renewBeforeDays = 365; + in + { + before = [ "${serviceName}.service" ]; + requiredBy = [ "${serviceName}.service" ]; + after = [ "local-fs.target" ]; + requires = [ "local-fs.target" ]; + + serviceConfig = { + Type = "oneshot"; + }; + + script = '' + set -eu + + ${pkgs.coreutils}/bin/install -d -m 0755 ${certsDir} + ${if config.swarselsystems.isImpermanence then "${pkgs.coreutils}/bin/install -d -m 0755 /persist${certsDir}" else ""} + ${pkgs.coreutils}/bin/install -d -m 0750 ${privateDir} + ${if config.swarselsystems.isImpermanence then "${pkgs.coreutils}/bin/install -d -m 0750 /persist${privateDir}" else ""} + + need_gen=0 + if [ ! -f "${certPath}" ] || [ ! -f "${keyPath}" ]; then + need_gen=1 + else + enddate="$(${pkgs.openssl}/bin/openssl x509 -noout -enddate -in "${certPath}" | cut -d= -f2)" + end_epoch="$(${pkgs.coreutils}/bin/date -d "$enddate" +%s)" + now_epoch="$(${pkgs.coreutils}/bin/date +%s)" + seconds_left=$(( end_epoch - now_epoch )) + days_left=$(( seconds_left / 86400 )) + if [ "$days_left" -lt ${toString renewBeforeDays} ]; then + need_gen=1 + else + echo 'Certificate exists and is still valid' + fi + fi + + if [ "$need_gen" -eq 1 ]; then + ${pkgs.openssl}/bin/openssl req -x509 -nodes -days ${toString daysValid} -newkey rsa:4096 -sha256 \ + -keyout "${keyPath}" \ + -out "${certPath}" \ + -subj "/CN=${serviceDomain}" \ + -addext "subjectAltName=DNS:${serviceDomain}" + + chmod 0644 "${certPath}" + chmod 0600 "${keyPath}" + chown ${serviceUser}:${serviceGroup} "${certPath}" "${keyPath}" + fi + ''; + }; + kanidm = { + environment.KANIDM_TRUST_X_FORWARD_FOR = "true"; + serviceConfig.RestartSec = "30"; + }; + }; + + + + # system.activationScripts."createPersistentStorageDirs" = lib.mkIf config.swarselsystems.isImpermanence { + # deps = [ "generateSSLCert-${serviceName}" "users" "groups" ]; + # }; + # system.activationScripts."generateSSLCert-${serviceName}" = + # let + # daysValid = 3650; + # renewBeforeDays = 365; + # in + # { + # text = '' + # set -eu + + # ${pkgs.coreutils}/bin/install -d -m 0755 ${certsDir} + # ${if config.swarselsystems.isImpermanence then "${pkgs.coreutils}/bin/install -d -m 0755 /persist${certsDir}" else ""} + # ${pkgs.coreutils}/bin/install -d -m 0750 ${privateDir} + # ${if config.swarselsystems.isImpermanence then "${pkgs.coreutils}/bin/install -d -m 0750 /persist${privateDir}" else ""} + + # need_gen=0 + # if [ ! -f "${certPathBase}" ] || [ ! -f "${keyPathBase}" ]; then + # need_gen=1 + # else + # enddate="$(${pkgs.openssl}/bin/openssl x509 -noout -enddate -in "${certPathBase}" | cut -d= -f2)" + # end_epoch="$(${pkgs.coreutils}/bin/date -d "$enddate" +%s)" + # now_epoch="$(${pkgs.coreutils}/bin/date +%s)" + # seconds_left=$(( end_epoch - now_epoch )) + # days_left=$(( seconds_left / 86400 )) + # if [ "$days_left" -lt ${toString renewBeforeDays} ]; then + # need_gen=1 + # fi + # fi + + # if [ "$need_gen" -eq 1 ]; then + # ${pkgs.openssl}/bin/openssl req -x509 -nodes -days ${toString daysValid} -newkey rsa:4096 -sha256 \ + # -keyout "${keyPath}" \ + # -out "${certPath}" \ + # -subj "/CN=${serviceDomain}" \ + # -addext "subjectAltName=DNS:${serviceDomain}" + + # chmod 0644 "${certPath}" + # chmod 0600 "${keyPath}" + # chown ${serviceUser}:${serviceGroup} "${certPath}" "${keyPath}" + # fi + # ''; + # deps = [ + # "etc" + # (lib.mkIf config.swarselsystems.isImpermanence "specialfs") + # ]; + # }; + + services = { + ${serviceName} = { + package = pkgs.kanidmWithSecretProvisioning_1_9; + server = { + enable = true; + settings = { + domain = serviceDomain; + origin = "https://${serviceDomain}"; + # tls_chain = config.sops.secrets.kanidm-self-signed-crt.path; + tls_chain = certPathBase; + # tls_key = config.sops.secrets.kanidm-self-signed-key.path; + tls_key = keyPathBase; + bindaddress = "0.0.0.0:${toString servicePort}"; + # trust_x_forward_for = true; + }; + }; + client = { + enable = true; + settings = { + uri = config.services.kanidm.server.settings.origin; + verify_ca = true; + verify_hostnames = true; + }; + }; + provision = { + enable = true; + adminPasswordFile = config.sops.secrets.kanidm-admin-pw.path; + idmAdminPasswordFile = config.sops.secrets.kanidm-idm-admin-pw.path; + groups = { + "immich.access" = { }; + "paperless.access" = { }; + "forgejo.access" = { }; + "forgejo.admins" = { }; + "grafana.access" = { }; + "grafana.editors" = { }; + "grafana.admins" = { }; + "grafana.server-admins" = { }; + "nextcloud.access" = { }; + "nextcloud.admins" = { }; + "navidrome.access" = { }; + "freshrss.access" = { }; + "firefly.access" = { }; + "radicale.access" = { }; + "slink.access" = { }; + "opkssh.access" = { }; + "adguardhome.access" = { }; + }; + + inherit (config.repo.secrets.local) persons; + + systems = { + oauth2 = { + immich = { + displayName = "Immich"; + originUrl = [ + "https://${immichDomain}/auth/login" + "https://${immichDomain}/user-settings" + "app.immich:///oauth-callback" + "https://${immichDomain}/api/oauth/mobile-redirect" + ]; + originLanding = "https://${immichDomain}/"; + basicSecretFile = config.sops.secrets.kanidm-immich.path; + preferShortUsername = true; + enableLegacyCrypto = true; # can use RS256 / HS256, not ES256 + scopeMaps."immich.access" = [ + "openid" + "email" + "profile" + ]; + }; + paperless = { + displayName = "Paperless"; + originUrl = "https://${paperlessDomain}/accounts/oidc/kanidm/login/callback/"; + originLanding = "https://${paperlessDomain}/"; + basicSecretFile = config.sops.secrets.kanidm-paperless.path; + preferShortUsername = true; + scopeMaps."paperless.access" = [ + "openid" + "email" + "profile" + ]; + }; + forgejo = { + displayName = "Forgejo"; + originUrl = "https://${forgejoDomain}/user/oauth2/kanidm/callback"; + originLanding = "https://${forgejoDomain}/"; + basicSecretFile = config.sops.secrets.kanidm-forgejo.path; + scopeMaps."forgejo.access" = [ + "openid" + "email" + "profile" + ]; + # XXX: PKCE is currently not supported by gitea/forgejo, + # see https://github.com/go-gitea/gitea/issues/21376. + allowInsecureClientDisablePkce = true; + preferShortUsername = true; + claimMaps.groups = { + joinType = "array"; + valuesByGroup."forgejo.admins" = [ "admin" ]; + }; + }; + grafana = { + displayName = "Grafana"; + originUrl = "https://${grafanaDomain}/login/generic_oauth"; + originLanding = "https://${grafanaDomain}/"; + basicSecretFile = config.sops.secrets.kanidm-grafana.path; + preferShortUsername = true; + scopeMaps."grafana.access" = [ + "openid" + "email" + "profile" + ]; + claimMaps.groups = { + joinType = "array"; + valuesByGroup = { + "grafana.editors" = [ "editor" ]; + "grafana.admins" = [ "admin" ]; + "grafana.server-admins" = [ "server_admin" ]; + }; + }; + }; + nextcloud = { + displayName = "Nextcloud"; + originUrl = " https://${nextcloudDomain}/apps/sociallogin/custom_oidc/kanidm"; + originLanding = "https://${nextcloudDomain}/"; + basicSecretFile = config.sops.secrets.kanidm-nextcloud.path; + allowInsecureClientDisablePkce = true; + scopeMaps."nextcloud.access" = [ + "openid" + "email" + "profile" + ]; + preferShortUsername = true; + claimMaps.groups = { + joinType = "array"; + valuesByGroup = { + "nextcloud.admins" = [ "admin" ]; + }; + }; + }; + opkssh = { + displayName = "OPKSSH"; + originUrl = [ + "http://localhost:3000" + "http://localhost:3000/login-callback" + "http://localhost:10001/login-callback" + "http://localhost:11110/login-callback" + ]; + originLanding = "http://localhost:3000"; + public = true; + enableLocalhostRedirects = true; + scopeMaps."opkssh.access" = [ + "openid" + "email" + "profile" + ]; + }; + oauth2-proxy = { + displayName = "Oauth2-Proxy"; + originUrl = "https://${oauth2ProxyDomain}/oauth2/callback"; + originLanding = "https://${oauth2ProxyDomain}/"; + basicSecretFile = config.sops.secrets.kanidm-oauth2-proxy.path; + scopeMaps = { + "freshrss.access" = [ + "openid" + "email" + "profile" + ]; + "navidrome.access" = [ + "openid" + "email" + "profile" + ]; + "firefly.access" = [ + "openid" + "email" + "profile" + ]; + "radicale.access" = [ + "openid" + "email" + "profile" + ]; + "slink.access" = [ + "openid" + "email" + "profile" + ]; + "adguardhome.access" = [ + "openid" + "email" + "profile" + ]; + }; + preferShortUsername = true; + claimMaps.groups = { + joinType = "array"; + valuesByGroup = { + "freshrss.access" = [ "ttrss_access" ]; + "navidrome.access" = [ "navidrome_access" ]; + "firefly.access" = [ "firefly_access" ]; + "radicale.access" = [ "radicale_access" ]; + "slink.access" = [ "slink_access" ]; + "adguardhome.access" = [ "adguardhome_access" ]; + }; + }; + }; + }; + }; + }; + }; + }; + + + nodes = + let + extraConfig = '' + allow ${globals.networks.home-lan.vlans.services.cidrv4}; + allow ${globals.networks.home-lan.vlans.services.cidrv6}; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; protocol = "https"; noSslVerify = true; }; + ${homeWebProxy}.services.nginx = confLib.genNginx { inherit servicePort serviceDomain serviceName; protocol = "https"; noSslVerify = true; extraConfig = extraConfig + nginxAccessRules; serviceAddress = homeServiceAddress; }; + }; + + }; +} diff --git a/modules-clone/nixos/server/kavita.nix b/modules-clone/nixos/server/kavita.nix new file mode 100644 index 0000000..b2d3d8f --- /dev/null +++ b/modules-clone/nixos/server/kavita.nix @@ -0,0 +1,62 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + + inherit (confLib.gen { name = "kavita"; port = 8080; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf nginxAccessRules homeServiceAddress; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds.kavita = confLib.mkIds 995; + users.${serviceUser} = { + extraGroups = [ "users" ]; + }; + }; + + + sops.secrets.kavita-token = { inherit sopsFile; owner = serviceUser; }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.${serviceName} = { + enable = true; + user = serviceUser; + settings.Port = servicePort; + tokenKeyFile = config.sops.secrets.kavita-token.path; + dataDir = "/var/lib/${serviceName}"; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/kea.nix b/modules-clone/nixos/server/kea.nix new file mode 100644 index 0000000..21c4228 --- /dev/null +++ b/modules-clone/nixos/server/kea.nix @@ -0,0 +1,101 @@ +{ self, lib, config, globals, confLib, ... }: +let + inherit (confLib.gen { name = "kea"; dir = "/var/lib/private/kea"; }) serviceName serviceDir; + inherit (confLib.static) homeDnsServer; + dhcpX = intX: + let + x = builtins.toString intX; + in + { + enable = true; + settings = { + reservations-out-of-pool = true; + lease-database = { + name = "/var/lib/kea/dhcp${x}.leases"; + persist = true; + type = "memfile"; + }; + valid-lifetime = 86400; + renew-timer = 3600; + interfaces-config = { + interfaces = map (name: "me-${name}") (builtins.attrNames globals.networks.home-lan.vlans); + service-sockets-max-retries = -1; + }; + "subnet${x}" = lib.flip lib.mapAttrsToList globals.networks.home-lan.vlans ( + vlanName: vlanCfg: { + inherit (vlanCfg) id; + interface = "me-${vlanName}"; + subnet = vlanCfg."cidrv${x}"; + rapid-commit = lib.mkIf (intX == 6) true; + pools = [ + { + pool = "${lib.net.cidr.host 100 vlanCfg."cidrv${x}"} - ${lib.net.cidr.host (-6) vlanCfg."cidrv${x}"}"; + } + ]; + pd-pools = lib.mkIf (intX == 6) [ + { + prefix = builtins.replaceStrings [ "::" ] [ ":0:0:100::" ] (lib.head (lib.splitString "/" vlanCfg.cidrv6)); + prefix-len = 56; + delegated-len = 64; + } + ]; + option-data = + lib.optional (intX == 4) + { + name = "routers"; + data = vlanCfg.hosts.hintbooth."ipv${x}"; + } + # Advertise DNS server for VLANS that have internet access + ++ + lib.optional + (lib.elem vlanName globals.general.internetVLANs) + { + name = if (intX == 4) then "domain-name-servers" else "dns-servers"; + data = globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}."ipv${x}"; + }; + reservations = lib.concatLists ( + lib.forEach (builtins.attrValues vlanCfg.hosts) ( + hostCfg: + lib.optional (hostCfg.mac != null) { + hw-address = lib.mkIf (intX == 4) hostCfg.mac; + duid = lib.mkIf (intX == 6) "00:03:00:01:${hostCfg.mac}"; # 00:03 = duid type 3; 00:01 = ethernet + ip-address = lib.mkIf (intX == 4) hostCfg."ipv${x}"; + ip-addresses = lib.mkIf (intX == 6) [ hostCfg."ipv${x}" ]; + prefixes = lib.mkIf (intX == 6) [ + "${builtins.replaceStrings ["::"] [":0:0:${builtins.toString (256 + hostCfg.id)}::"] (lib.head (lib.splitString "/" vlanCfg.cidrv6))}/64" + ]; + } + ) + ); + } + ); + }; + }; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = serviceDir; mode = "0700"; } + ]; + + users.persistentIds.kea = confLib.mkIds 968; + + topology = { + extractors.kea.enable = false; + self.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + }; + + services.kea = { + dhcp4 = dhcpX 4; + dhcp6 = dhcpX 6; + }; + + }; +} diff --git a/modules-clone/nixos/server/koillection.nix b/modules-clone/nixos/server/koillection.nix new file mode 100644 index 0000000..cae571b --- /dev/null +++ b/modules-clone/nixos/server/koillection.nix @@ -0,0 +1,145 @@ +{ self, lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "koillection"; port = 2282; dir = "/var/lib/koillection"; }) servicePort serviceName serviceUser serviceDir serviceDomain serviceAddress proxyAddress4 proxyAddress6 topologyContainerName; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + serviceDB = "koillection"; + + postgresUser = config.systemd.services.postgresql.serviceConfig.User; # postgres + postgresPort = config.services.postgresql.settings.port; # 5432 + containerRev = "sha256:bb8ad2b6891441d8ec5c3169b684b71574f3bb3e9afb345bad2f91d833d60340"; + + inherit (config.swarselsystems) sopsFile; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + swarselmodules.server = { + podman = true; + postgresql = true; + }; + + sops.secrets = { + koillection-db-password = { inherit sopsFile; owner = postgresUser; group = postgresUser; mode = "0440"; }; + koillection-env-file = { inherit sopsFile; }; + }; + + topology.nodes.${topologyContainerName}.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort postgresPort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort postgresPort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; }]; + }; + + virtualisation.oci-containers.containers = { + koillection = { + image = "koillection/koillection@${containerRev}"; + + ports = [ + "${toString servicePort}:80" + ]; + + volumes = [ + "${serviceDir}/uploads:/uploads" + ]; + + environment = { + APP_DEBUG = "0"; + APP_ENV = "prod"; + HTTPS_ENABLED = "1"; + UPLOAD_MAX_FILESIZE = "512M"; + PHP_MEMORY_LIMIT = "512M"; + PHP_TZ = config.repo.secrets.common.location.timezone; + + CORS_ALLOW_ORIGIN = "https?://(localhost|swag\\.swarsel\\.win)(:[0-9]+)?$"; + + DB_DRIVER = "pdo_pgsql"; + DB_NAME = serviceDB; + DB_HOST = "host.docker.internal"; + DB_USER = serviceUser; + # DB_PASSWORD set in koillection-env-file + DB_PORT = "${toString postgresPort}"; + DB_VERSION = "16"; + }; + + environmentFiles = [ + config.sops.secrets.koillection-env-file.path + ]; + + extraOptions = [ + "--add-host=host.docker.internal:host-gateway" # podman + ]; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort postgresPort ]; + + systemd.services.postgresql.postStart = + let + passwordPath = config.sops.secrets.koillection-db-password.path; + in + '' + ${config.services.postgresql.package}/bin/psql -tA <<'EOF' + DO $$ + DECLARE password TEXT; + BEGIN + password := trim(both from replace(pg_read_file('${passwordPath}'), E'\n', ''')); + EXECUTE format('ALTER ROLE ${serviceDB} WITH PASSWORD '''%s''';', password); + END $$; + EOF + ''; + services = { + postgresql = { + enable = true; + enableTCPIP = true; + ensureDatabases = [ serviceDB ]; + ensureUsers = [ + { + name = serviceDB; + ensureDBOwnership = true; + } + ]; + authentication = '' + host ${serviceDB} ${serviceDB} 10.88.0.0/16 scram-sha-256 + ''; + }; + }; + + nodes = + let + extraConfigLoc = '' + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + + }; +} diff --git a/modules-clone/nixos/server/mailserver.nix b/modules-clone/nixos/server/mailserver.nix new file mode 100644 index 0000000..255b60e --- /dev/null +++ b/modules-clone/nixos/server/mailserver.nix @@ -0,0 +1,166 @@ +{ self, lib, config, globals, dns, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "mailserver"; dir = "/var/lib/dovecot"; user = "virtualMail"; group = "virtualMail"; port = 443; }) serviceName serviceDir servicePort serviceUser serviceGroup serviceAddress serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome webProxy homeWebProxy dnsServer homeServiceAddress nginxAccessRules; + inherit (config.repo.secrets.local.mailserver) user1 alias1_1 alias1_2 alias1_3 alias1_4 user2 alias2_1 alias2_2 alias2_3 user3; + baseDomain = globals.domains.main; + + roundcubeDomain = config.repo.secrets.common.services.domains.roundcube; + endpointAddress4 = globals.hosts.${config.node.name}.wanAddress4 or null; + endpointAddress6 = globals.hosts.${config.node.name}.wanAddress6 or null; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds = { + knot-resolver = confLib.mkIds 963; + postfix-tlspol = confLib.mkIds 962; + roundcube = confLib.mkIds 961; + redis-rspamd = confLib.mkIds 960; + }; + }; + + globals.services = { + ${serviceName} = { + domain = serviceDomain; + proxyAddress4 = endpointAddress4; + proxyAddress6 = endpointAddress6; + }; + roundcube = { + domain = roundcubeDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + topology.self.services = lib.listToAttrs (map + (service: + lib.nameValuePair "${service}" { + name = lib.swarselsystems.toCapitalized service; + info = lib.mkIf (service == "postfix" || service == "roundcube") (if service == "postfix" then "https://${serviceDomain}" else "https://${roundcubeDomain}"); + icon = "${self}/files/topology-images/${service}.png"; + } + ) + [ "postfix" "dovecot" "rspamd" "clamav" "roundcube" ]); + + sops.secrets = { + user1-hashed-pw = { inherit sopsFile; owner = serviceUser; }; + user2-hashed-pw = { inherit sopsFile; owner = serviceUser; }; + user3-hashed-pw = { inherit sopsFile; owner = serviceUser; }; + }; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = "/var/vmail"; user = serviceUser; group = serviceGroup; mode = "0770"; } + { directory = "/var/sieve"; user = serviceUser; group = serviceGroup; mode = "0770"; } + { directory = "/var/dkim"; user = "rspamd"; group = "rspamd"; mode = "0700"; } + { directory = serviceDir; user = serviceUser; group = serviceGroup; mode = "0700"; } + # { directory = "/var/lib/postgresql"; user = "postgres"; group = "postgres"; mode = "0750"; } + { directory = "/var/lib/rspamd"; user = "rspamd"; group = "rspamd"; mode = "0700"; } + { directory = "/var/lib/roundcube"; user = "roundcube"; group = "roundcube"; mode = "0700"; } + { directory = "/var/lib/redis-rspamd"; user = "redis-rspamd"; group = "redis-rspamd"; mode = "0700"; } + { directory = "/var/lib/postfix"; user = "root"; group = "root"; mode = "0755"; } + { directory = "/var/lib/knot-resolver"; user = "knot-resolver"; group = "knot-resolver"; mode = "0770"; } + ]; + + mailserver = { + enable = true; + stateVersion = 3; + fqdn = serviceDomain; + domains = [ baseDomain ]; + indexDir = "${serviceDir}/indices"; + openFirewall = true; + # certificateScheme = "acme"; + dmarcReporting.enable = true; + enableSubmission = true; + enableSubmissionSsl = true; + enableImapSsl = true; + x509.useACMEHost = globals.domains.main; + + loginAccounts = { + "${user1}@${baseDomain}" = { + hashedPasswordFile = config.sops.secrets.user1-hashed-pw.path; + aliases = [ + "${alias1_1}@${baseDomain}" + "${alias1_2}@${baseDomain}" + "${alias1_3}@${baseDomain}" + "${alias1_4}@${baseDomain}" + ]; + }; + "${user2}@${baseDomain}" = { + hashedPasswordFile = config.sops.secrets.user2-hashed-pw.path; + aliases = [ + "${alias2_1}@${baseDomain}" + "${alias2_2}@${baseDomain}" + "${alias2_3}@${baseDomain}" + ]; + sendOnly = true; + }; + "${user3}@${baseDomain}" = { + hashedPasswordFile = config.sops.secrets.user3-hashed-pw.path; + aliases = [ + "@${baseDomain}" + ]; + catchAll = [ + baseDomain + ]; + }; + }; + }; + + services.roundcube = { + enable = true; + # this is the url of the vhost, not necessarily the same as the fqdn of + # the mailserver + hostName = roundcubeDomain; + extraConfig = '' + $config['imap_host'] = "ssl://${config.mailserver.fqdn}"; + $config['smtp_host'] = "ssl://${config.mailserver.fqdn}"; + $config['smtp_user'] = "%u"; + $config['smtp_pass'] = "%p"; + ''; + configureNginx = true; + }; + + # the rest of the ports are managed by snm + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + services.nginx = { + virtualHosts = { + "${roundcubeDomain}" = { + useACMEHost = globals.domains.main; + enableACME = false; + forceSSL = true; + acmeRoot = null; + locations = { + "/".recommendedSecurityHeaders = false; + "~ ^/(SQL|bin|config|logs|temp|vendor)/".recommendedSecurityHeaders = false; + "~ ^/(CHANGELOG.md|INSTALL|LICENSE|README.md|SECURITY.md|UPGRADING|composer.json|composer.lock)".recommendedSecurityHeaders = false; + "~* \\.php(/|$)".recommendedSecurityHeaders = false; + }; + }; + }; + }; + + nodes = + let + extraConfigLoc = '' + proxy_ssl_server_name on; + proxy_ssl_name ${roundcubeDomain}; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host endpointAddress4 endpointAddress6; + "${globals.services.roundcube.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceName extraConfigLoc; serviceDomain = roundcubeDomain; protocol = "https"; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceName extraConfigLoc; serviceDomain = roundcubeDomain; protocol = "https"; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/matrix.nix b/modules-clone/nixos/server/matrix.nix new file mode 100644 index 0000000..f65c143 --- /dev/null +++ b/modules-clone/nixos/server/matrix.nix @@ -0,0 +1,390 @@ +{ lib, config, pkgs, globals, dns, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "matrix"; user = "matrix-synapse"; port = 8008; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + federationPort = 8448; + whatsappPort = 29318; + telegramPort = 29317; + signalPort = 29328; + baseUrl = "https://${serviceDomain}"; + clientConfig."m.homeserver".base_url = baseUrl; + serverConfig."m.server" = "${serviceDomain}:443"; + mkWellKnown = data: '' + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON data}'; + ''; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + swarselmodules.server = { + postgresql = true; + }; + + environment.systemPackages = with pkgs; [ + matrix-synapse + lottieconverter + ffmpeg + ]; + + sops = { + secrets = { + matrix-shared-secret = { inherit sopsFile; owner = serviceUser; }; + mautrix-telegram-as-token = { inherit sopsFile; owner = serviceUser; }; + mautrix-telegram-hs-token = { inherit sopsFile; owner = serviceUser; }; + mautrix-telegram-api-id = { inherit sopsFile; owner = serviceUser; }; + mautrix-telegram-api-hash = { inherit sopsFile; owner = serviceUser; }; + }; + templates = { + "matrix_user_register.sh".content = '' + register_new_matrix_user -k ${config.sops.placeholder.matrix-shared-secret} http://localhost:${builtins.toString servicePort} + ''; + matrixshared = { + owner = serviceUser; + content = '' + registration_shared_secret: ${config.sops.placeholder.matrix-shared-secret} + ''; + }; + mautrixtelegram = { + owner = serviceUser; + content = '' + MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN=${config.sops.placeholder.mautrix-telegram-as-token} + MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN=${config.sops.placeholder.mautrix-telegram-hs-token} + MAUTRIX_TELEGRAM_TELEGRAM_API_ID=${config.sops.placeholder.mautrix-telegram-api-id} + MAUTRIX_TELEGRAM_TELEGRAM_API_HASH=${config.sops.placeholder.mautrix-telegram-api-hash} + ''; + }; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort federationPort ]; + + systemd = { + timers."restart-bridges" = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "1d"; + OnUnitActiveSec = "1d"; + Unit = "restart-bridges.service"; + }; + }; + + services = { + "restart-bridges" = { + script = '' + systemctl restart mautrix-whatsapp.service + systemctl restart mautrix-signal.service + systemctl restart mautrix-telegram.service + ''; + serviceConfig = { + Type = "oneshot"; + User = "root"; + }; + }; + mautrix-telegram.path = with pkgs; [ + lottieconverter # for animated stickers conversion, unfree package + ffmpeg # if converting animated stickers to webm (very slow!) + ]; + }; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort federationPort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/matrix-synapse"; user = serviceUser; group = serviceGroup; } + { directory = "/var/lib/mautrix-whatsapp"; user = "mautrix-whatsapp"; group = "mautrix-whatsapp"; } + { directory = "/var/lib/mautrix-telegram"; user = "mautrix-telegram"; group = "mautrix-telegram"; } + { directory = "/var/lib/mautrix-signal"; user = "mautrix-signal"; group = "mautrix-signal"; } + ]; + }; + + + services = { + postgresql = { + initialScript = pkgs.writeText "synapse-init.sql" '' + CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; + CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + CREATE ROLE "mautrix-telegram" WITH LOGIN PASSWORD 'telegram'; + CREATE DATABASE "mautrix-telegram" WITH OWNER "mautrix-telegram" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + CREATE ROLE "mautrix-whatsapp" WITH LOGIN PASSWORD 'whatsapp'; + CREATE DATABASE "mautrix-whatsapp" WITH OWNER "mautrix-whatsapp" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + CREATE ROLE "mautrix-signal" WITH LOGIN PASSWORD 'signal'; + CREATE DATABASE "mautrix-signal" WITH OWNER "mautrix-signal" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + ''; + }; + + matrix-synapse = { + enable = true; + dataDir = "/var/lib/matrix-synapse"; + settings = { + app_service_config_files = + let + inherit (config.services.matrix-synapse) dataDir; + in + [ + "${dataDir}/telegram-registration.yaml" + "${dataDir}/whatsapp-registration.yaml" + "${dataDir}/signal-registration.yaml" + "${dataDir}/doublepuppet.yaml" + ]; + server_name = serviceDomain; + public_baseurl = "https://${serviceDomain}"; + listeners = [ + { + port = servicePort; + bind_addresses = [ + "0.0.0.0" + # "::1" + ]; + type = "http"; + tls = false; + x_forwarded = true; + resources = [ + { + names = [ "client" "federation" ]; + compress = true; + } + ]; + } + ]; + }; + extraConfigFiles = [ + config.sops.templates.matrixshared.path + ]; + }; + + mautrix-telegram = { + enable = true; + environmentFile = config.sops.templates.mautrixtelegram.path; + registerToSynapse = false; + settings = { + homeserver = { + address = "http://localhost:${builtins.toString servicePort}"; + domain = serviceDomain; + }; + appservice = { + address = "http://localhost:${builtins.toString telegramPort}"; + hostname = "0.0.0.0"; + port = telegramPort; + provisioning.enabled = true; + id = "telegram"; + # ephemeral_events = true; # not needed due to double puppeting + public = { + enabled = false; + }; + database = "postgresql:///mautrix-telegram?host=/run/postgresql"; + }; + bridge = { + relaybot.authless_portals = true; + allow_avatar_remove = true; + allow_contact_info = true; + sync_channel_members = true; + startup_sync = true; + sync_create_limit = 0; + sync_direct_chats = true; + telegram_link_preview = true; + permissions = { + "*" = "relaybot"; + "@swarsel:${serviceDomain}" = "admin"; + }; + animated_sticker = { + target = "gif"; + args = { + width = 256; + height = 256; + fps = 30; # only for webm + background = "020202"; # only for gif, transparency not supported + }; + }; + }; + }; + }; + + mautrix-whatsapp = { + enable = true; + registerToSynapse = false; + settings = { + homeserver = { + address = "http://localhost:${builtins.toString servicePort}"; + domain = serviceDomain; + }; + database = { + type = "postgres"; + uri = "postgresql:///mautrix-whatsapp?host=/run/postgresql"; + }; + appservice = { + address = "http://localhost:${builtins.toString whatsappPort}"; + hostname = "0.0.0.0"; + port = whatsappPort; + }; + bridge = { + displayname_template = "{{or .FullName .PushName .JID}} (WA)"; + history_sync = { + backfill = true; + max_initial_conversations = -1; + message_count = -1; + request_full_sync = true; + full_sync_config = { + days_limit = 900; + size_mb_limit = 5000; + storage_quota_mb = 5000; + }; + }; + login_shared_secret_map = { + ${serviceDomain} = "as_token:doublepuppet"; + }; + sync_manual_marked_unread = true; + send_presence_on_typing = true; + parallel_member_sync = true; + url_previews = true; + caption_in_message = true; + extev_polls = true; + permissions = { + "*" = "relay"; + "@swarsel:${serviceDomain}" = "admin"; + }; + }; + }; + }; + + mautrix-signal = { + enable = true; + registerToSynapse = false; + settings = { + homeserver = { + address = "http://localhost:${builtins.toString servicePort}"; + domain = serviceDomain; + }; + database = { + type = "postgres"; + uri = "postgresql:///mautrix-signal?host=/run/postgresql"; + }; + appservice = { + address = "http://localhost:${builtins.toString signalPort}"; + hostname = "0.0.0.0"; + port = signalPort; + }; + bridge = { + displayname_template = "{{or .ContactName .ProfileName .PhoneNumber}} (Signal)"; + login_shared_secret_map = { + ${serviceDomain} = "as_token:doublepuppet"; + }; + caption_in_message = true; + permissions = { + "*" = "relay"; + "@swarsel:${serviceDomain}" = "admin"; + }; + }; + }; + }; + }; + + # restart the bridges daily. this is done for the signal bridge mainly which stops carrying + # messages out after a while. + + users.persistentIds = { + mautrix-signal = confLib.mkIds 993; + mautrix-whatsapp = confLib.mkIds 992; + mautrix-telegram = confLib.mkIds 991; + }; + + nodes = + let + genNginx = toAddress: extraConfig: { + upstreams = { + ${serviceName} = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + + forceSSL = true; + acmeRoot = null; + listen = [ + { + addr = "0.0.0.0"; + port = 8448; + ssl = true; + extraParameters = [ + "default_server" + ]; + } + { + addr = "[::0]"; + port = 8448; + ssl = true; + extraParameters = [ + "default_server" + ]; + } + { + addr = "0.0.0.0"; + port = 443; + ssl = true; + } + { + addr = "[::0]"; + port = 443; + ssl = true; + } + ]; + inherit extraConfig; + locations = { + "~ ^(/_matrix|/_synapse/client)" = { + proxyPass = "http://${serviceName}"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + "= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; + "= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = genNginx homeServiceAddress nginxAccessRules; + }; + + }; +} diff --git a/modules-clone/nixos/server/microbin.nix b/modules-clone/nixos/server/microbin.nix new file mode 100644 index 0000000..d962877 --- /dev/null +++ b/modules-clone/nixos/server/microbin.nix @@ -0,0 +1,127 @@ +{ self, lib, config, dns, globals, confLib, ... }: +let + inherit (confLib.gen { name = "microbin"; port = 8777; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + inherit (config.swarselsystems) sopsFile; + + cfg = config.services.${serviceName}; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds.${serviceName} = confLib.mkIds 964; + groups.${serviceGroup} = { }; + + users.${serviceUser} = { + isSystemUser = true; + group = serviceGroup; + }; + }; + + sops = { + secrets = { + microbin-admin-username = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + microbin-admin-password = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + microbin-uploader-password = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + templates = { + "microbin-env" = { + content = '' + MICROBIN_ADMIN_USERNAME="${config.sops.placeholder.microbin-admin-username}" + MICROBIN_ADMIN_PASSWORD="${config.sops.placeholder.microbin-admin-password}" + MICROBIN_UPLOADER_PASSWORD="${config.sops.placeholder.microbin-uploader-password}" + ''; + owner = serviceUser; + group = serviceGroup; + mode = "0440"; + }; + }; + }; + + topology.self.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.${serviceName} = { + enable = true; + passwordFile = config.sops.templates.microbin-env.path; + dataDir = "/var/lib/microbin"; + settings = { + MICROBIN_HIDE_LOGO = true; + MICROBIN_PORT = servicePort; + MICROBIN_EDITABLE = true; + MICROBIN_HIDE_HEADER = true; + MICROBIN_HIDE_FOOTER = true; + MICROBIN_NO_LISTING = false; + MICROBIN_HIGHLIGHTSYNTAX = true; + MICROBIN_BIND = "0.0.0.0"; + MICROBIN_PRIVATE = true; + MICROBIN_PUBLIC_PATH = "https://${serviceDomain}"; + MICROBIN_READONLY = true; + MICROBIN_SHOW_READ_STATS = true; + MICROBIN_TITLE = "~SwarselScratch~"; + MICROBIN_THREADS = 1; + MICROBIN_GC_DAYS = 30; + MICROBIN_ENABLE_BURN_AFTER = true; + MICROBIN_QR = true; + MICROBIN_ETERNAL_PASTA = true; + MICROBIN_ENABLE_READONLY = true; + MICROBIN_DEFAULT_EXPIRY = "1week"; + MICROBIN_NO_FILE_UPLOAD = false; + MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB = 256; + MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB = 1024; + MICROBIN_DISABLE_UPDATE_CHECKING = true; + MICROBIN_DISABLE_TELEMETRY = true; + MICROBIN_LIST_SERVER = false; + }; + }; + + systemd.services = { + ${serviceName} = { + serviceConfig = { + DynamicUser = lib.mkForce false; + User = serviceUser; + Group = serviceGroup; + }; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = cfg.dataDir; user = serviceUser; group = serviceGroup; mode = "0700"; } + ]; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 1; maxBodyUnit = "G"; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 1; maxBodyUnit = "G"; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; + +} diff --git a/modules-clone/nixos/server/minecraft/default.nix b/modules-clone/nixos/server/minecraft/default.nix new file mode 100644 index 0000000..1c37055 --- /dev/null +++ b/modules-clone/nixos/server/minecraft/default.nix @@ -0,0 +1,55 @@ +{ self, lib, config, pkgs, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "minecraft"; port = 25565; dir = "/opt/minecraft"; proxy = config.node.name; }) serviceName servicePort serviceDir serviceDomain proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome dnsServer; + inherit (config.swarselsystems) mainUser; + worldName = "${mainUser}craft"; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + nodes.${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + + topology.self.services.${serviceName} = { + name = "Minecraft"; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + globals.services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome; + }; + + networking.firewall.allowedTCPPorts = [ servicePort ]; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = serviceDir; mode = "0755"; } + ]; + + systemd.services.minecraft-swarselcraft = { + description = "Minecraft Server"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + serviceConfig = { + User = "root"; + WorkingDirectory = "${serviceDir}/${worldName}"; + + ExecStart = "${lib.getExe pkgs.temurin-jre-bin-17} @user_jvm_args.txt @libraries/net/minecraftforge/forge/1.20.1-47.2.20/unix_args.txt nogui"; + + Restart = "always"; + RestartSec = 30; + StandardInput = "null"; + }; + + wantedBy = [ "multi-user.target" ]; + }; + + + }; + +} diff --git a/modules-clone/nixos/server/monitoring.nix b/modules-clone/nixos/server/monitoring.nix new file mode 100644 index 0000000..b47001f --- /dev/null +++ b/modules-clone/nixos/server/monitoring.nix @@ -0,0 +1,294 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "grafana"; port = 3000; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + prometheusPort = 9090; + prometheusUser = "prometheus"; + prometheusGroup = prometheusUser; + grafanaUpstream = "grafana"; + prometheusUpstream = "prometheus"; + prometheusWebRoot = "prometheus"; + kanidmDomain = globals.services.kanidm.domain; + + inherit (config.swarselsystems) sopsFile; + + # sopsFile2 = config.node.secretsDir + "/secrets2.yaml"; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops = { + secrets = { + grafana-admin-pw = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + prometheus-admin-pw = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + kanidm-grafana-client = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + # prometheus-admin-hash = { sopsFile = sopsFile2; owner = prometheusUser; group = prometheusGroup; mode = "0440"; }; + prometheus-admin-hash = { inherit sopsFile; owner = prometheusUser; group = prometheusGroup; mode = "0440"; }; + + }; + templates = { + "web-config" = { + content = '' + basic_auth_users: + admin: ${config.sops.placeholder.prometheus-admin-hash} + ''; + owner = prometheusUser; + group = prometheusGroup; + mode = "0440"; + }; + }; + }; + + users = { + persistentIds = { + nextcloud-exporter = confLib.mkIds 988; + node-exporter = confLib.mkIds 987; + grafana = confLib.mkIds 974; + }; + groups.nextcloud-exporter = { }; + users = { + nextcloud-exporter = { + group = "nextcloud-exporter"; + extraGroups = [ "nextcloud" ]; + }; + + ${serviceUser} = { + extraGroups = [ "users" ]; + }; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort prometheusPort ]; + + topology.self.services.prometheus.info = "https://${serviceDomain}/${prometheusWebRoot}"; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort prometheusPort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort prometheusPort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/lib/prometheus2"; user = prometheusUser; group = prometheusGroup; } + ]; + }; + + services = { + ${serviceName} = { + enable = true; + dataDir = "/var/lib/${serviceName}"; + provision = { + enable = true; + datasources.settings = { + datasources = [ + { + name = "prometheus"; + type = "prometheus"; + url = "https://${serviceDomain}/prometheus"; + editable = false; + access = "proxy"; + basicAuth = true; + basicAuthUser = "admin"; + jsonData = { + httpMethod = "POST"; + manageAlerts = true; + prometheusType = "Prometheus"; + prometheusVersion = "> 2.50.x"; + cacheLevel = "High"; + disableRecordingRules = false; + incrementalQueryOverlapWindow = "10m"; + }; + secureJsonData = { + basicAuthPassword = "$__file{/run/secrets/prometheus-admin-pw}"; + }; + } + ]; + }; + }; + + settings = { + analytics.reporting_enabled = false; + users.allow_sign_up = false; + security = { + # admin_password = "$__file{/run/secrets/grafana-admin-pw}"; + disable_initial_admin_creation = true; + secret_key = "$__file{${config.sops.secrets.grafana-admin-pw.path}}"; + cookie_secure = true; + disable_gravatar = true; + }; + server = { + domain = serviceDomain; + root_url = "https://${serviceDomain}"; + http_port = servicePort; + http_addr = "0.0.0.0"; + protocol = "http"; + enforce_domain = true; + enable_gzip = true; + }; + "auth.generic_oauth" = { + enabled = true; + name = "Kanidm"; + icon = "signin"; + allow_sign_up = true; + #auto_login = true; + client_id = "grafana"; + client_secret = "$__file{${config.sops.secrets.kanidm-grafana-client.path}}"; + scopes = "openid email profile"; + login_attribute_path = "preferred_username"; + auth_url = "https://${kanidmDomain}/ui/oauth2"; + token_url = "https://${kanidmDomain}/oauth2/token"; + api_url = "https://${kanidmDomain}/oauth2/openid/grafana/userinfo"; + use_pkce = true; + use_refresh_token = true; + # Allow mapping oauth2 roles to server admin + allow_assign_grafana_admin = true; + role_attribute_path = "contains(groups[*], 'server_admin') && 'GrafanaAdmin' || contains(groups[*], 'admin') && 'Admin' || contains(groups[*], 'editor') && 'Editor' || 'Viewer'"; + }; + }; + }; + + prometheus = + let + nextcloudUser = config.repo.secrets.local.nextcloud.adminuser; + in + { + enable = true; + webExternalUrl = "https://${serviceDomain}/${prometheusWebRoot}"; + port = prometheusPort; + listenAddress = "0.0.0.0"; + globalConfig = { + scrape_interval = "10s"; + }; + webConfigFile = config.sops.templates.web-config.path; + scrapeConfigs = [ + { + job_name = "node"; + static_configs = [{ + targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ]; + }]; + } + { + job_name = "zfs"; + static_configs = [{ + targets = [ "localhost:${toString config.services.prometheus.exporters.zfs.port}" ]; + }]; + } + { + job_name = "nginx"; + static_configs = [{ + targets = [ "localhost:${toString config.services.prometheus.exporters.nginx.port}" ]; + }]; + } + { + job_name = "nextcloud"; + static_configs = [{ + targets = [ "localhost:${toString config.services.prometheus.exporters.nextcloud.port}" ]; + }]; + } + ]; + exporters = { + node = { + enable = true; + port = 9000; + enabledCollectors = [ "systemd" ]; + extraFlags = [ "--collector.ethtool" "--collector.softirqs" "--collector.tcpstat" "--collector.wifi" ]; + }; + zfs = { + enable = true; + port = 9134; + pools = [ + "Vault" + ]; + }; + restic = { + enable = false; + port = 9753; + }; + nginx = { + enable = true; + port = 9113; + sslVerify = false; + scrapeUri = "http://localhost/nginx_status"; + }; + nextcloud = lib.mkIf config.swarselmodules.server.nextcloud { + enable = true; + port = 9205; + url = "https://${serviceDomain}/ocs/v2.php/apps/serverinfo/api/v1/info"; + username = nextcloudUser; + passwordFile = config.sops.secrets.nextcloud-admin-pw.path; + }; + }; + }; + }; + + + nodes = + let + extraConfig = '' + allow ${globals.networks.home-lan.vlans.services.cidrv4}; + allow ${globals.networks.home-lan.vlans.services.cidrv6}; + ''; + genNginx = toAddress: extraConfigPre: { + upstreams = { + "${grafanaUpstream}" = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + "${prometheusUpstream}" = { + servers = { + "${toAddress}:${builtins.toString prometheusPort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + + forceSSL = true; + acmeRoot = null; + extraConfig = extraConfigPre; + locations = + let + extraConfig = '' + client_max_body_size 0; + ''; + in + { + "/" = { + proxyPass = "http://${grafanaUpstream}"; + proxyWebsockets = true; + inherit extraConfig; + }; + "/${prometheusWebRoot}" = { + proxyPass = "http://${prometheusUpstream}"; + inherit extraConfig; + }; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = genNginx homeServiceAddress (extraConfig + nginxAccessRules); + }; + }; +} diff --git a/modules-clone/nixos/server/mpd.nix b/modules-clone/nixos/server/mpd.nix new file mode 100644 index 0000000..119a019 --- /dev/null +++ b/modules-clone/nixos/server/mpd.nix @@ -0,0 +1,66 @@ +{ lib, config, pkgs, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "mpd"; port = 3254; }) servicePort serviceName serviceUser serviceGroup; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + users = { + groups = { + mpd = { }; + }; + + users = { + ${serviceUser} = { + isSystemUser = true; + group = serviceGroup; + extraGroups = [ "audio" "utmp" ]; + }; + }; + }; + + sops = { + secrets.mpd-pw = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + environment.systemPackages = with pkgs; [ + pciutils + alsa-utils + mpv + ]; + + # topology.self.services.${serviceName} = { + # info = "http://localhost:${builtins.toString servicePort}"; + # icon = lib.mkForce "${self}/files/topology-images/mpd.png"; + # }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = "mpd"; group = "mpd"; }]; + }; + + services.${serviceName} = { + enable = true; + openFirewall = true; + settings = { + music_directory = "/storage/Music"; + bind_to_address = "any"; + port = servicePort; + }; + user = serviceUser; + group = serviceGroup; + credentials = [ + { + passwordFile = config.sops.secrets.mpd-pw.path; + permissions = [ + "read" + "add" + "control" + "admin" + ]; + } + ]; + }; + }; + +} diff --git a/modules-clone/nixos/server/navidrome.nix b/modules-clone/nixos/server/navidrome.nix new file mode 100644 index 0000000..c2e9d29 --- /dev/null +++ b/modules-clone/nixos/server/navidrome.nix @@ -0,0 +1,188 @@ +{ pkgs, config, lib, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "navidrome"; port = 4040; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf nginxAccessRules homeServiceAddress; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + + environment.systemPackages = with pkgs; [ + pciutils + alsa-utils + mpv + ]; + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + users = { + groups = { + ${serviceGroup} = { + gid = 61593; + }; + }; + + users = { + ${serviceUser} = { + isSystemUser = true; + uid = 61593; + group = serviceGroup; + extraGroups = [ "audio" "utmp" "users" "pipewire" ]; + }; + }; + }; + + hardware = { + enableAllFirmware = lib.mkForce true; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.snapserver = { + enable = true; + settings = { + stream = { + port = 1704; + source = "pipe:///tmp/snapfifo?name=default"; + bind_to_address = "0.0.0.0"; + }; + }; + }; + + systemd.services = { + ${serviceName}.serviceConfig = { + PrivateDevices = lib.mkForce false; + PrivateUsers = lib.mkForce false; + RestrictRealtime = lib.mkForce false; + SystemCallFilter = lib.mkForce null; + RootDirectory = lib.mkForce null; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services.${serviceName} = { + enable = true; + settings = { + LogLevel = "debug"; + Address = "0.0.0.0"; + Port = servicePort; + MusicFolder = "/storage/Music"; + PlaylistsPath = "./Playlists"; + AutoImportPlaylists = false; + EnableSharing = true; + EnableTranscodingConfig = true; + Scanner.GroupAlbumReleases = true; + ScanSchedule = "@every 24h"; + # MPVPath = ""; + # MPVCommandTemplate = "${pkgs.mpv}/bin/mpv --audio-device=%d --input-ipc-server=%s --no-audio-display --log-file=/tmp/mpv.log --pause %f"; + # MPVCmdTemplate = "${pkgs.mpv}/bin/mpv --no-audio-display --pause %f --input-ipc-server=%s --audio-channels=stereo --audio-samplerate=48000 --audio-format=s16 --ao=pcm --ao-pcm-file=/tmp/snapfifo --log-file=/tmp/mpv.log"; + ReverseProxyWhitelist = "0.0.0.0/0"; + ReverseProxyUserHeader = "X-User"; + Jukebox = { + Enabled = true; + Default = "default"; + Devices = [ + # use mpv --audio-device=help to get these + [ "default" "alsa/sysdefault:CARD=PCH" ] + ]; + }; + # Switch using --impure as these credential files are not stored within the flake + # sops-nix is not supported for these which is why we need to resort to these + LastFM = { + inherit (config.repo.secrets.local.LastFM) ApiKey Secret; + }; + Spotify = { + inherit (config.repo.secrets.local.Spotify) ID Secret; + }; + UILoginBackgroundUrl = "https://i.imgur.com/OMLxi7l.png"; + UIWelcomeMessage = "~SwarselSound~"; + EnableInsightsCollector = false; + }; + }; + + nodes = + let + genNginx = toAddress: extraConfigPre: { + upstreams = { + ${serviceName} = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + forceSSL = true; + acmeRoot = null; + oauth2 = { + enable = true; + allowedGroups = [ "navidrome_access" ]; + }; + extraConfig = extraConfigPre; + locations = + let + extraConfig = '' + proxy_redirect http:// https://; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + proxy_buffering off; + proxy_request_buffering off; + client_max_body_size 0; + ''; + in + { + "/" = { + proxyPass = "http://${serviceName}"; + proxyWebsockets = true; + inherit extraConfig; + }; + "/share" = { + proxyPass = "http://${serviceName}"; + proxyWebsockets = true; + setOauth2Headers = false; + bypassAuth = true; + inherit extraConfig; + }; + "/rest" = { + proxyPass = "http://${serviceName}"; + proxyWebsockets = true; + setOauth2Headers = false; + bypassAuth = true; + inherit extraConfig; + }; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (genNginx homeServiceAddress nginxAccessRules); + }; + + }; +} diff --git a/modules-clone/nixos/server/network.nix b/modules-clone/nixos/server/network.nix new file mode 100644 index 0000000..15cdee8 --- /dev/null +++ b/modules-clone/nixos/server/network.nix @@ -0,0 +1,60 @@ +{ lib, config, ... }: +let + netConfig = config.repo.secrets.local.networking; + netPrefix = "${if config.swarselsystems.isCloud then config.node.name else "home"}"; +in +{ + options = { + swarselmodules.server.network = lib.mkEnableOption "enable server network config"; + swarselsystems.server = { + localNetwork = lib.mkOption { + type = lib.types.str; + default = ""; + }; + netConfigName = lib.mkOption { + type = lib.types.str; + default = "${netPrefix}-${config.swarselsystems.server.localNetwork}"; + readOnly = true; + }; + netConfigPrefix = lib.mkOption { + type = lib.types.str; + default = netPrefix; + readOnly = true; + }; + }; + }; + config = lib.mkIf config.swarselmodules.server.network { + + swarselsystems.server.localNetwork = netConfig.localNetwork or ""; + + globals.networks = lib.mkIf config.swarselsystems.writeGlobalNetworks (lib.mapAttrs' + (netName: _: + lib.nameValuePair "${netPrefix}-${netName}" { + hosts.${config.node.name} = { + inherit (netConfig.networks.${netName}) id; + mac = netConfig.networks.${netName}.mac or null; + }; + } + ) + netConfig.networks); + + globals.hosts.${config.node.name} = { + defaultGateway4 = netConfig.defaultGateway4 or null; + defaultGateway6 = netConfig.defaultGateway6 or null; + wanAddress4 = netConfig.wanAddress4 or null; + wanAddress6 = netConfig.wanAddress6 or null; + isHome = if (netPrefix == "home") then true else false; + }; + + networking = { + inherit (netConfig) hostId; + hostName = config.node.name; + nftables.enable = lib.mkDefault false; + enableIPv6 = lib.mkDefault true; + firewall = { + enable = lib.mkDefault true; + }; + }; + + }; +} diff --git a/modules-clone/nixos/server/nextcloud.nix b/modules-clone/nixos/server/nextcloud.nix new file mode 100644 index 0000000..9846c1d --- /dev/null +++ b/modules-clone/nixos/server/nextcloud.nix @@ -0,0 +1,72 @@ +{ pkgs, lib, config, globals, dns, confLib, ... }: +let + inherit (config.repo.secrets.local.nextcloud) adminuser; + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "nextcloud"; port = 80; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome dnsServer webProxy homeWebProxy homeServiceAddress nginxAccessRules; + + nextcloudVersion = "33"; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops.secrets = { + nextcloud-admin-pw = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + kanidm-nextcloud-client = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + users.persistentIds = { + nextcloud = confLib.mkIds 990; + redis-nextcloud = confLib.mkIds 976; + }; + + globals.services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/lib/redis-${serviceName}"; user = serviceUser; group = serviceGroup; } + ]; + }; + + services = { + ${serviceName} = { + enable = true; + settings = { + trusted_proxies = [ "0.0.0.0" ]; + overwriteprotocol = "https"; + }; + package = pkgs."nextcloud${nextcloudVersion}"; + hostName = serviceDomain; + home = "/var/lib/${serviceName}"; + datadir = "/var/lib/${serviceName}"; + https = true; + configureRedis = true; + maxUploadSize = "4G"; + extraApps = { + inherit (pkgs."nextcloud${nextcloudVersion}Packages".apps) mail calendar contacts cospend phonetrack polls tasks sociallogin; + }; + extraAppsEnable = true; + config = { + inherit adminuser; + adminpassFile = config.sops.secrets.nextcloud-admin-pw.path; + dbtype = "sqlite"; + }; + }; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/nfs.nix b/modules-clone/nixos/server/nfs.nix new file mode 100644 index 0000000..d5afd43 --- /dev/null +++ b/modules-clone/nixos/server/nfs.nix @@ -0,0 +1,51 @@ +{ lib, config, pkgs, globals, confLib, ... }: +let + nfsUser = globals.user.name; +in +{ + options.swarselmodules.server.nfs = lib.mkEnableOption "enable nfs on server"; + config = lib.mkIf config.swarselmodules.server.nfs { + + users.persistentIds = { + avahi = confLib.mkIds 978; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/cache/samba"; } + ]; + }; + + services = { + # add a user with sudo smbpasswd -a + samba = { + package = pkgs.samba4; + enable = true; + openFirewall = true; + settings.Eternor = { + browseable = "yes"; + "read only" = "no"; + "guest ok" = "no"; + path = "/storage"; + writable = "true"; + comment = "Eternor"; + "valid users" = nfsUser; + }; + }; + + avahi = { + publish.enable = true; + publish.userServices = true; # Needed to allow samba to automatically register mDNS records without the need for an `extraServiceFile` + nssmdns4 = true; + enable = true; + openFirewall = true; + }; + + # This enables autodiscovery on windows since SMB1 (and thus netbios) support was discontinued + samba-wsdd = { + enable = true; + openFirewall = true; + }; + }; + }; +} diff --git a/modules-clone/nixos/server/nftables.nix b/modules-clone/nixos/server/nftables.nix new file mode 100644 index 0000000..6ded12c --- /dev/null +++ b/modules-clone/nixos/server/nftables.nix @@ -0,0 +1,75 @@ +{ lib, config, confLib, ... }: +let + inherit (confLib.gen { name = "nftables"; }) serviceName; +in +{ + options = { + swarselmodules.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.${serviceName} { + + networking.nftables = { + stopRuleset = lib.mkDefault '' + table inet filter { + chain input { + type filter hook input priority filter; policy drop; + ct state invalid drop + ct state {established, related} accept + + iifname lo accept + meta l4proto ipv6-icmp accept + meta l4proto icmp accept + ip protocol igmp accept + tcp dport ${toString (lib.head config.services.openssh.ports)} accept + } + chain forward { + type filter hook forward priority filter; policy drop; + } + chain output { + type filter hook output priority filter; policy accept; + } + } + ''; + + firewall = { + enable = true; + localZoneName = "local"; + snippets = { + nnf-common.enable = false; + nnf-conntrack.enable = true; + nnf-drop.enable = true; + nnf-loopback.enable = true; + nnf-ssh.enable = true; + nnf-dhcpv6.enable = true; + }; + + rules.untrusted-to-local = { + from = [ "untrusted" ]; + to = [ "local" ]; + + inherit (config.networking.firewall) + allowedTCPPorts + allowedTCPPortRanges + allowedUDPPorts + allowedUDPPortRanges + ; + }; + + rules.icmp-and-igmp = { + after = [ + "ct" + "ssh" + ]; + from = "all"; + to = [ "local" ]; + extraLines = [ + "meta l4proto ipv6-icmp accept" + "meta l4proto icmp accept" + "ip protocol igmp accept" + ]; + }; + }; + }; + + }; +} diff --git a/modules-clone/nixos/server/nginx.nix b/modules-clone/nixos/server/nginx.nix new file mode 100644 index 0000000..918da3e --- /dev/null +++ b/modules-clone/nixos/server/nginx.nix @@ -0,0 +1,167 @@ +{ pkgs, lib, config, ... }: +let + serviceUser = "nginx"; + serviceGroup = serviceUser; + + sslBasePath = "/etc/ssl"; + dhParamsPathBase = "${sslBasePath}/dhparams.pem"; + dhParamsPath = + if config.swarselsystems.isImpermanence then + "/persist/${dhParamsPathBase}" + else + "${dhParamsPathBase}"; +in +{ + options.swarselmodules.server.nginx = lib.mkEnableOption "enable nginx on server"; + options.services.nginx = { + recommendedSecurityHeaders = lib.mkEnableOption "additional security headers by default in each location block."; + defaultStapling = lib.mkEnableOption "add ssl stapling in each location block.."; + virtualHosts = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule (topmod: { + options = { + defaultStapling = lib.mkOption { + type = lib.types.bool; + default = config.services.nginx.defaultStapling; + description = "Whether to add ssl stapling to this location."; + }; + locations = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule (submod: { + options = { + recommendedSecurityHeaders = lib.mkOption { + type = lib.types.bool; + default = config.services.nginx.recommendedSecurityHeaders; + description = "Whether to add additional security headers to this location."; + }; + + X-Frame-Options = lib.mkOption { + type = lib.types.str; + default = "DENY"; + description = "The value to use for X-Frame-Options"; + }; + }; + + config = { + extraConfig = lib.mkIf submod.config.recommendedSecurityHeaders (lib.mkBefore '' + # Hide upstream's versions + proxy_hide_header Strict-Transport-Security; + proxy_hide_header Referrer-Policy; + proxy_hide_header X-Content-Type-Options; + proxy_hide_header X-Frame-Options; + + # Enable HTTP Strict Transport Security (HSTS) + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + + # Minimize information leaked to other domains + add_header Referrer-Policy "origin-when-cross-origin"; + + add_header X-XSS-Protection "1; mode=block"; + add_header X-Frame-Options "${submod.config.X-Frame-Options}"; + add_header X-Content-Type-Options "nosniff"; + '' + ); + }; + }) + ); + }; + }; + config = { + extraConfig = lib.mkIf topmod.config.defaultStapling (lib.mkBefore '' + ssl_stapling on; + ssl_stapling_verify on; + resolver 1.1.1.1 8.8.8.8 valid=300s; + resolver_timeout 5s; + ''); + }; + }) + ); + }; + }; + config = lib.mkIf config.swarselmodules.server.nginx { + + swarselmodules.server.acme = lib.mkDefault true; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + environment.persistence = { + "/persist" = lib.mkIf config.swarselsystems.isImpermanence { + files = [ dhParamsPathBase ]; + }; + "/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/cache/nginx"; user = "nginx"; group = "nginx"; } + ]; + }; + }; + + services.nginx = { + enable = true; + user = serviceUser; + group = serviceGroup; + statusPage = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedBrotliSettings = true; + recommendedSecurityHeaders = true; + defaultStapling = true; + sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:!aNULL"; + sslDhparam = dhParamsPathBase; + virtualHosts.fallback = { + default = true; + rejectSSL = true; + locations."/".extraConfig = '' + deny all; + ''; + }; + }; + systemd.services.generateDHParams = { + before = [ "nginx.service" ]; + requiredBy = [ "nginx.service" ]; + after = [ "local-fs.target" ]; + requires = [ "local-fs.target" ]; + serviceConfig = { + Type = "oneshot"; + }; + + script = '' + set -eu + + install -d -m 0755 ${sslBasePath} + ${if config.swarselsystems.isImpermanence then "${pkgs.coreutils}/bin/install -d -m 0755 /persist${sslBasePath}" else ""} + + if [ ! -f "${dhParamsPath}" ]; then + ${pkgs.openssl}/bin/openssl dhparam -out "${dhParamsPath}" 4096 + chmod 0644 "${dhParamsPath}" + chown ${serviceUser}:${serviceGroup} "${dhParamsPath}" + else + echo 'Already generated DHParams' + fi + ''; + }; + + # system.activationScripts."createPersistentStorageDirs" = lib.mkIf config.swarselsystems.isImpermanence { + # deps = [ "generateDHParams" "users" "groups" ]; + # }; + # system.activationScripts."generateDHParams" = + # { + # text = '' + # set -eu + + # ${if config.swarselsystems.isImpermanence then "${pkgs.coreutils}/bin/install -d -m 0755 /persist${sslBasePath}" else "${pkgs.coreutils}/bin/install -d -m 0755 ${sslBasePath}"} + + # if [ ! -f "${dhParamsPath}" ]; then + # ${pkgs.openssl}/bin/openssl dhparam -out ${dhParamsPath} 4096 + # chmod 0644 ${dhParamsPath} + # chown ${serviceUser}:${serviceGroup} ${dhParamsPath} + # fi + # ''; + # deps = [ + # (lib.mkIf config.swarselsystems.isImpermanence "specialfs") + # (lib.mkIf (!config.swarselsystems.isImpermanence) "etc") + # ]; + # }; + }; +} diff --git a/modules-clone/nixos/server/nsd/default.nix b/modules-clone/nixos/server/nsd/default.nix new file mode 100644 index 0000000..84efeca --- /dev/null +++ b/modules-clone/nixos/server/nsd/default.nix @@ -0,0 +1,95 @@ +{ self, lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "nsd"; port = 53; }) serviceName servicePort proxyAddress4 proxyAddress6; + inherit (config.swarselsystems) sopsFile; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + swarselsystems.server.dns = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + subdomainRecords = lib.mkOption { + type = lib.types.attrsOf dns.lib.types.subzone; + default = { }; + }; + }; + } + ); + }; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops.secrets = { + tsig-key = { inherit sopsFile; }; + }; + + # services.resolved.enable = false; + networking = { + # nameservers = [ "1.1.1.1" "8.8.8.8" ]; + firewall = { + allowedUDPPorts = [ servicePort ]; + allowedTCPPorts = [ servicePort ]; + }; + }; + + topology.self.services.${serviceName} = { + name = lib.toUpper serviceName; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + services.nsd = { + enable = true; + keys = { + "${globals.domains.main}.${proxyAddress4}" = { + algorithm = "hmac-sha256"; + keyFile = config.sops.secrets.tsig-key.path; + }; + "${globals.domains.main}.${proxyAddress6}" = { + algorithm = "hmac-sha256"; + keyFile = config.sops.secrets.tsig-key.path; + }; + "${globals.domains.main}" = { + algorithm = "hmac-sha256"; + keyFile = config.sops.secrets.tsig-key.path; + }; + }; + interfaces = [ + "10.1.2.157" + "2603:c020:801f:a0cc::9d" + ]; + zones = { + "${globals.domains.main}" = + let + keyName4 = "${globals.domains.main}.${proxyAddress4}"; + keyName6 = "${globals.domains.main}.${proxyAddress6}"; + keyName = "${globals.domains.main}"; + transferList = [ + "213.239.242.238 ${keyName4}" + "2a01:4f8:0:a101::a:1 ${keyName6}" + "213.133.100.103 ${keyName4}" + "2a01:4f8:0:1::5ddc:2 ${keyName6}" + "193.47.99.3 ${keyName4}" + "2001:67c:192c::add:a3 ${keyName6}" + ]; + + in + { + outgoingInterface = "2603:c020:801f:a0cc::9d"; + notify = transferList ++ [ + "216.218.130.2 ${keyName}" + ]; + provideXFR = transferList ++ [ + "216.218.133.2 ${keyName}" + "2001:470:600::2 ${keyName}" + ]; + + # dnssec = true; + data = dns.lib.toString "${globals.domains.main}" (import ./site1.nix { inherit config globals dns proxyAddress4 proxyAddress6; }); + }; + }; + }; + + }; +} diff --git a/modules-clone/nixos/server/nsd/site1.nix b/modules-clone/nixos/server/nsd/site1.nix new file mode 100644 index 0000000..8c155c7 --- /dev/null +++ b/modules-clone/nixos/server/nsd/site1.nix @@ -0,0 +1,117 @@ +{ config, globals, dns, proxyAddress4, proxyAddress6, ... }: +with dns.lib.combinators; { + SOA = { + nameServer = "soa"; + adminEmail = "admin@${globals.domains.main}"; # this option is not parsed as domain (we cannot just write "admin") + serial = 2026010201; # update this on changes for secondary dns + }; + + useOrigin = false; + + NS = [ + "soa" + "srv" + ] ++ globals.domains.externalDns; + + CAA = [ + { + issuerCritical = false; + tag = "issue"; + value = "letsencrypt.org"; + } + { + issuerCritical = false; + tag = "issuewild"; + value = "letsencrypt.org"; + } + { + issuerCritical = false; + tag = "iodef"; + value = "mailto:${config.repo.secrets.common.dnsMail}"; + } + ]; + + A = [ config.repo.secrets.local.dns.homepage-ip ]; + + SRV = [ + { + service = "_matrix"; + proto = "_tcp"; + port = 443; + target = "${globals.services.matrix.subDomain}"; + priority = 10; + weight = 5; + } + { + service = "_submissions"; + proto = "_tcp"; + port = 465; + target = "${globals.services.mailserver.subDomain}"; + priority = 5; + weight = 0; + ttl = 3600; + } + { + service = "_submission"; + proto = "_tcp"; + port = 587; + target = "${globals.services.mailserver.subDomain}"; + priority = 5; + weight = 0; + ttl = 3600; + } + { + service = "_imap"; + proto = "_tcp"; + port = 143; + target = "${globals.services.mailserver.subDomain}"; + priority = 5; + weight = 0; + ttl = 3600; + } + { + service = "_imaps"; + proto = "_tcp"; + port = 993; + target = "${globals.services.mailserver.subDomain}"; + priority = 5; + weight = 0; + ttl = 3600; + } + ]; + + MX = [ + { + preference = 10; + exchange = "${globals.services.mailserver.subDomain}"; + } + ]; + + DKIM = [ + { + selector = "mail"; + k = "rsa"; + p = config.repo.secrets.local.dns.mailserver.dkim-public; + ttl = 10800; + } + ]; + + TXT = [ + (with spf; strict [ "a:${globals.services.mailserver.subDomain}.${globals.domains.main}" ]) + "google-site-verification=${config.repo.secrets.local.dns.google-site-verification}" + ]; + + DMARC = [ + { + p = "none"; + ttl = 10800; + } + ]; + + subdomains = config.swarselsystems.server.dns.${globals.domains.main}.subdomainRecords // { + "www".CNAME = [ "${globals.domains.main}." ]; + "_acme-challenge".CNAME = [ "${config.repo.secrets.local.dns.acme-challenge-domain}." ]; + "soa" = host proxyAddress4 proxyAddress6; + "srv" = host proxyAddress4 proxyAddress6; + }; +} diff --git a/modules-clone/nixos/server/oauth2-proxy.nix b/modules-clone/nixos/server/oauth2-proxy.nix new file mode 100644 index 0000000..dfc89a3 --- /dev/null +++ b/modules-clone/nixos/server/oauth2-proxy.nix @@ -0,0 +1,235 @@ +{ lib, config, pkgs, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "oauth2-proxy"; port = 3004; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf oauthServer nginxAccessRules homeServiceAddress; + + kanidmDomain = globals.services.kanidm.domain; + mainDomain = globals.domains.main; + + inherit (config.swarselsystems) sopsFile; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + # largely based on https://github.com/oddlama/nix-config/blob/main/modules/oauth2-proxy.nix + services.nginx.virtualHosts = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { config, ... }: + { + options.oauth2 = { + enable = lib.mkEnableOption "access protection of this virtualHost using oauth2-proxy."; + allowedGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + A list of kanidm groups that are allowed to access this resource, or the + empty list to allow any authenticated client. + ''; + }; + X-User = lib.mkOption { + type = lib.types.str; + default = "$upstream_http_x_auth_request_user"; + description = "The variable to set as X-User"; + }; + X-Email = lib.mkOption { + type = lib.types.str; + default = "$upstream_http_x_auth_request_email"; + description = "The variable to set as X-Email"; + }; + X-Access-Token = lib.mkOption { + type = lib.types.str; + default = "$upstream_http_x_auth_request_access_token"; + description = "The variable to set as X-Access-Token"; + }; + }; + options.locations = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule (locationSubmodule: { + options = { + setOauth2Headers = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to add oauth2 headers to this location. Only takes effect is oauth2 is actually enabled on the parent vhost."; + }; + bypassAuth = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to set auth_request off for this location. Only takes effect is oauth2 is actually enabled on the parent vhost."; + }; + }; + config = lib.mkIf config.oauth2.enable { + extraConfig = lib.optionalString locationSubmodule.config.setOauth2Headers '' + proxy_set_header X-User $user; + proxy_set_header Remote-User $user; + proxy_set_header X-Remote-User $user; + proxy_set_header X-Email $email; + # proxy_set_header X-Access-Token $token; + add_header Set-Cookie $auth_cookie; + '' + lib.optionalString locationSubmodule.config.bypassAuth '' + auth_request off; + ''; + }; + }) + ); + }; + config = lib.mkIf config.oauth2.enable { + extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = /oauth2/sign_in; + + # set variables that can be used in locations..extraConfig + # pass information via X-User and X-Email headers to backend, + # requires running with --set-xauthrequest flag + auth_request_set $user ${config.oauth2.X-User}; + auth_request_set $email ${config.oauth2.X-Email}; + # if you enabled --pass-access-token, this will pass the token to the backend + # auth_request_set $token ${config.oauth2.X-Access-Token}; + # if you enabled --cookie-refresh, this is needed for it to work with auth_request + auth_request_set $auth_cookie $upstream_http_set_cookie; + ''; + locations = { + "/oauth2/" = { + proxyPass = "http://oauth2-proxy"; + setOauth2Headers = false; + bypassAuth = true; + extraConfig = '' + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + ''; + }; + "= /oauth2/auth" = { + proxyPass = "http://oauth2-proxy/oauth2/auth" + lib.optionalString (config.oauth2.allowedGroups != [ ]) "?allowed_groups=${lib.concatStringsSep "," config.oauth2.allowedGroups}"; + setOauth2Headers = false; + bypassAuth = true; + extraConfig = '' + internal; + + proxy_set_header X-Scheme $scheme; + # nginx auth_request includes headers but not body + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + }; + }; + } + ) + ); + }; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops = { + secrets = { + "oauth2-cookie-secret" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + "kanidm-oauth2-proxy-client" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + templates = { + "kanidm-oauth2-proxy-client-env" = { + content = '' + OAUTH2_PROXY_CLIENT_SECRET="${config.sops.placeholder.kanidm-oauth2-proxy-client}" + OAUTH2_PROXY_COOKIE_SECRET=${config.sops.placeholder.oauth2-cookie-secret} + ''; + owner = serviceUser; + group = serviceGroup; + mode = "0440"; + }; + }; + }; + + users = { + persistentIds.oauth2-proxy = confLib.mkIds 966; + }; + + # needed for homeWebProxy + networking.firewall.allowedTCPPorts = [ servicePort ]; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services = { + ${serviceName} = { + enable = true; + package = pkgs.oauth2-proxy; + cookie = { + domain = ".${mainDomain}"; + secure = true; + expire = "900m"; + secretFile = null; + }; + clientSecretFile = null; + reverseProxy = true; + httpAddress = "0.0.0.0:${builtins.toString servicePort}"; + redirectURL = "https://${serviceDomain}/oauth2/callback"; + setXauthrequest = true; + upstream = [ + "static://202" + ]; + + extraConfig = { + code-challenge-method = "S256"; + whitelist-domain = ".${mainDomain}"; + set-authorization-header = true; + pass-access-token = true; + skip-jwt-bearer-tokens = true; + oidc-issuer-url = "https://${kanidmDomain}/oauth2/openid/oauth2-proxy"; + provider-display-name = "Kanidm"; + }; + provider = "oidc"; + scope = "openid email"; + loginURL = "https://${kanidmDomain}/ui/oauth2"; + redeemURL = "https://${kanidmDomain}/oauth2/token"; + validateURL = "https://${kanidmDomain}/oauth2/openid/oauth2-proxy/userinfo"; + clientID = serviceName; + email.domains = [ "*" ]; + }; + }; + + systemd.services = { + ${serviceName} = { + # after = [ "kanidm.service" ]; + serviceConfig = { + RuntimeDirectory = serviceName; + RuntimeDirectoryMode = "0750"; + UMask = "007"; # TODO remove once https://github.com/oauth2-proxy/oauth2-proxy/issues/2141 is fixed + RestartSec = "60"; # Retry every minute + EnvironmentFile = [ + config.sops.templates.kanidm-oauth2-proxy-client-env.path + ]; + }; + }; + }; + + nodes = + let + extraConfig = '' + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + allow ${globals.networks.home-lan.vlans.services.cidrv4}; + allow ${globals.networks.home-lan.vlans.services.cidrv6}; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit servicePort serviceAddress serviceDomain serviceName extraConfig; }; + ${homeWebProxy}.services.nginx = confLib.genNginx { inherit servicePort serviceDomain serviceName; extraConfig = extraConfig + nginxAccessRules; serviceAddress = globals.hosts.${oauthServer}.wanAddress4; }; + }; + }; +} diff --git a/modules-clone/nixos/server/opkssh.nix b/modules-clone/nixos/server/opkssh.nix new file mode 100644 index 0000000..6f2a4de --- /dev/null +++ b/modules-clone/nixos/server/opkssh.nix @@ -0,0 +1,40 @@ +{ lib, config, globals, confLib, ... }: +let + inherit (confLib.gen { name = "opkssh"; user = "opksshuser"; group = "opksshuser"; }) serviceName serviceUser serviceGroup; + + kanidmDomain = globals.services.kanidm.domain; + + inherit (config.swarselsystems) mainUser; + mailAddress = config.repo.secrets.common.mail.address4; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users.persistentIds = { + opksshuser = confLib.mkIds 980; + }; + + services.${serviceName} = { + enable = true; + user = serviceUser; + group = serviceGroup; + providers = { + kanidm = { + lifetime = "oidc"; + issuer = "https://${kanidmDomain}/oauth2/openid/${serviceName}"; + clientId = serviceName; + }; + }; + authorizations = [ + { + user = mainUser; + principal = mailAddress; + inherit (config.services.opkssh.providers.kanidm) issuer; + } + ]; + }; + + }; + +} diff --git a/modules-clone/nixos/server/packages.nix b/modules-clone/nixos/server/packages.nix new file mode 100644 index 0000000..6b954eb --- /dev/null +++ b/modules-clone/nixos/server/packages.nix @@ -0,0 +1,24 @@ +{ lib, config, pkgs, withHomeManager, ... }: +{ + options.swarselmodules.server.packages = lib.mkEnableOption "enable packages on server"; + config = lib.mkIf config.swarselmodules.server.packages { + environment.systemPackages = with pkgs; [ + gnupg + nvd + nix-output-monitor + ssh-to-age + git + emacs + vim + sops + tmux + busybox + ndisc6 + tcpdump + swarsel-deploy + ] ++ lib.optionals withHomeManager [ + swarsel-gens + swarsel-switch + ]; + }; +} diff --git a/modules-clone/nixos/server/paperless.nix b/modules-clone/nixos/server/paperless.nix new file mode 100644 index 0000000..7baae06 --- /dev/null +++ b/modules-clone/nixos/server/paperless.nix @@ -0,0 +1,144 @@ +{ lib, pkgs, config, dns, globals, confLib, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (confLib.gen { name = "paperless"; port = 28981; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + tikaPort = 9998; + gotenbergPort = 3002; + kanidmDomain = globals.services.kanidm.domain; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users = { + persistentIds = { + redis-paperless = confLib.mkIds 975; + }; + users.${serviceUser} = { + extraGroups = [ "users" ]; + }; + }; + + sops.secrets = { + paperless-admin-pw = { inherit sopsFile; owner = serviceUser; }; + kanidm-paperless-client = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/lib/redis-${serviceName}"; user = "redis-${serviceUser}"; group = "redis-${serviceGroup}"; } + { directory = "/var/lib/private/tika"; } + { directory = "/var/cache/${serviceName}"; user = serviceUser; group = serviceGroup; } + { directory = "/var/cache/private/tika"; } + ]; + }; + + services = { + ${serviceName} = { + enable = true; + mediaDir = "/storage/Documents/${serviceName}"; + dataDir = "/var/lib/${serviceName}"; + user = serviceUser; + port = servicePort; + passwordFile = config.sops.secrets.paperless-admin-pw.path; + address = "0.0.0.0"; + settings = { + PAPERLESS_OCR_LANGUAGE = "deu+eng"; + PAPERLESS_URL = "https://${serviceDomain}"; + PAPERLESS_OCR_USER_ARGS = builtins.toJSON { + optimize = 1; + invalidate_digital_signatures = true; + pdfa_image_compression = "lossless"; + }; + PAPERLESS_TIKA_ENABLED = "true"; + PAPERLESS_TIKA_ENDPOINT = "http://localhost:${builtins.toString tikaPort}"; + PAPERLESS_TIKA_GOTENBERG_ENDPOINT = "http://localhost:${builtins.toString gotenbergPort}"; + PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect"; + PAPERLESS_SOCIALACCOUNT_PROVIDERS = builtins.toJSON { + openid_connect = { + OAUTH_PKCE_ENABLED = "True"; + APPS = [ + rec { + provider_id = "kanidm"; + name = "Kanidm"; + client_id = "paperless"; + # secret will be added by paperless-web.service (see below) + #secret = ""; + settings.server_url = "https://${kanidmDomain}/oauth2/openid/${client_id}/.well-known/openid-configuration"; + } + ]; + }; + }; + }; + }; + + tika = { + enable = true; + port = tikaPort; + openFirewall = false; + listenAddress = "127.0.0.1"; + enableOcr = true; + }; + + gotenberg = { + enable = true; + package = pkgs.gotenberg; + libreoffice.package = pkgs.libreoffice; + port = gotenbergPort; + bindIP = "127.0.0.1"; + timeout = "600s"; + chromium.package = pkgs.chromium; + }; + }; + + + # Add secret to PAPERLESS_SOCIALACCOUNT_PROVIDERS + systemd.services.paperless-web.script = lib.mkBefore '' + oidcSecret=$(< ${config.sops.secrets.kanidm-paperless-client.path}) + export PAPERLESS_SOCIALACCOUNT_PROVIDERS=$( + ${pkgs.jq}/bin/jq <<< "$PAPERLESS_SOCIALACCOUNT_PROVIDERS" \ + --compact-output \ + --arg oidcSecret "$oidcSecret" '.openid_connect.APPS.[0].secret = $oidcSecret' + ) + ''; + + nodes = + let + extraConfigLoc = '' + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + send_timeout 300; + ''; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName extraConfigLoc; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/pipewire.nix b/modules-clone/nixos/server/pipewire.nix new file mode 100644 index 0000000..d6549f2 --- /dev/null +++ b/modules-clone/nixos/server/pipewire.nix @@ -0,0 +1,26 @@ +{ lib, config, confLib, ... }: +{ + config = lib.mkIf (config.swarselmodules.server.mpd || config.swarselmodules.server.navidrome) { + + security.rtkit.enable = true; # this is required for pipewire real-time access + + users.persistentIds.rtkit = confLib.mkIds 996; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/pipewire"; user = "pipewire"; group = "pipewire"; }]; + }; + + services.pipewire = { + enable = true; + pulse.enable = true; + jack.enable = true; + audio.enable = true; + wireplumber.enable = true; + alsa = { + enable = true; + support32Bit = true; + }; + }; + }; + +} diff --git a/modules-clone/nixos/server/podman.nix b/modules-clone/nixos/server/podman.nix new file mode 100644 index 0000000..f1f397b --- /dev/null +++ b/modules-clone/nixos/server/podman.nix @@ -0,0 +1,48 @@ +{ config, lib, confLib, ... }: +let + serviceName = "podman"; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users.persistentIds = { + podman = confLib.mkIds 969; + }; + + virtualisation = { + podman.enable = true; + oci-containers.backend = "podman"; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/containers"; } + ]; + }; + + networking.nftables.firewall = lib.mkIf config.networking.nftables.enable { + + zones.podman = { + interfaces = [ "podman0" ]; + }; + + rules = { + podman-to-postgres = lib.mkIf config.services.postgresql.enable { + from = [ "podman" ]; + to = [ "local" ]; + before = [ "drop" ]; + allowedTCPPorts = [ config.services.postgresql.settings.port ]; + }; + + local-to-podman = { + from = [ "local" "wgProxy" "wgHome" ]; + to = [ "podman" ]; + before = [ "drop" ]; + verdict = "accept"; + }; + }; + }; + + }; +} diff --git a/modules-clone/nixos/server/postgresql.nix b/modules-clone/nixos/server/postgresql.nix new file mode 100644 index 0000000..d02e28e --- /dev/null +++ b/modules-clone/nixos/server/postgresql.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, confLib, ... }: +let + inherit (confLib.gen { name = "postgresql"; port = 3254; }) serviceName; + postgresVersion = 14; + postgresDirPrefix = "/var/lib"; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + services = { + ${serviceName} = { + enable = true; + package = pkgs."postgresql_${builtins.toString postgresVersion}"; + dataDir = "${postgresDirPrefix}/${serviceName}/${builtins.toString postgresVersion}"; + }; + }; + environment.persistence = { + "/persist".directories = lib.mkIf (config.swarselsystems.isImpermanence && config.swarselsystems.isCloud) [ + { directory = "/var/lib/postgresql"; user = "postgres"; group = "postgres"; mode = "0750"; } + ]; + "/state".directories = lib.mkIf config.swarselsystems.isMicroVM [ + { directory = "/var/lib/postgresql"; user = "postgres"; group = "postgres"; mode = "0750"; } + ]; + }; + + }; +} diff --git a/modules-clone/nixos/server/radicale.nix b/modules-clone/nixos/server/radicale.nix new file mode 100644 index 0000000..25ba047 --- /dev/null +++ b/modules-clone/nixos/server/radicale.nix @@ -0,0 +1,118 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "radicale"; port = 8000; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + inherit (config.swarselsystems) sopsFile; + + cfg = config.services.${serviceName}; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops = { + secrets.radicale-user = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + + templates = + let + inherit (config.repo.secrets.local.radicale) user1; + in + { + "radicale-users" = { + content = '' + ${user1}:${config.sops.placeholder.radicale-user} + ''; + owner = serviceUser; + group = serviceGroup; + mode = "0440"; + }; + }; + }; + + users.persistentIds = { + radicale = confLib.mkIds 982; + }; + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.${serviceName} = { + enable = true; + settings = { + server = { + hosts = [ + "0.0.0.0:${builtins.toString servicePort}" + "[::]:${builtins.toString servicePort}" + ]; + }; + auth = + { + type = "htpasswd"; + htpasswd_filename = config.sops.templates.radicale-users.path; + htpasswd_encryption = "autodetect"; + }; + storage = { + filesystem_folder = "/var/lib/radicale/collections"; + }; + }; + rights = { + # all: match authenticated users only + root = { + user = ".+"; + collection = ""; + permissions = "R"; + }; + principal = { + user = ".+"; + collection = "{user}"; + permissions = "RW"; + }; + calendars = { + user = ".+"; + collection = "{user}/[^/]+"; + permissions = "rw"; + }; + }; + }; + + systemd.tmpfiles.settings."10-radicale" = { + "${cfg.settings.storage.filesystem_folder}" = { + d = { + group = serviceGroup; + user = serviceUser; + mode = "0750"; + }; + }; + }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 16; maxBodyUnit = "M"; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 16; maxBodyUnit = "M"; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/restic.nix b/modules-clone/nixos/server/restic.nix new file mode 100644 index 0000000..9c6c7d7 --- /dev/null +++ b/modules-clone/nixos/server/restic.nix @@ -0,0 +1,87 @@ +{ lib, pkgs, config, ... }: +let + inherit (config.swarselsystems) sopsFile; + inherit (config.swarselsystems.server.restic) targets; +in +{ + options.swarselmodules.server.restic = lib.mkEnableOption "enable restic backups on server"; + options.swarselsystems.server.restic = { + targets = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: { + options = { + bucketName = lib.mkOption { + type = lib.types.str; + default = name; + }; + repository = lib.mkOption { + type = lib.types.str; + }; + paths = lib.mkOption { + type = lib.types.listOf lib.types.str; + }; + withPostgres = lib.mkOption { + type = lib.types.bool; + default = false; + }; + }; + })); + default = { }; + }; + }; + + config = lib.mkIf config.swarselmodules.server.restic { + + sops = { + secrets = + lib.mkMerge (lib.mapAttrsToList + (name: _: { + "resticpw-${name}" = { inherit sopsFile; }; + "resticaccesskey-${name}" = { inherit sopsFile; }; + "resticsecretaccesskey-${name}" = { inherit sopsFile; }; + }) + targets); + + templates = + lib.mkMerge (lib.mapAttrsToList + (name: _: { + "restic-env-${name}".content = '' + AWS_ACCESS_KEY_ID=${config.sops.placeholder."resticaccesskey-${name}"} + AWS_SECRET_ACCESS_KEY=${config.sops.placeholder."resticsecretaccesskey-${name}"} + ''; + }) + targets); + }; + + services.restic.backups = + lib.mapAttrs' + (name: target: + lib.nameValuePair target.bucketName { + environmentFile = + config.sops.templates."restic-env-${name}".path; + + passwordFile = + config.sops.secrets."resticpw-${name}".path; + + inherit (target) paths repository; + + pruneOpts = [ + "--keep-daily 3" + "--keep-weekly 2" + "--keep-monthly 3" + "--keep-yearly 100" + ]; + + backupPrepareCommand = '' + ${pkgs.restic}/bin/restic prune + ''; + + initialize = true; + + timerConfig = { + OnCalendar = "03:00"; + }; + } + ) + targets; + }; +} diff --git a/modules-clone/nixos/server/router.nix b/modules-clone/nixos/server/router.nix new file mode 100644 index 0000000..b0c64b3 --- /dev/null +++ b/modules-clone/nixos/server/router.nix @@ -0,0 +1,265 @@ +{ lib, config, globals, ... }: +let + serviceName = "router"; + bridgeVLANs = lib.mapAttrsToList + (_: vlan: { + VLAN = vlan.id; + }) + globals.networks.home-lan.vlans; + selectVLANs = vlans: map (vlan: { VLAN = globals.networks.home-lan.vlans.${vlan}.id; }) vlans; + lan1VLANs = selectVLANs [ "home" "devices" "guests" ]; + lan2VLANs = selectVLANs [ "home" "devices" "services" ]; + lan3VLANs = selectVLANs [ "home" "devices" "services" ]; + lan4VLANs = lan3VLANs; + # TODO: remove services and reset ports 5+6 on swLR to guest when kitchen construction is finished + lan5VLANs = selectVLANs [ "home" "devices" "services" "guests" ]; + inherit (globals.general) homeDnsServer; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} + { + services.avahi.reflector = true; + + topology.self.interfaces = (lib.mapAttrs' + (vlanName: _: + lib.nameValuePair "vlan-${vlanName}" { + network = lib.mkForce vlanName; + } + ) + globals.networks.home-lan.vlans) // (lib.mapAttrs' + (vlanName: _: + lib.nameValuePair "me-${vlanName}" { + network = lib.mkForce vlanName; + } + ) + globals.networks.home-lan.vlans); + + networking.nftables = { + firewall = { + zones = { + untrusted.interfaces = [ "lan" ]; + wgHome.interfaces = [ "wgHome" ]; + adguardhome.ipv4Addresses = [ globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv4 ]; + adguardhome.ipv6Addresses = [ globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv6 ]; + } + // lib.flip lib.concatMapAttrs globals.networks.home-lan.vlans ( + vlanName: _: { + "vlan-${vlanName}".interfaces = [ "me-${vlanName}" ]; + } + ); + + rules = { + masquerade-internet = { + from = map (name: "vlan-${name}") globals.general.internetVLANs; + to = [ "untrusted" ]; + # masquerade = true; NOTE: custom rule below for ip4 + ip6 + late = true; # Only accept after any rejects have been processed + verdict = "accept"; + }; + + # Allow access to the AdGuardHome DNS server from any VLAN that has internet access + access-adguardhome-dns = { + from = map (name: "vlan-${name}") globals.general.internetVLANs; + to = [ "adguardhome" ]; + verdict = "accept"; + }; + + # Allow devices in the home VLAN to talk to any of the services or home devices. + access-services = { + from = [ "vlan-home" ]; + to = [ + "vlan-services" + "vlan-devices" + ]; + late = true; + verdict = "accept"; + }; + + # Allow the services VLAN to talk to our wireguard server + services-to-local = { + from = [ "vlan-services" ]; + to = [ "local" ]; + allowedUDPPorts = [ 52829 547 ]; + }; + + # Forward traffic between wireguard participants + forward-proxy-home-vpn-traffic = { + from = [ "wgHome" ]; + to = [ "wgHome" ]; + verdict = "accept"; + }; + }; + }; + + chains.postrouting = { + masquerade-internet = { + after = [ "hook" ]; + late = true; + rules = + lib.forEach + (map (name: "vlan-${name}") globals.general.internetVLANs) + ( + zone: + lib.concatStringsSep " " [ + "meta protocol { ip, ip6 }" + (lib.head config.networking.nftables.firewall.zones.${zone}.ingressExpression) + (lib.head config.networking.nftables.firewall.zones.untrusted.egressExpression) + "masquerade random" + ] + ); + }; + }; + }; + + boot.kernel.sysctl = { + "net.ipv4.ip_forward" = 1; + "net.ipv4.conf.all.forwarding" = true; + "net.ipv6.conf.all.forwarding" = true; + }; + + systemd.network = { + wait-online.anyInterface = true; + + netdevs = { + "10-veth" = { + netdevConfig = { + Kind = "veth"; + Name = "veth-br"; + }; + peerConfig = { + Name = "veth-int"; + }; + }; + "20-br" = { + netdevConfig = { + Kind = "bridge"; + Name = "br"; + }; + bridgeConfig = { + VLANFiltering = true; + }; + }; + }; + networks = { + "40-br" = { + matchConfig.Name = "br"; + bridgeConfig = { }; + linkConfig = { + ActivationPolicy = "always-up"; + RequiredForOnline = "no"; + }; + networkConfig = { + ConfigureWithoutCarrier = true; + LinkLocalAddressing = "no"; + }; + }; + "15-veth-br" = { + matchConfig.Name = "veth-br"; + + linkConfig = { + RequiredForOnline = "no"; + }; + + networkConfig = { + Bridge = "br"; + }; + inherit bridgeVLANs; + }; + "15-veth-int" = { + matchConfig.Name = "veth-int"; + + linkConfig = { + ActivationPolicy = "always-up"; + RequiredForOnline = "no"; + }; + + networkConfig = { + ConfigureWithoutCarrier = true; + LinkLocalAddressing = "no"; + }; + + vlan = map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans); + }; + # br + "30-lan1" = { + matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan1.mac; + linkConfig.RequiredForOnline = "enslaved"; + networkConfig = { + Bridge = "br"; + ConfigureWithoutCarrier = true; + }; + bridgeVLANs = lan1VLANs; + }; + # winters + "30-lan2" = { + matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan2.mac; + linkConfig.RequiredForOnline = "enslaved"; + networkConfig = { + Bridge = "br"; + ConfigureWithoutCarrier = true; + }; + bridgeVLANs = lan2VLANs; + }; + # summers + "30-lan3" = { + matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan3.mac; + linkConfig.RequiredForOnline = "enslaved"; + networkConfig = { + Bridge = "br"; + ConfigureWithoutCarrier = true; + }; + bridgeVLANs = lan3VLANs; + }; + # summers + "30-lan4" = { + matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan4.mac; + linkConfig.RequiredForOnline = "enslaved"; + networkConfig = { + Bridge = "br"; + ConfigureWithoutCarrier = true; + }; + bridgeVLANs = lan4VLANs; + }; + # lr + "30-lan5" = { + matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan5.mac; + linkConfig.RequiredForOnline = "enslaved"; + networkConfig = { + Bridge = "br"; + ConfigureWithoutCarrier = true; + }; + bridgeVLANs = lan5VLANs; + }; + } // lib.flip lib.concatMapAttrs globals.networks.home-lan.vlans ( + vlanName: vlanCfg: { + "40-me-${vlanName}" = lib.mkForce { + address = [ + vlanCfg.hosts.${config.node.name}.cidrv4 + vlanCfg.hosts.${config.node.name}.cidrv6 + ]; + matchConfig.Name = "me-${vlanName}"; + networkConfig = { + IPv4Forwarding = "yes"; + IPv6PrivacyExtensions = "yes"; + IPv6SendRA = true; + IPv6AcceptRA = false; + }; + ipv6Prefixes = [ + { + Prefix = vlanCfg.cidrv6; + } + ]; + ipv6SendRAConfig = { + Managed = true; # set RA M flag -> DHCPv6 for addresses + OtherInformation = true; # optional, for “other info” via DHCPv6 + }; + linkConfig.RequiredForOnline = "routable"; + }; + } + ); + + }; + + }; +} diff --git a/modules-clone/nixos/server/settings.nix b/modules-clone/nixos/server/settings.nix new file mode 100644 index 0000000..c9d393c --- /dev/null +++ b/modules-clone/nixos/server/settings.nix @@ -0,0 +1,39 @@ +{ lib, config, ... }: +let + inherit (config.swarselsystems) flakePath; +in +{ + + options.swarselmodules.server.general = lib.mkEnableOption "general setting on server"; + options.swarselsystems = { + shellAliases = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + }; + }; + config = lib.mkIf config.swarselmodules.server.general { + + environment.shellAliases = lib.recursiveUpdate + { + nswitch = "cd ${flakePath}; swarsel-deploy $(hostname) switch; cd -;"; + ntest = "cd ${flakePath}; swarsel-deploy $(hostname) test; cd -;"; + nboot = "cd ${flakePath}; swarsel-deploy $(hostname) boot; cd -;"; + ndry = "cd ${flakePath}; swarsel-deploy $(hostname) dry-activate; cd -;"; + } + config.swarselsystems.shellAliases; + + nixpkgs.config = lib.mkIf (!config.swarselsystems.isMicroVM) { + permittedInsecurePackages = [ + # matrix + "olm-3.2.16" + # sonarr + "aspnetcore-runtime-wrapped-6.0.36" + "aspnetcore-runtime-6.0.36" + "dotnet-sdk-wrapped-6.0.428" + "dotnet-sdk-6.0.428" + # + "SDL_ttf-2.0.11" + ]; + }; + }; +} diff --git a/modules-clone/nixos/server/shlink.nix b/modules-clone/nixos/server/shlink.nix new file mode 100644 index 0000000..fb552a3 --- /dev/null +++ b/modules-clone/nixos/server/shlink.nix @@ -0,0 +1,111 @@ +{ self, lib, config, dns, globals, confLib, ... }: +let + inherit (confLib.gen { name = "shlink"; port = 8081; dir = "/var/lib/shlink"; }) servicePort serviceName serviceDomain serviceDir serviceAddress proxyAddress4 proxyAddress6 topologyContainerName; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + containerRev = "sha256:1a697baca56ab8821783e0ce53eb4fb22e51bb66749ec50581adc0cb6d031d7a"; + + inherit (config.swarselsystems) sopsFile; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + swarselmodules.server = { + podman = true; + }; + + sops = { + secrets = { + shlink-api = { inherit sopsFile; }; + }; + + templates = { + "shlink-env" = { + content = '' + INITIAL_API_KEY=${config.sops.placeholder.shlink-api} + ''; + }; + }; + }; + + topology.nodes.${topologyContainerName}.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "${self}/files/topology-images/${serviceName}.png"; + }; + + virtualisation.oci-containers.containers.${serviceName} = { + image = "shlinkio/shlink@${containerRev}"; + environment = { + "DEFAULT_DOMAIN" = serviceDomain; + "PORT" = "${builtins.toString servicePort}"; + "USE_HTTPS" = "false"; + "DEFAULT_SHORT_CODES_LENGTH" = "4"; + "WEB_WORKER_NUM" = "1"; + "TASK_WORKER_NUM" = "1"; + }; + environmentFiles = [ + config.sops.templates.shlink-env.path + ]; + ports = [ "${builtins.toString servicePort}:${builtins.toString servicePort}" ]; + volumes = [ + "${serviceDir}/data:/etc/shlink/data" + ]; + }; + + systemd.tmpfiles.settings."11-shlink" = builtins.listToAttrs ( + map + (path: { + name = "${serviceDir}/${path}"; + value = { + d = { + group = "root"; + user = "1001"; + mode = "0750"; + }; + }; + }) [ + "data" + "data/cache" + "data/locks" + "data/log" + "data/proxies" + ] + ); + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = serviceDir; } + { directory = "/var/lib/containers"; } + ]; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/slink.nix b/modules-clone/nixos/server/slink.nix new file mode 100644 index 0000000..ae1ce6d --- /dev/null +++ b/modules-clone/nixos/server/slink.nix @@ -0,0 +1,123 @@ +{ lib, config, dns, globals, confLib, ... }: +let + inherit (confLib.gen { name = "slink"; port = 3000; dir = "/var/lib/slink"; }) servicePort serviceName serviceDomain serviceDir serviceAddress proxyAddress4 proxyAddress6 topologyContainerName; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + containerRev = "sha256:98b9442696f0a8cbc92f0447f54fa4bad227af5dcfd6680545fedab2ed28ddd9"; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + swarselmodules.server = { + podman = true; + }; + + topology.nodes.${topologyContainerName}.services.${serviceName} = { + name = lib.swarselsystems.toCapitalized serviceName; + info = "https://${serviceDomain}"; + icon = "services.not-available"; + }; + + virtualisation.oci-containers.containers.${serviceName} = { + image = "anirdev/slink@${containerRev}"; + environment = { + "ORIGIN" = "https://${serviceDomain}"; + "TZ" = config.repo.secrets.common.location.timezone; + "STORAGE_PROVIDER" = "local"; + "IMAGE_MAX_SIZE" = "50M"; + "USER_APPROVAL_REQUIRED" = "true"; + }; + ports = [ "${builtins.toString servicePort}:${builtins.toString servicePort}" ]; + volumes = [ + "${serviceDir}/var/data:/app/var/data" + "${serviceDir}/images:/app/slink/images" + ]; + }; + + systemd.tmpfiles.settings."12-slink" = builtins.listToAttrs ( + map + (path: { + name = "${serviceDir}/${path}"; + value = { + d = { + group = "root"; + user = "root"; + mode = "0750"; + }; + }; + }) [ + "var/data" + "images" + ] + ); + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [ + { directory = serviceDir; } + ]; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + nodes = + let + genNginx = toAddress: extraConfig: { + upstreams = { + ${serviceName} = { + servers = { + "${toAddress}:${builtins.toString servicePort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + + forceSSL = true; + acmeRoot = null; + oauth2 = { + enable = true; + allowedGroups = [ "slink_access" ]; + }; + inherit extraConfig; + locations = { + "/" = { + proxyPass = "http://${serviceName}"; + }; + "/image" = { + proxyPass = "http://${serviceName}"; + setOauth2Headers = false; + bypassAuth = true; + }; + }; + }; + }; + }; + in + { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = genNginx serviceAddress ""; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (genNginx homeServiceAddress nginxAccessRules); + }; + + }; +} diff --git a/modules-clone/nixos/server/snipe-it.nix b/modules-clone/nixos/server/snipe-it.nix new file mode 100644 index 0000000..3ebb784 --- /dev/null +++ b/modules-clone/nixos/server/snipe-it.nix @@ -0,0 +1,66 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (confLib.gen { name = "snipeit"; port = 80; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy webProxyIf homeProxyIf dnsServer homeServiceAddress nginxAccessRules; + # sopsFile = config.node.secretsDir + "/secrets2.yaml"; + inherit (config.swarselsystems) sopsFile; + + serviceDB = "snipeit"; + + mysqlPort = 3306; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops = { + secrets = { + snipe-it-appkey = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; }; + }; + }; + + topology.self.services.${serviceName}.info = "https://${serviceDomain}"; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ]; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy}.allowedTCPPorts = [ servicePort ]; + }; + }; + services.${serviceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + services.snipe-it = { + enable = true; + appKeyFile = config.sops.secrets.snipe-it-appkey.path; + appURL = "https://${serviceDomain}"; + hostName = serviceDomain; + user = serviceUser; + group = serviceGroup; + dataDir = "/var/lib/snipeit"; + database = { + user = serviceUser; + port = mysqlPort; + name = serviceDB; + host = "localhost"; + createLocally = true; + }; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { + "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain serviceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain serviceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/spotifyd.nix b/modules-clone/nixos/server/spotifyd.nix new file mode 100644 index 0000000..a392313 --- /dev/null +++ b/modules-clone/nixos/server/spotifyd.nix @@ -0,0 +1,54 @@ +{ lib, config, confLib, ... }: +let + inherit (confLib.gen { name = "spotifyd"; port = 1025; }) servicePort serviceName serviceUser serviceGroup; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + users.groups.${serviceGroup} = { + gid = 65136; + }; + + users.users.${serviceUser} = { + isSystemUser = true; + uid = 65136; + group = serviceGroup; + extraGroups = [ "audio" "utmp" "pipewire" ]; + }; + + networking.firewall.allowedTCPPorts = [ servicePort ]; + + services.pipewire.systemWide = true; + + # https://github.com/Spotifyd/spotifyd/issues/1366 + networking.hosts."0.0.0.0" = [ "apresolve.spotify.com" ]; + + # hacky way to enable multi-session + # when another user connects, the service will crash and the new user will login + systemd.services.spotifyd.serviceConfig.RestartSec = lib.mkForce 1; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/cache/private/spotifyd"; } + ]; + }; + + services.spotifyd = { + enable = true; + settings = { + global = { + dbus_type = "session"; + use_mpris = false; + device = "sysdefault:CARD=PCH"; + # device = "default"; + device_name = "SwarselSpot"; + # backend = "pulseaudio"; + backend = "alsa"; + # mixer = "alsa"; + zeroconf_port = servicePort; + }; + }; + }; + }; + +} diff --git a/modules-clone/nixos/server/ssh-builder.nix b/modules-clone/nixos/server/ssh-builder.nix new file mode 100644 index 0000000..1118bd6 --- /dev/null +++ b/modules-clone/nixos/server/ssh-builder.nix @@ -0,0 +1,44 @@ +{ self, pkgs, lib, config, confLib, ... }: +let + ssh-restrict = "restrict,pty,command=\"${wrapper-dispatch-ssh-nix}/bin/wrapper-dispatch-ssh-nix\" "; + + wrapper-dispatch-ssh-nix = pkgs.writeShellScriptBin "wrapper-dispatch-ssh-nix" '' + case $SSH_ORIGINAL_COMMAND in + "nix-daemon --stdio") + exec env NIX_SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt ${config.nix.package}/bin/nix-daemon --stdio + ;; + "nix-store --serve --write") + exec env NIX_SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt ${config.nix.package}/bin/nix-store --serve --write + ;; + *) + echo "Access only allowed for using the nix remote builder" 1>&2 + exit + esac + ''; +in +{ + options.swarselmodules.server.ssh-builder = lib.mkEnableOption "enable ssh-builder config on server"; + config = lib.mkIf config.swarselmodules.server.ssh-builder { + users = { + persistentIds.builder = confLib.mkIds 965; + groups.builder = { }; + users.builder = { + useDefaultShell = true; + isSystemUser = true; + group = "builder"; + openssh.authorizedKeys.keys = [ + ''${ssh-restrict} ${builtins.readFile "${self}/secrets/public/ssh/builder.pub"}'' + ]; + }; + }; + + services.openssh = { + settings = { + AllowUsers = [ + "builder" + ]; + }; + }; + + }; +} diff --git a/modules-clone/nixos/server/ssh.nix b/modules-clone/nixos/server/ssh.nix new file mode 100644 index 0000000..22a2204 --- /dev/null +++ b/modules-clone/nixos/server/ssh.nix @@ -0,0 +1,45 @@ +{ self, lib, config, withHomeManager, confLib, ... }: +{ + options.swarselmodules.server.ssh = lib.mkEnableOption "enable ssh on server"; + config = lib.mkIf config.swarselmodules.server.ssh { + services.openssh = { + enable = true; + startWhenNeeded = lib.mkForce false; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + PermitRootLogin = "yes"; + AllowUsers = [ + "root" + config.swarselsystems.mainUser + ]; + }; + hostKeys = [ + { + path = "/etc/ssh/ssh_host_ed25519_key"; + type = "ed25519"; + } + ]; + }; + users = { + persistentIds = { + sshd = lib.mkIf config.swarselmodules.server.ids (confLib.mkIds 979); + }; + users = { + "${config.swarselsystems.mainUser}".openssh.authorizedKeys.keyFiles = lib.mkIf withHomeManager [ + (self + /secrets/public/ssh/yubikey.pub) + (self + /secrets/public/ssh/magicant.pub) + # (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub)) + ]; + root.openssh.authorizedKeys.keyFiles = [ + (self + /secrets/public/ssh/yubikey.pub) + (self + /secrets/public/ssh/magicant.pub) + # (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub)) + ]; + }; + }; + security.sudo.extraConfig = '' + Defaults env_keep+=SSH_AUTH_SOCK + ''; + }; +} diff --git a/modules-clone/nixos/server/syncthing.nix b/modules-clone/nixos/server/syncthing.nix new file mode 100644 index 0000000..6998326 --- /dev/null +++ b/modules-clone/nixos/server/syncthing.nix @@ -0,0 +1,145 @@ +{ lib, config, globals, dns, confLib, ... }: +let + inherit (config.swarselsystems.syncthing) serviceDomain; + inherit (confLib.gen { name = "syncthing"; port = 8384; }) servicePort serviceName serviceUser serviceGroup serviceAddress proxyAddress4 proxyAddress6; + inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules; + + specificServiceName = "${serviceName}-${config.node.name}"; + + cfg = config.services.${serviceName}; + devices = config.swarselsystems.syncthing.syncDevices; +in +{ + options = { + swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server"; + + swarselsystems.syncthing = { + serviceDomain = lib.mkOption { + type = lib.types.str; + default = config.repo.secrets.common.services.domains.syncthing1; + }; + syncDevices = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "magicant" "winters" "pyramid" "moonside@oracle" ]; + }; + devices = lib.mkOption { + type = lib.types.attrs; + default = { + "magicant" = { + id = "VMWGEE2-4HDS2QO-KNQOVGN-LXLX6LA-666E4EK-ZBRYRRO-XFEX6FB-6E3XLQO"; + }; + "winters" = { + id = "O7RWDMD-AEAHPP7-7TAVLKZ-BSWNBTU-2VA44MS-EYGUNBB-SLHKB3C-ZSLMOAA"; + }; + "moonside@oracle" = { + id = "VPCDZB6-MGVGQZD-Q6DIZW3-IZJRJTO-TCC3QUQ-2BNTL7P-AKE7FBO-N55UNQE"; + }; + "pyramid" = { + id = "YAPV4BV-I26WPTN-SIP32MV-SQP5TBZ-3CHMTCI-Z3D6EP2-MNDQGLP-53FT3AB"; + }; + }; + }; + }; + }; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + users.users.${serviceUser} = { + extraGroups = [ "users" ]; + group = serviceGroup; + isSystemUser = true; + }; + + users.groups.${serviceGroup} = { }; + + # networking.firewall.allowedTCPPorts = [ servicePort ]; + + globals = { + networks = { + ${webProxyIf}.hosts = lib.mkIf isProxied { + ${config.node.name}.firewallRuleForNode.${webProxy} = { + allowedTCPPorts = [ servicePort 22000 ]; + allowedUDPPorts = [ 20000 21027 ]; + }; + }; + ${homeProxyIf}.hosts = lib.mkIf isHome { + ${config.node.name}.firewallRuleForNode.${homeWebProxy} = { + allowedTCPPorts = [ servicePort 20000 ]; + allowedUDPPorts = [ 20000 21027 ]; + }; + }; + }; + services.${specificServiceName} = { + domain = serviceDomain; + inherit proxyAddress4 proxyAddress6 isHome serviceAddress; + homeServiceAddress = lib.mkIf isHome homeServiceAddress; + }; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }]; + }; + + services.${serviceName} = rec { + enable = true; + user = serviceUser; + group = serviceGroup; + dataDir = if config.swarselsystems.isMicroVM then "/storage/Documents/syncthing" else (lib.mkDefault "/var/lib/${serviceName}"); + configDir = if config.swarselsystems.isMicroVM then "/var/lib/syncthing/.config/syncthing" else "${cfg.dataDir}/.config/${serviceName}"; + guiAddress = "0.0.0.0:${builtins.toString servicePort}"; + openDefaultPorts = lib.mkIf (!isProxied) true; # opens ports TCP/UDP 22000 and UDP 21027 for discovery + relay.enable = false; + settings = { + urAccepted = -1; + inherit (config.swarselsystems.syncthing) devices; + folders = { + "Default Folder" = lib.mkForce { + path = "${cfg.dataDir}/Sync"; + type = "receiveonly"; + versioning = null; + inherit devices; + id = "default"; + }; + "Obsidian" = { + path = "${cfg.dataDir}/Obsidian"; + type = "receiveonly"; + versioning = { + type = "simple"; + params.keep = "5"; + }; + inherit devices; + id = "yjvni-9eaa7"; + }; + "Org" = { + path = "${cfg.dataDir}/Org"; + type = "receiveonly"; + versioning = { + type = "simple"; + params.keep = "5"; + }; + inherit devices; + id = "a7xnl-zjj3d"; + }; + "Vpn" = { + path = "${cfg.dataDir}/Vpn"; + type = "receiveonly"; + versioning = { + type = "simple"; + params.keep = "5"; + }; + inherit devices; + id = "hgp9s-fyq3p"; + }; + }; + }; + }; + + nodes = { + ${dnsServer}.swarselsystems.server.dns.${globals.services.${specificServiceName}.baseDomain}.subdomainRecords = { + "${globals.services.${specificServiceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; + }; + ${webProxy}.services.nginx = confLib.genNginx { inherit serviceAddress servicePort serviceDomain; serviceName = specificServiceName; maxBody = 0; }; + ${homeWebProxy}.services.nginx = lib.mkIf isHome (confLib.genNginx { inherit servicePort serviceDomain; serviceName = specificServiceName; maxBody = 0; extraConfig = nginxAccessRules; serviceAddress = homeServiceAddress; }); + }; + + }; +} diff --git a/modules-clone/nixos/server/transmission.nix b/modules-clone/nixos/server/transmission.nix new file mode 100644 index 0000000..34d3a2e --- /dev/null +++ b/modules-clone/nixos/server/transmission.nix @@ -0,0 +1,248 @@ +{ self, pkgs, lib, config, confLib, ... }: +let + inherit (confLib.gen { name = "transmission"; port = 9091; }) serviceName servicePort serviceDomain; + inherit (confLib.static) isHome homeServiceAddress homeWebProxy nginxAccessRules; + inherit (config.swarselsystems) sopsFile; + + lidarrUser = "lidarr"; + lidarrGroup = lidarrUser; + lidarrPort = 8686; + radarrUser = "radarr"; + radarrGroup = radarrUser; + radarrPort = 7878; + sonarrUser = "sonarr"; + sonarrGroup = sonarrUser; + sonarrPort = 8989; + readarrUser = "readarr"; + readarrGroup = readarrUser; + readarrPort = 8787; + prowlarrUser = "prowlarr"; + prowlarrGroup = prowlarrUser; + prowlarrPort = 9696; +in +{ + options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} and friends on server"; + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + sops.secrets = { + pia = { inherit sopsFile; }; + }; + + # this user/group section is probably unneeded + users = { + persistentIds = { + prowlarr = confLib.mkIds 971; + readarr = confLib.mkIds 970; + }; + groups = { + dockeruser = { + gid = 1155; + }; + "${radarrGroup}" = { }; + "${readarrGroup}" = { }; + "${sonarrGroup}" = { }; + "${lidarrGroup}" = { }; + "${prowlarrGroup}" = { }; + }; + users = { + dockeruser = { + isSystemUser = true; + uid = 1155; + group = "docker"; + extraGroups = [ "users" ]; + }; + "${radarrUser}" = { + isSystemUser = true; + group = radarrGroup; + extraGroups = [ "users" ]; + }; + "${readarrGroup}" = { + isSystemUser = true; + group = readarrGroup; + extraGroups = [ "users" ]; + }; + "${sonarrGroup}" = { + isSystemUser = true; + group = sonarrGroup; + extraGroups = [ "users" ]; + }; + "${lidarrUser}" = { + isSystemUser = true; + group = lidarrGroup; + extraGroups = [ "users" ]; + }; + "${prowlarrGroup}" = { + isSystemUser = true; + group = prowlarrGroup; + extraGroups = [ "users" ]; + }; + }; + }; + + virtualisation.docker.enable = true; + environment.systemPackages = with pkgs; [ + docker + ]; + + topology.self.services = { + radarr.info = "https://${serviceDomain}/radarr"; + readarr = { + name = "Readarr"; + info = "https://${serviceDomain}/readarr"; + icon = "${self}/files/topology-images/readarr.png"; + }; + sonarr.info = "https://${serviceDomain}/sonarr"; + lidarr.info = "https://${serviceDomain}/lidarr"; + prowlarr.info = "https://${serviceDomain}/prowlarr"; + }; + + globals.services.transmission = { + domain = serviceDomain; + inherit isHome; + }; + + environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM { + directories = [ + { directory = "/var/lib/radarr"; user = radarrUser; group = radarrGroup; } + { directory = "/var/lib/readarr"; user = readarrUser; group = readarrGroup; } + { directory = "/var/lib/sonarr"; user = sonarrUser; group = sonarrGroup; } + { directory = "/var/lib/lidarr"; user = lidarrUser; group = lidarrGroup; } + { directory = "/var/lib/private/prowlarr"; user = prowlarrUser; group = prowlarrGroup; } + ]; + }; + + services = { + pia = { + enable = true; + credentials.credentialsFile = config.sops.secrets.pia.path; + protocol = "wireguard"; + autoConnect = { + enable = true; + region = "sweden"; + }; + portForwarding.enable = true; + dns.enable = true; + }; + radarr = { + enable = true; + user = radarrUser; + group = radarrGroup; + settings.server.port = radarrPort; + openFirewall = true; + dataDir = "/var/lib/radarr"; + }; + readarr = { + enable = true; + user = readarrUser; + group = readarrGroup; + settings.server.port = readarrPort; + openFirewall = true; + dataDir = "/var/lib/readarr"; + }; + sonarr = { + enable = true; + user = sonarrUser; + group = sonarrGroup; + settings.server.port = sonarrPort; + openFirewall = true; + dataDir = "/var/lib/sonarr"; + }; + lidarr = { + enable = true; + user = lidarrUser; + group = lidarrGroup; + settings.server.port = lidarrPort; + openFirewall = true; + dataDir = "/var/lib/lidarr"; + }; + prowlarr = { + enable = true; + settings.server.port = prowlarrPort; + openFirewall = true; + }; + }; + + nodes = { + ${homeWebProxy}.services.nginx = { + upstreams = { + transmission = { + servers = { + "${homeServiceAddress}:${builtins.toString servicePort}" = { }; + }; + }; + radarr = { + servers = { + "${homeServiceAddress}:${builtins.toString radarrPort}" = { }; + }; + }; + readarr = { + servers = { + "${homeServiceAddress}:${builtins.toString readarrPort}" = { }; + }; + }; + sonarr = { + servers = { + "${homeServiceAddress}:${builtins.toString sonarrPort}" = { }; + }; + }; + lidarr = { + servers = { + "${homeServiceAddress}:${builtins.toString lidarrPort}" = { }; + }; + }; + prowlarr = { + servers = { + "${homeServiceAddress}:${builtins.toString prowlarrPort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + enableACME = false; + forceSSL = false; + acmeRoot = null; + extraConfig = nginxAccessRules; + locations = { + "/" = { + proxyPass = "http://transmission"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + "/radarr" = { + proxyPass = "http://radarr"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + "/readarr" = { + proxyPass = "http://readarr"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + "/sonarr" = { + proxyPass = "http://sonarr"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + "/lidarr" = { + proxyPass = "http://lidarr"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + "/prowlarr" = { + proxyPass = "http://prowlarr"; + extraConfig = '' + client_max_body_size 0; + ''; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/modules-clone/nixos/server/wireguard.nix b/modules-clone/nixos/server/wireguard.nix new file mode 100644 index 0000000..dcfa71d --- /dev/null +++ b/modules-clone/nixos/server/wireguard.nix @@ -0,0 +1,304 @@ +{ self, lib, pkgs, config, confLib, nodes, globals, ... }: +let + inherit (confLib.gen { + name = "wireguard"; + port = 52829; + user = "systemd-network"; + group = "systemd-network"; + }) servicePort serviceName serviceUser serviceGroup; + + inherit (config.swarselsystems) sopsFile; + wgSopsFile = self + "/secrets/repo/wg.yaml"; + + cfg = config.swarselsystems.server.wireguard; + inherit (cfg) interfaces; + ifaceList = builtins.attrValues interfaces; +in +{ + options = { + swarselmodules.server.${serviceName} = + lib.mkEnableOption "enable ${serviceName} settings"; + + swarselsystems.server.wireguard = { + interfaces = + let + topConfig = config; + in + lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({ config, name, ... }: { + options = { + isServer = lib.mkEnableOption "set this interface as a wireguard server"; + isClient = lib.mkEnableOption "set this interface as a wireguard client"; + + serverName = lib.mkOption { + type = lib.types.str; + default = if config.isServer then topConfig.node.name else ""; + description = "Hostname of the WireGuard server this interface connects to (when isClient = true)."; + }; + + serverNetConfigPrefix = lib.mkOption { + type = lib.types.str; + default = + let + serverCfg = nodes.${config.serverName}.config; + in + if serverCfg.swarselsystems.isCloud + then serverCfg.node.name + else "home"; + readOnly = true; + description = "Prefix used to look up the server network in globals.networks.\"-wg\"."; + }; + + ifName = lib.mkOption { + type = lib.types.str; + default = name; + description = "Name of the WireGuard interface."; + }; + + port = lib.mkOption { + type = lib.types.int; + default = servicePort; + description = "Port of the WireGuard interface."; + }; + + peers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = lib.attrNames (lib.filterAttrs (name: _: name != topConfig.node.name) globals.networks."${config.serverNetConfigPrefix}-${config.ifName}".hosts); + description = "WireGuard peer config names of this wireguardinterface."; + }; + }; + })); + default = { }; + description = "WireGuard interfaces defined on this host."; + }; + }; + }; + + config = lib.mkIf config.swarselmodules.server.${serviceName} { + + assertions = lib.concatLists ( + lib.flip lib.mapAttrsToList interfaces ( + ifName: ifCfg: + let + assertionPrefix = "While evaluating the wireguard network ${ifName}:"; + in + [ + { + assertion = ifCfg.isServer || (ifCfg.isClient && ifCfg.serverName != ""); + message = "${assertionPrefix}: This node must either be a server for the wireguard network or a client with serverName set."; + } + { + assertion = lib.stringLength ifName < 16; + message = "${assertionPrefix}: The specified linkName '${ifName}' is too long (must be max 15 characters)."; + } + ] + ) + ); + + topology.self.interfaces = lib.mapAttrs' + (wgName: _: + lib.nameValuePair "${wgName}" { + network = wgName; + } + ) + config.swarselsystems.server.wireguard.interfaces; + + environment.systemPackages = with pkgs; [ + wireguard-tools + ]; + + sops.secrets = + lib.mkMerge ( + [ + { + wireguard-private-key = { + inherit sopsFile; + owner = serviceUser; + group = serviceGroup; + mode = "0600"; + }; + } + ] ++ (map + (i: + let + clientSecrets = + lib.optionalAttrs i.isClient { + "wireguard-${i.serverName}-${config.node.name}-${i.ifName}-presharedKey" = { + sopsFile = wgSopsFile; + owner = serviceUser; + group = serviceGroup; + mode = "0600"; + }; + }; + + serverSecrets = + lib.optionalAttrs i.isServer (builtins.listToAttrs (map + (clientName: { + name = "wireguard-${config.node.name}-${clientName}-${i.ifName}-presharedKey"; + value = { + sopsFile = wgSopsFile; + owner = serviceUser; + group = serviceGroup; + mode = "0600"; + }; + }) + i.peers)); + in + clientSecrets // serverSecrets + ) + ifaceList) + ); + + networking.firewall = { + checkReversePath = lib.mkIf (lib.any (i: i.isClient) ifaceList) "loose"; + allowedUDPPorts = lib.mkMerge ( + lib.flip lib.mapAttrsToList interfaces ( + _: ifCfg: + lib.optional ifCfg.isServer ifCfg.port + ) + ); + }; + + networking.nftables.firewall = { + zones = lib.mkMerge + ( + lib.flip lib.mapAttrsToList interfaces ( + ifName: ifCfg: + { + ${ifName}.interfaces = [ ifName ]; + } + // lib.listToAttrs (map + (peer: + let + peerNet = globals.networks."${ifCfg.serverNetConfigPrefix}-${ifName}".hosts.${peer}; + in + lib.nameValuePair "${ifName}-node-${peer}" { + parent = ifName; + ipv4Addresses = lib.optional (peerNet.ipv4 != null) peerNet.ipv4; + ipv6Addresses = lib.optional (peerNet.ipv6 != null) peerNet.ipv6; + } + ) + ifCfg.peers) + ) + ); + rules = lib.mkMerge ( + lib.flip lib.mapAttrsToList interfaces ( + ifName: ifCfg: + let + inherit (config.networking.nftables.firewall) localZoneName; + netCfg = globals.networks."${ifCfg.serverNetConfigPrefix}-${ifName}"; + in + { + "${ifName}-to-${localZoneName}" = { + inherit (netCfg.firewallRuleForAll) allowedTCPPorts allowedUDPPorts allowedTCPPortRanges allowedUDPPortRanges; + from = [ ifName ]; + to = [ localZoneName ]; + ignoreEmptyRule = true; + }; + } + // lib.listToAttrs (map + (peer: + lib.nameValuePair "${ifName}-node-${peer}-to-${localZoneName}" ( + lib.mkIf (netCfg.hosts.${config.node.name}.firewallRuleForNode ? ${peer}) { + inherit (netCfg.hosts.${config.node.name}.firewallRuleForNode.${peer}) allowedTCPPorts allowedTCPPortRanges allowedUDPPorts allowedUDPPortRanges; + from = [ "${ifName}-node-${peer}" ]; + to = [ localZoneName ]; + ignoreEmptyRule = true; + } + ) + ) + ifCfg.peers) + ) + ); + }; + + systemd.network = { + enable = true; + + networks = lib.mkMerge (map + (i: + let + inherit (i) ifName; + in + { + "50-${ifName}" = { + matchConfig.Name = ifName; + linkConfig = { + MTUBytes = 1408; # TODO: figure out where we lose those 12 bits (8 from pppoe maybe + ???) + }; + + address = [ + globals.networks."${i.serverNetConfigPrefix}-${ifName}".hosts.${config.node.name}.cidrv4 + globals.networks."${i.serverNetConfigPrefix}-${ifName}".hosts.${config.node.name}.cidrv6 + ]; + }; + }) + ifaceList); + + netdevs = lib.mkMerge (map + (i: + let + inherit (i) ifName; + in + { + "50-${ifName}" = { + netdevConfig = { + Kind = "wireguard"; + Name = ifName; + }; + + wireguardConfig = { + ListenPort = lib.mkIf i.isServer servicePort; + + PrivateKeyFile = config.sops.secrets.wireguard-private-key.path; + + RouteTable = lib.mkIf i.isClient "main"; + }; + + wireguardPeers = + lib.optionals i.isClient [ + { + PublicKey = + builtins.readFile "${self}/secrets/public/wg/${i.serverName}.pub"; + + PresharedKeyFile = + config.sops.secrets."wireguard-${i.serverName}-${config.node.name}-${i.ifName}-presharedKey".path; + + Endpoint = + "server.${i.serverName}.${globals.domains.main}:${toString servicePort}"; + + PersistentKeepalive = 25; + + AllowedIPs = + let + wgNetwork = globals.networks."${i.serverNetConfigPrefix}-${i.ifName}"; + in + (lib.optional (wgNetwork.cidrv4 != null) wgNetwork.cidrv4) + ++ (lib.optional (wgNetwork.cidrv6 != null) wgNetwork.cidrv6); + } + ] + ++ lib.optionals i.isServer (map + (clientName: { + PublicKey = + builtins.readFile "${self}/secrets/public/wg/${clientName}.pub"; + + PresharedKeyFile = + config.sops.secrets."wireguard-${i.serverName}-${clientName}-${i.ifName}-presharedKey".path; + + AllowedIPs = + let + clientInWgNetwork = + globals.networks."${i.serverNetConfigPrefix}-${i.ifName}".hosts.${clientName}; + in + (lib.optional (clientInWgNetwork.ipv4 != null) + (lib.net.cidr.make 32 clientInWgNetwork.ipv4)) + ++ (lib.optional (clientInWgNetwork.ipv6 != null) + (lib.net.cidr.make 128 clientInWgNetwork.ipv6)); + }) + i.peers); + }; + }) + ifaceList); + }; + }; +} diff --git a/modules-clone/shared/config-lib.nix b/modules-clone/shared/config-lib.nix new file mode 100644 index 0000000..9fbea0a --- /dev/null +++ b/modules-clone/shared/config-lib.nix @@ -0,0 +1,225 @@ +{ self, config, lib, globals, inputs, outputs, minimal, ... }: +let + domainDefault = service: config.repo.secrets.common.services.domains.${service}; + proxyDefault = config.swarselsystems.proxyHost; + + addressDefault = + if + config.swarselsystems.proxyHost != config.node.name + then + if + config.swarselsystems.server.wireguard.interfaces.wgProxy.isClient + then + globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgProxy.serverNetConfigPrefix}-wgProxy".hosts.${config.node.name}.ipv4 + else + globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.ipv4 + else + "localhost"; +in +{ + _module.args = { + confLib = rec { + getConfig = config; + + gen = { name ? "n/a", user ? name, group ? user, dir ? null, port ? null, domain ? (domainDefault name), address ? addressDefault, proxy ? proxyDefault }: rec { + servicePort = port; + serviceName = name; + specificServiceName = "${name}-${config.node.name}"; + serviceUser = user; + serviceGroup = group; + serviceDomain = domain; + baseDomain = lib.swarselsystems.getBaseDomain domain; + subDomain = lib.swarselsystems.getSubDomain domain; + serviceDir = dir; + serviceAddress = address; + serviceProxy = proxy; + serviceNode = config.node.name; + topologyContainerName = "${serviceNode}-${config.virtualisation.oci-containers.backend}-${name}"; + proxyAddress4 = globals.hosts.${proxy}.wanAddress4 or null; + proxyAddress6 = globals.hosts.${proxy}.wanAddress6 or null; + }; + + static = rec { + inherit (globals.hosts.${config.node.name}) isHome; + inherit (globals.general) homeProxy webProxy dnsServer homeDnsServer homeWebProxy idmServer oauthServer; + webProxyIf = "${webProxy}-wgProxy"; + homeProxyIf = "home-wgHome"; + isProxied = config.node.name != webProxy; + nginxAccessRules = '' + allow ${globals.networks.home-lan.vlans.home.cidrv4}; + allow ${globals.networks.home-lan.vlans.home.cidrv6}; + allow ${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv4}; + allow ${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv6}; + deny all; + ''; + homeServiceAddress = lib.optionalString (config.swarselsystems.server.wireguard.interfaces ? wgHome) globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgHome.serverNetConfigPrefix}-wgHome".hosts.${config.node.name}.ipv4; + }; + + mkIds = id: { + uid = id; + gid = id; + }; + + mkDeviceMac = id: + let + mod = n: d: n - (n / d) * d; + toHexByte = n: + let + hex = "0123456789abcdef"; + hi = n / 16; + lo = mod n 16; + in + builtins.substring hi 1 hex + + builtins.substring lo 1 hex; + + max = 16777215; # 256^3 - 1 + + b1 = id / (256 * 256); + r1 = mod id (256 * 256); + b2 = r1 / 256; + b3 = mod r1 256; + in + if + (id <= max) + then + (builtins.concatStringsSep ":" + (map toHexByte [ b1 b2 b3 ])) + else + (throw "Device MAC ID too large (max is 16777215)"); + + mkMicrovm = + if config.swarselsystems.withMicroVMs then + (guestName: + { eternorPaths ? [ ] + , withZfs ? false + , ... + }: + { + ${guestName} = + { + backend = "microvm"; + autostart = true; + zfs = lib.mkIf withZfs + ({ + # stateful config usually bind-mounted to /var/lib/ that should be backed up remotely + "/state" = { + pool = "Vault"; + dataset = "guests/${guestName}/state"; + }; + # other stuff that should only reside on zfs, not backed up remotely + "/persist" = { + pool = "Vault"; + dataset = "guests/${guestName}/persist"; + }; + } // lib.optionalAttrs (eternorPaths != [ ]) + (lib.listToAttrs (map + # data that is pulled in externally by services, some of which is backed up externally + (eternorPath: + lib.nameValuePair "/storage/${eternorPath}" { + pool = "Vault"; + dataset = "Eternor/${eternorPath}"; + }) + eternorPaths))); + modules = [ + (config.node.configDir + /guests/${guestName}/default.nix) + { + node.secretsDir = config.node.configDir + /secrets/${guestName}; + node.configDir = config.node.configDir + /guests/${guestName}; + networking.nftables.firewall = { + zones.untrusted.interfaces = lib.mkIf + ( + lib.length config.guests.${guestName}.networking.links == 1 + ) + config.guests.${guestName}.networking.links; + }; + + fileSystems = { + "/persist".neededForBoot = true; + } // lib.optionalAttrs withZfs { + "/state".neededForBoot = true; + }; + } + "${self}/modules/nixos/optional/microvm-guest.nix" + "${self}/modules/nixos/optional/systemd-networkd-base.nix" + ]; + microvm = { + system = config.node.arch; + baseMac = config.repo.secrets.local.networking.networks.lan.mac; + interfaces.vlan-services = { + mac = lib.mkForce "02:${lib.substring 3 5 config.guests.${guestName}.microvm.baseMac}:${mkDeviceMac globals.networks.home-lan.vlans.services.hosts."${config.node.name}-${guestName}".id}"; + + }; + }; + extraSpecialArgs = { + inherit (inputs.self) nodes; + inherit (inputs.self.pkgs.${config.node.arch}) lib; + inherit inputs outputs minimal; + inherit (inputs) self; + withHomeManager = false; + microVMParent = config.node.name; + globals = inputs.self.globals.${config.node.arch}; + }; + }; + }) else + (_: { + _ = { }; + }); + + overrideTarget = target: { + Unit = { + PartOf = lib.mkForce [ target ]; + After = lib.mkForce [ target ]; + }; + Install.WantedBy = lib.mkForce [ target ]; + }; + + genNginx = + { serviceAddress + , serviceName + , serviceDomain + , servicePort + , protocol ? "http" + , maxBody ? (-1) + , maxBodyUnit ? "" + , noSslVerify ? false + , proxyWebsockets ? false + , oauth2 ? false + , oauth2Groups ? [ ] + , extraConfig ? "" + , extraConfigLoc ? "" + }: { + upstreams = { + ${serviceName} = { + servers = { + "${serviceAddress}:${builtins.toString servicePort}" = { }; + }; + }; + }; + virtualHosts = { + "${serviceDomain}" = { + useACMEHost = globals.domains.main; + forceSSL = true; + acmeRoot = null; + oauth2 = { + enable = lib.mkIf oauth2 true; + allowedGroups = lib.mkIf (oauth2Groups != [ ]) oauth2Groups; + }; + locations = { + "/" = { + proxyPass = "${protocol}://${serviceName}"; + proxyWebsockets = lib.mkIf proxyWebsockets true; + extraConfig = lib.optionalString (maxBody != (-1)) '' + client_max_body_size ${builtins.toString maxBody}${maxBodyUnit}; + '' + extraConfigLoc; + }; + }; + extraConfig = lib.optionalString noSslVerify '' + proxy_ssl_verify off; + '' + extraConfig; + }; + }; + }; + + }; + }; +} diff --git a/modules-clone/shared/meta.nix b/modules-clone/shared/meta.nix new file mode 100644 index 0000000..f37cc0f --- /dev/null +++ b/modules-clone/shared/meta.nix @@ -0,0 +1,30 @@ +{ lib, ... }: +{ + options = { + node = { + secretsDir = lib.mkOption { + description = "Path to the secrets directory for this node."; + type = lib.types.path; + default = ./.; + }; + configDir = lib.mkOption { + description = "Path to the base directory for this node."; + type = lib.types.path; + default = ./.; + }; + name = lib.mkOption { + type = lib.types.str; + }; + arch = lib.mkOption { + type = lib.types.str; + }; + type = lib.mkOption { + type = lib.types.str; + }; + lockFromBootstrapping = lib.mkOption { + description = "Whether this host should be marked to not be bootstrapped again using swarsel-bootstrap."; + type = lib.types.bool; + }; + }; + }; +} diff --git a/modules-clone/shared/options.nix b/modules-clone/shared/options.nix new file mode 100644 index 0000000..baacc6e --- /dev/null +++ b/modules-clone/shared/options.nix @@ -0,0 +1,105 @@ +{ self, config, lib, ... }: +{ + options.swarselsystems = { + proxyHost = lib.mkOption { + type = lib.types.str; + default = config.node.name; + }; + isBastionTarget = lib.mkOption { + type = lib.types.bool; + default = false; + }; + isCloud = lib.mkOption { + type = lib.types.bool; + default = false; + }; + isServer = lib.mkOption { + type = lib.types.bool; + default = config.swarselsystems.isCloud; + }; + isClient = lib.mkOption { + type = lib.types.bool; + default = config.swarselsystems.isLaptop; + }; + isMicroVM = lib.mkOption { + type = lib.types.bool; + default = false; + }; + isSwap = lib.mkOption { + type = lib.types.bool; + default = true; + }; + writeGlobalNetworks = lib.mkOption { + type = lib.types.bool; + default = true; + }; + swapSize = lib.mkOption { + type = lib.types.str; + default = "8G"; + }; + rootDisk = lib.mkOption { + type = lib.types.str; + default = ""; + }; + # @ future me: dont put this under server prefix + # home-manager would then try to import all swarselsystems.server.* options + localVLANs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + # @ future me: dont put this under server prefix + # home-manager would then try to import all swarselsystems.server.* options + initrdVLAN = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + mainUser = lib.mkOption { + type = lib.types.str; + default = "swarsel"; + }; + isCrypted = lib.mkEnableOption "uses full disk encryption"; + withMicroVMs = lib.mkEnableOption "enable MicroVMs on this host"; + + isImpermanence = lib.mkEnableOption "use impermanence on this system"; + isSecureBoot = lib.mkEnableOption "use secure boot on this system"; + isLaptop = lib.mkEnableOption "laptop host"; + isNixos = lib.mkEnableOption "nixos host"; + isPublic = lib.mkEnableOption "is a public machine (no secrets)"; + isDarwin = lib.mkEnableOption "darwin host"; + isLinux = lib.mkEnableOption "whether this is a linux machine"; + isBtrfs = lib.mkEnableOption "use btrfs filesystem"; + sopsFile = lib.mkOption { + type = lib.types.either lib.types.str lib.types.path; + # default = (if config.swarselsystems.isImpermanence then "/persist" else "") + config.node.secretsDir + "/secrets.yaml"; + default = config.node.secretsDir + "/secrets.yaml"; + }; + homeDir = lib.mkOption { + type = lib.types.str; + default = "/home/swarsel"; + }; + xdgDir = lib.mkOption { + type = lib.types.str; + default = "/run/user/1000"; + }; + flakePath = lib.mkOption { + type = lib.types.str; + default = "/home/swarsel/.dotfiles"; + }; + wallpaper = lib.mkOption { + type = lib.types.path; + default = "${self}/files/wallpaper/landscape/lenovowp.png"; + }; + sharescreen = lib.mkOption { + type = lib.types.str; + default = ""; + }; + lowResolution = lib.mkOption { + type = lib.types.str; + default = ""; + }; + highResolution = lib.mkOption { + type = lib.types.str; + default = ""; + }; + }; +} diff --git a/modules-clone/shared/vars.nix b/modules-clone/shared/vars.nix new file mode 100644 index 0000000..869d26c --- /dev/null +++ b/modules-clone/shared/vars.nix @@ -0,0 +1,243 @@ +{ self, pkgs, ... }: +{ + _module.args = { + vars = rec { + waylandSessionVariables = { + ANKI_WAYLAND = "1"; + MOZ_ENABLE_WAYLAND = "1"; + MOZ_WEBRENDER = "1"; + NIXOS_OZONE_WL = "1"; + OBSIDIAN_USE_WAYLAND = "1"; + QT_QPA_PLATFORM = "wayland-egl"; + QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; + SDL_VIDEODRIVER = "wayland"; + _JAVA_AWT_WM_NONREPARENTING = "1"; + }; + + waylandExports = + let + renderedWaylandExports = map (key: "export ${key}=${waylandSessionVariables.${key}};") (builtins.attrNames waylandSessionVariables); + in + builtins.concatStringsSep "\n" renderedWaylandExports; + + stylix = { + polarity = "dark"; + opacity.popups = 0.5; + cursor = { + package = pkgs.banana-cursor; + # package = pkgs.capitaine-cursors; + name = "Banana"; + # name = "capitaine-cursors"; + size = 16; + }; + fonts = { + sizes = { + terminal = 10; + applications = 11; + }; + serif = { + # package = (pkgs.nerdfonts.override { fonts = [ "FiraMono" "FiraCode"]; }); + # package = pkgs.cantarell-fonts; + # package = pkgs.montserrat; + # name = "Cantarell"; + package = pkgs.iosevka-bin.override { variant = "Aile"; }; + name = "Iosevka Aile"; + # name = "FiraCode Nerd Font Propo"; + # name = "Montserrat"; + }; + sansSerif = { + # package = (pkgs.nerdfonts.override { fonts = [ "FiraMono" "FiraCode"]; }); + # package = pkgs.cantarell-fonts; + # package = pkgs.montserrat; + # name = "Cantarell"; + package = pkgs.iosevka-bin.override { variant = "Aile"; }; + name = "Iosevka Aile"; + # name = "FiraCode Nerd Font Propo"; + # name = "Montserrat"; + }; + monospace = { + package = pkgs.nerd-fonts.fira-code; # has overrides + name = "FiraCode Nerd Font"; + }; + emoji = { + package = pkgs.noto-fonts-color-emoji; + name = "Noto Color Emoji"; + }; + }; + }; + + stylixHomeTargets = { + emacs.enable = false; + waybar.enable = false; + sway.useWallpaper = false; + spicetify.enable = true; + firefox.profileNames = [ "default" ]; + }; + + firefox = { + userChrome = builtins.readFile "${self}/files/firefox/chrome/userChrome.css"; + extensions = { + packages = with pkgs.nur.repos.rycee.firefox-addons; [ + tridactyl + tampermonkey + sidebery + browserpass + clearurls + darkreader + # enhancer-for-youtube + istilldontcareaboutcookies + translate-web-pages + ublock-origin + reddit-enhancement-suite + sponsorblock + web-archives + onepassword-password-manager + single-file + widegithub + enhanced-github + unpaywall + don-t-fuck-with-paste + # plasma-integration + noscript + + # configure a shortcut 'ctrl+shift+c' with behaviour 'do nothing' in order to disable the dev console shortcut + # (buildFirefoxXpiAddon { + # pname = "shortkeys"; + # version = "4.0.2"; + # addonId = "Shortkeys@Shortkeys.com"; + # url = "https://addons.mozilla.org/firefox/downloads/file/3673761/shortkeys-4.0.2.xpi"; + # sha256 = "c6fe12efdd7a871787ac4526eea79ecc1acda8a99724aa2a2a55c88a9acf467c"; + # meta = with lib; + # { + # description = "Easily customizable custom keyboard shortcuts for Firefox. To configure this addon go to Addons (ctrl+shift+a) ->Shortkeys ->Options. Report issues here (please specify that the issue is found in Firefox): https://github.com/mikecrittenden/shortkeys"; + # mozPermissions = [ + # "tabs" + # "downloads" + # "clipboardWrite" + # "browsingData" + # "storage" + # "bookmarks" + # "sessions" + # "" + # ]; + # platforms = platforms.all; + # }; + # }) + ]; + }; + + settings = + { + "extensions.autoDisableScopes" = 0; + "browser.bookmarks.showMobileBookmarks" = true; + "browser.autofocus" = false; + "toolkit.legacyUserProfileCustomizations.stylesheets" = true; + "browser.search.suggest.enabled" = false; + "browser.search.suggest.enabled.private" = false; + "browser.urlbar.suggest.searches" = false; + "browser.urlbar.showSearchSuggestionsFirst" = false; + "browser.topsites.contile.enabled" = false; + "browser.newtabpage.activity-stream.feeds.section.topstories" = false; + "browser.newtabpage.activity-stream.feeds.snippets" = false; + "browser.newtabpage.activity-stream.section.highlights.includePocket" = false; + "browser.newtabpage.activity-stream.section.highlights.includeBookmarks" = false; + "browser.newtabpage.activity-stream.section.highlights.includeDownloads" = false; + "browser.newtabpage.activity-stream.section.highlights.includeVisited" = false; + "browser.newtabpage.activity-stream.showSponsored" = false; + "browser.newtabpage.activity-stream.system.showSponsored" = false; + "browser.newtabpage.activity-stream.showSponsoredTopSites" = false; + }; + + search = { + # default = "Kagi"; + default = "google"; + # privateDefault = "Kagi"; + privateDefault = "google"; + engines = { + "Kagi" = { + urls = [{ + template = "https://kagi.com/search"; + params = [ + { name = "q"; value = "{searchTerms}"; } + ]; + }]; + icon = "https://kagi.com/favicon.ico"; + updateInterval = 24 * 60 * 60 * 1000; # every day + definedAliases = [ "@k" ]; + }; + + "Nix Packages" = { + urls = [{ + template = "https://search.nixos.org/packages"; + params = [ + { name = "type"; value = "packages"; } + { name = "query"; value = "{searchTerms}"; } + ]; + }]; + icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; + definedAliases = [ "@np" ]; + }; + + "NixOS Wiki" = { + urls = [{ + template = "https://nixos.wiki/index.php?search={searchTerms}"; + }]; + icon = "https://nixos.wiki/favicon.png"; + updateInterval = 24 * 60 * 60 * 1000; # every day + definedAliases = [ "@nw" ]; + }; + + "NixOS Options" = { + urls = [{ + template = "https://search.nixos.org/options"; + params = [ + { name = "query"; value = "{searchTerms}"; } + ]; + }]; + + icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; + definedAliases = [ "@no" ]; + }; + + "Home Manager Options" = { + urls = [{ + template = "https://home-manager-options.extranix.com/"; + params = [ + { name = "query"; value = "{searchTerms}"; } + ]; + }]; + + icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; + definedAliases = [ "@hm" "@ho" "@hmo" ]; + }; + + "Confluence search" = { + urls = [{ + template = "https://vbc.atlassian.net/wiki/search"; + params = [ + { name = "text"; value = "{searchTerms}"; } + ]; + }]; + + definedAliases = [ "@c" "@cf" "@confluence" ]; + }; + + "Jira search" = { + urls = [{ + template = "https://vbc.atlassian.net/issues/"; + params = [ + { name = "jql"; value = "textfields ~ \"{searchTerms}*\"&wildcardFlag=true"; } + ]; + }]; + + definedAliases = [ "@j" "@jire" ]; + }; + + "google".metaData.alias = "@g"; + }; + force = true; # this is required because otherwise the search.json.mozlz4 symlink gets replaced on every firefox restart + }; + }; + }; + }; +} diff --git a/modules/nixos/common/boot.nix b/modules/nixos/common/boot.nix index 758f29c..bb90c87 100644 --- a/modules/nixos/common/boot.nix +++ b/modules/nixos/common/boot.nix @@ -2,6 +2,7 @@ { options.swarselmodules.boot = lib.mkEnableOption "boot config"; config = lib.mkIf config.swarselmodules.boot { + boot = { initrd.systemd = { enable = true; diff --git a/modules/nixos/server/disk-encrypt.nix b/modules/nixos/server/disk-encrypt.nix index cdc4823..330d98f 100644 --- a/modules/nixos/server/disk-encrypt.nix +++ b/modules/nixos/server/disk-encrypt.nix @@ -30,8 +30,7 @@ in ''; deps = [ "users" - "createPersistentStorageDirs" - ]; + ] ++ lib.optional config.swarselsystems.isImpermanence "createPersistentStorageDirs"; }; environment.persistence."/persist" = lib.mkIf (config.swarselsystems.isImpermanence && (config.swarselprofiles.server || minimal)) { diff --git a/profiles-clone/home/default.nix b/profiles-clone/home/default.nix new file mode 100644 index 0000000..8f72c0d --- /dev/null +++ b/profiles-clone/home/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + profileNames = lib.swarselsystems.readNix "profiles-clone/home"; +in +{ + imports = lib.swarselsystems.mkImports profileNames "profiles-clone/home"; +} diff --git a/profiles-clone/home/dgxspark/default.nix b/profiles-clone/home/dgxspark/default.nix new file mode 100644 index 0000000..c4d63bb --- /dev/null +++ b/profiles-clone/home/dgxspark/default.nix @@ -0,0 +1,30 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.dgxspark = lib.mkEnableOption "is this a dgx spark host"; + config = lib.mkIf config.swarselprofiles.dgxspark { + swarselmodules = { + atuin = lib.mkDefault true; + bash = lib.mkDefault true; + blueman-applet = lib.mkDefault true; + direnv = lib.mkDefault true; + eza = lib.mkDefault true; + firefox = lib.mkDefault true; + fuzzel = lib.mkDefault true; + general = lib.mkDefault true; + git = lib.mkDefault true; + gpgagent = lib.mkDefault true; + kitty = lib.mkDefault true; + nix-index = lib.mkDefault true; + nixgl = lib.mkDefault true; + nix-your-shell = lib.mkDefault true; + nm-applet = lib.mkDefault true; + sops = lib.mkDefault true; + starship = lib.mkDefault true; + stylix = lib.mkDefault true; + tmux = lib.mkDefault true; + zellij = lib.mkDefault true; + zsh = lib.mkDefault true; + }; + }; + +} diff --git a/profiles-clone/home/hotel/default.nix b/profiles-clone/home/hotel/default.nix new file mode 100644 index 0000000..8a81bcb --- /dev/null +++ b/profiles-clone/home/hotel/default.nix @@ -0,0 +1,18 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.hotel = lib.mkEnableOption "is this a hotel host"; + config = lib.mkIf config.swarselprofiles.hotel { + swarselprofiles.personal = true; + swarselmodules = { + yubikey = lib.mkForce false; + ssh = lib.mkForce false; + env = lib.mkForce false; + git = lib.mkForce false; + mail = lib.mkForce false; + emacs = lib.mkForce false; + obsidian = lib.mkForce false; + gammastep = lib.mkForce false; + }; + }; + +} diff --git a/profiles-clone/home/localserver/default.nix b/profiles-clone/home/localserver/default.nix new file mode 100644 index 0000000..d906701 --- /dev/null +++ b/profiles-clone/home/localserver/default.nix @@ -0,0 +1,13 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.server.local = lib.mkEnableOption "is this a local server"; + config = lib.mkIf config.swarselprofiles.server.local { + swarselmodules = { + general = lib.mkDefault true; + server = { + dotfiles = lib.mkDefault true; + }; + }; + }; + +} diff --git a/profiles-clone/home/minimal/default.nix b/profiles-clone/home/minimal/default.nix new file mode 100644 index 0000000..bea6b11 --- /dev/null +++ b/profiles-clone/home/minimal/default.nix @@ -0,0 +1,14 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.minimal = lib.mkEnableOption "is this a personal host"; + config = lib.mkIf config.swarselprofiles.minimal { + swarselmodules = { + general = lib.mkDefault true; + sops = lib.mkDefault true; + kitty = lib.mkDefault true; + zsh = lib.mkDefault true; + git = lib.mkDefault true; + }; + }; + +} diff --git a/profiles-clone/home/personal/default.nix b/profiles-clone/home/personal/default.nix new file mode 100644 index 0000000..16dfec5 --- /dev/null +++ b/profiles-clone/home/personal/default.nix @@ -0,0 +1,71 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.personal = lib.mkEnableOption "is this a personal host"; + config = lib.mkIf config.swarselprofiles.personal { + swarselmodules = { + anki = lib.mkDefault true; + anki-tray = lib.mkDefault true; + attic-store-push = lib.mkDefault true; + atuin = lib.mkDefault true; + autotiling = lib.mkDefault false; # niri + batsignal = lib.mkDefault false; # niri + blueman-applet = lib.mkDefault true; + desktop = lib.mkDefault true; + direnv = lib.mkDefault true; + element-desktop = lib.mkDefault true; + element-tray = lib.mkDefault true; + emacs = lib.mkDefault true; + env = lib.mkDefault true; + eza = lib.mkDefault true; + firefox = lib.mkDefault true; + firezone-tray = lib.mkDefault true; + fuzzel = lib.mkDefault true; + gammastep = lib.mkDefault false; # niri + general = lib.mkDefault true; + git = lib.mkDefault true; + gnome-keyring = lib.mkDefault true; + gpgagent = lib.mkDefault true; + hexchat = lib.mkDefault true; + kanshi = lib.mkDefault false; # niri + kdeconnect = lib.mkDefault true; + kitty = lib.mkDefault true; + khal = lib.mkDefault true; + mail = lib.mkDefault true; + mako = lib.mkDefault false; # niri + nix-index = lib.mkDefault true; + nixgl = lib.mkDefault true; + nix-your-shell = lib.mkDefault true; + nm-applet = lib.mkDefault true; + obs-studio = lib.mkDefault true; + obsidian = lib.mkDefault true; + obsidian-tray = lib.mkDefault true; + opkssh = lib.mkDefault true; + ownpackages = lib.mkDefault true; + packages = lib.mkDefault true; + passwordstore = lib.mkDefault true; + programs = lib.mkDefault true; + sops = lib.mkDefault false; + spicetify = lib.mkDefault true; + spotify-player = lib.mkDefault true; + ssh = lib.mkDefault true; + starship = lib.mkDefault true; + stylix = lib.mkDefault true; + sway = lib.mkDefault false; # niri + swayidle = lib.mkDefault true; + swaylock = lib.mkDefault false; # niri + swayosd = lib.mkDefault true; + symlink = lib.mkDefault true; + tmux = lib.mkDefault true; + vesktop = lib.mkDefault true; + vesktop-tray = lib.mkDefault true; + shikane = lib.mkDefault true; + syncthing-tray = lib.mkDefault true; + waybar = lib.mkDefault true; + yubikey = lib.mkDefault false; + yubikeytouch = lib.mkDefault true; + zellij = lib.mkDefault true; + zsh = lib.mkDefault true; + }; + }; + +} diff --git a/profiles-clone/nixos/default.nix b/profiles-clone/nixos/default.nix new file mode 100644 index 0000000..278e47c --- /dev/null +++ b/profiles-clone/nixos/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + profileNames = lib.swarselsystems.readNix "profiles-clone/nixos"; +in +{ + imports = lib.swarselsystems.mkImports profileNames "profiles-clone/nixos"; +} diff --git a/profiles-clone/nixos/hotel/default.nix b/profiles-clone/nixos/hotel/default.nix new file mode 100644 index 0000000..7759055 --- /dev/null +++ b/profiles-clone/nixos/hotel/default.nix @@ -0,0 +1,12 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.hotel = lib.mkEnableOption "is this a hotel host"; + config = lib.mkIf config.swarselprofiles.hotel { + swarselprofiles.personal = true; + swarselmodules = { + yubikey = false; + }; + + }; + +} diff --git a/profiles-clone/nixos/localserver/default.nix b/profiles-clone/nixos/localserver/default.nix new file mode 100644 index 0000000..e010a70 --- /dev/null +++ b/profiles-clone/nixos/localserver/default.nix @@ -0,0 +1,29 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.server = lib.mkEnableOption "is this a server"; + config = lib.mkIf config.swarselprofiles.server { + swarselmodules = { + general = lib.mkDefault true; + lanzaboote = lib.mkDefault true; + home-manager = lib.mkDefault true; + xserver = lib.mkDefault true; + time = lib.mkDefault true; + impermanence = lib.mkDefault true; + btrfs = lib.mkDefault true; + sops = lib.mkDefault true; + boot = lib.mkDefault true; + nftables = lib.mkDefault true; + server = { + general = lib.mkDefault true; + ids = lib.mkDefault true; + network = lib.mkDefault true; + diskEncryption = lib.mkDefault true; + packages = lib.mkDefault true; + ssh = lib.mkDefault true; + attic-setup = lib.mkDefault true; + dns-hostrecord = lib.mkDefault true; + }; + }; + }; + +} diff --git a/profiles-clone/nixos/microvm/default.nix b/profiles-clone/nixos/microvm/default.nix new file mode 100644 index 0000000..96945a9 --- /dev/null +++ b/profiles-clone/nixos/microvm/default.nix @@ -0,0 +1,29 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.microvm = lib.mkEnableOption "is this a server"; + config = lib.mkIf config.swarselprofiles.microvm { + swarselsystems = { + isLinux = true; + isNixos = true; + }; + swarselmodules = { + general = lib.mkDefault true; + xserver = lib.mkDefault true; + time = lib.mkDefault true; + impermanence = lib.mkDefault true; + btrfs = lib.mkDefault true; + sops = lib.mkDefault true; + nftables = lib.mkDefault true; + server = { + general = lib.mkDefault true; + ids = lib.mkDefault true; + packages = lib.mkDefault true; + ssh = lib.mkDefault true; + wireguard = lib.mkDefault true; + dns-home = lib.mkDefault true; + opkssh = true; + }; + }; + }; + +} diff --git a/profiles-clone/nixos/minimal/default.nix b/profiles-clone/nixos/minimal/default.nix new file mode 100644 index 0000000..d6355d2 --- /dev/null +++ b/profiles-clone/nixos/minimal/default.nix @@ -0,0 +1,29 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.minimal = lib.mkEnableOption "declare this a minimal host"; + config = lib.mkIf config.swarselprofiles.minimal { + swarselmodules = { + general = lib.mkDefault true; + home-manager = lib.mkDefault true; + xserver = lib.mkDefault true; + lanzaboote = lib.mkDefault true; + time = lib.mkDefault true; + impermanence = lib.mkDefault true; + security = lib.mkDefault true; + sops = lib.mkDefault true; + zsh = lib.mkDefault true; + yubikey = lib.mkDefault true; + autologin = lib.mkDefault true; + boot = lib.mkDefault true; + btrfs = lib.mkDefault true; + nftables = lib.mkDefault true; + + server = { + ssh = lib.mkDefault true; + diskEncryption = lib.mkDefault true; + }; + }; + + }; + +} diff --git a/profiles-clone/nixos/personal/default.nix b/profiles-clone/nixos/personal/default.nix new file mode 100644 index 0000000..f8ab245 --- /dev/null +++ b/profiles-clone/nixos/personal/default.nix @@ -0,0 +1,63 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.personal = lib.mkEnableOption "is this a personal host"; + config = lib.mkIf config.swarselprofiles.personal { + swarselmodules = { + # keyd = lib.mkDefault true; + appimage = lib.mkDefault true; + autologin = lib.mkDefault true; + blueman = lib.mkDefault true; + boot = lib.mkDefault true; + btrfs = lib.mkDefault true; + distrobox = lib.mkDefault true; + env = lib.mkDefault true; + firezone-client = lib.mkDefault true; + general = lib.mkDefault true; + gnome-keyring = lib.mkDefault true; + gvfs = lib.mkDefault true; + hardware = lib.mkDefault true; + home-manager = lib.mkDefault true; + impermanence = lib.mkDefault true; + interceptionTools = lib.mkDefault true; + keyboards = lib.mkDefault true; + lanzaboote = lib.mkDefault true; + ledger = lib.mkDefault true; + lid = lib.mkDefault true; + login = lib.mkDefault true; + lowBattery = lib.mkDefault false; + nautilus = lib.mkDefault true; + network = lib.mkDefault true; + networkDevices = lib.mkDefault true; + nftables = lib.mkDefault true; + nix-ld = lib.mkDefault true; + nvd = lib.mkDefault true; + packages = lib.mkDefault true; + pipewire = lib.mkDefault true; + ppd = lib.mkDefault true; + programs = lib.mkDefault true; + pulseaudio = lib.mkDefault true; + remotebuild = lib.mkDefault true; + security = lib.mkDefault true; + sops = lib.mkDefault true; + stylix = lib.mkDefault true; + sway = lib.mkDefault false; # niri + swayosd = lib.mkDefault false; # niri + syncthing = lib.mkDefault true; + systemdTimeout = lib.mkDefault true; + time = lib.mkDefault true; + uwsm = lib.mkDefault true; + xdg-portal = lib.mkDefault true; + xserver = lib.mkDefault true; + yubikey = lib.mkDefault true; + zsh = lib.mkDefault true; + + }; + home-manager.users."${config.swarselsystems.mainUser}" = { + swarselprofiles = { + personal = lib.mkDefault true; + }; + }; + + }; + +} diff --git a/profiles-clone/nixos/router/default.nix b/profiles-clone/nixos/router/default.nix new file mode 100644 index 0000000..6e10c2e --- /dev/null +++ b/profiles-clone/nixos/router/default.nix @@ -0,0 +1,14 @@ +{ lib, config, ... }: +{ + options.swarselprofiles.router = lib.mkEnableOption "enable the router profile"; + config = lib.mkIf config.swarselprofiles.router { + swarselmodules = { + nftables = lib.mkDefault true; + server = { + router = lib.mkDefault true; + kea = lib.mkDefault true; + }; + }; + }; + +}