diff --git a/.gitignore b/.gitignore index a211cae..d2f95d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ private.nix +private/ diff --git a/system/flake.lock b/system/flake.lock index a5aac98..8820ce7 100644 --- a/system/flake.lock +++ b/system/flake.lock @@ -1,5 +1,37 @@ { "nodes": { + "blobs": { + "flake": false, + "locked": { + "lastModified": 1604995301, + "narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=", + "owner": "simple-nixos-mailserver", + "repo": "blobs", + "rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265", + "type": "gitlab" + }, + "original": { + "owner": "simple-nixos-mailserver", + "repo": "blobs", + "type": "gitlab" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1668681692, + "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "009399224d5e398d03b22badca40a37ac85412a1", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -83,6 +115,32 @@ "type": "github" } }, + "nixos-mailserver": { + "inputs": { + "blobs": "blobs", + "flake-compat": "flake-compat", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-22_11": [ + "nixpkgs" + ], + "utils": "utils" + }, + "locked": { + "lastModified": 1671738303, + "narHash": "sha256-PRgqtaWf2kMSYqVmcnmhTh+UsC0RmvXRTr+EOw5VZUA=", + "owner": "simple-nixos-mailserver", + "repo": "nixos-mailserver", + "rev": "6d0d9fb966cc565a3df74d3b686f924c7615118c", + "type": "gitlab" + }, + "original": { + "owner": "simple-nixos-mailserver", + "repo": "nixos-mailserver", + "type": "gitlab" + } + }, "nixpkgs": { "locked": { "lastModified": 1683408522, @@ -104,9 +162,10 @@ "impermanence": "impermanence", "nix-gaming": "nix-gaming", "nixos-hardware": "nixos-hardware", + "nixos-mailserver": "nixos-mailserver", "nixpkgs": "nixpkgs", "rust-overlay": "rust-overlay", - "utils": "utils" + "utils": "utils_2" } }, "rust-overlay": { @@ -117,11 +176,11 @@ ] }, "locked": { - "lastModified": 1683685144, - "narHash": "sha256-HZBieMZj9Rp+WtbGiK5JaUciZwJ/2YX6LRe5z8jkfow=", + "lastModified": 1683708507, + "narHash": "sha256-i5zgWcuyZcNBnlrzVjhpAFQdJWr4OyhvQ1owAEHFFKw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "74e44edb87aeed50798f5b81795ebad250d4a0c0", + "rev": "ed55dc022aa23ed3c42f383cf1782290b3b939d5", "type": "github" }, "original": { @@ -146,6 +205,21 @@ } }, "utils": { + "locked": { + "lastModified": 1605370193, + "narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5021eac20303a61fafe17224c087f5519baed54d", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "utils_2": { "inputs": { "flake-utils": "flake-utils_2" }, diff --git a/system/flake.nix b/system/flake.nix index e0e2435..0aaf2a2 100644 --- a/system/flake.nix +++ b/system/flake.nix @@ -15,14 +15,25 @@ url = "github:fufexan/nix-gaming"; inputs.nixpkgs.follows = "nixpkgs"; }; + nixos-mailserver = { + url = "gitlab:simple-nixos-mailserver/nixos-mailserver"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs-22_11.follows = "nixpkgs"; + }; }; - outputs = inputs@{ self, nixpkgs, utils, nixos-hardware, impermanence, nix-gaming, ... }: + outputs = inputs@{ self, nixpkgs, utils, nixos-hardware, impermanence, nix-gaming, nixos-mailserver, ... }: let hw = nixos-hardware.nixosModules; # IRL-related stuff I'd rather not put into git - priv = if builtins.pathExists ./private.nix then (import ./private.nix) else { }; - getPriv = (hostname: with builtins; if hasAttr hostname priv then getAttr hostname priv else { }); + priv = if builtins.pathExists ./private/default.nix then (import ./private) + else if builtins.pathExists ./private.nix then (import ./private.nix) + else { }; + getPriv = hostname: with builtins; if hasAttr hostname priv then getAttr hostname priv else { }; + common = hostname: [ (getPriv hostname) impermanence.nixosModule ]; + extraArgs = { + inherit nixpkgs; + }; in utils.lib.mkFlake { inherit self inputs; hostDefaults.modules = [ @@ -44,18 +55,24 @@ system = "x86_64-linux"; modules = [ ./hosts/nixmsi.nix - impermanence.nixosModule nix-gaming.nixosModules.pipewireLowLatency hw.common-pc-ssd # enables fstrim hw.common-cpu-amd # microcode hw.common-cpu-amd-pstate # amd-pstate hw.common-gpu-amd # configures drivers hw.common-pc-laptop # enables tlp - (getPriv "nixmsi") - ]; - extraArgs = { - inherit nixpkgs; - }; + ] ++ common "nixmsi"; + inherit extraArgs; + }; + nixserver = { + system = "x86_64-linux"; + modules = [ + ./hosts/nixserver + nixos-mailserver.nixosModules.default + hw.common-pc-hdd + hw.common-cpu-intel + ] ++ common "nixserver"; + inherit extraArgs; }; }; }; diff --git a/system/hosts/nixmsi.nix b/system/hosts/nixmsi.nix index 5936db7..e9f79da 100644 --- a/system/hosts/nixmsi.nix +++ b/system/hosts/nixmsi.nix @@ -108,6 +108,11 @@ in { opengl.extraPackages = with pkgs; [ vulkan-validation-layers ]; }; + services.openssh = { + enable = true; + settings.PasswordAuthentication = false; + }; + services.tlp.enable = true; services.tlp.settings = { USB_EXCLUDE_PHONE = 1; @@ -207,7 +212,6 @@ in { ++ (lib.range 1714 1764); networking.firewall.allowedUDPPorts = lib.range 1714 1764; - # networking.hostName = "nixmsi"; networking.wireless.iwd.enable = true; #networking.networkmanager.enable = true; @@ -327,17 +331,6 @@ in { documentation.dev.enable = true; - ### RANDOM PATCHES ### - - # I've had some weird issues with the entire system breaking after - # suspend because of /dev/shm getting nuked, maybe this'll help - services.logind.extraConfig = '' - RemoveIPC=no - ''; - - # why is this not part of base NixOS? - systemd.tmpfiles.rules = [ "d /var/lib/systemd/pstore 0755 root root 14d" ]; - # autologin once after boot # --skip-login means directly call login instead of first asking for username # (normally login asks for username too, but getty prefers to do it by itself for whatever reason) diff --git a/system/hosts/nixserver/default.nix b/system/hosts/nixserver/default.nix new file mode 100644 index 0000000..3f5aa24 --- /dev/null +++ b/system/hosts/nixserver/default.nix @@ -0,0 +1,464 @@ +{ lib +, pkgs +, config +, ... }: + +let + cfg = config.server; + # TODO: move to lib + quotePotentialIpV6 = addr: + if lib.hasInfix ":" addr then "[${addr}]" else addr; + + efiPart = "/dev/disk/by-uuid/3E2A-A5CB"; + rootUuid = "6aace237-9b48-4294-8e96-196759a5305b"; + rootPart = "/dev/disk/by-uuid/${rootUuid}"; + + hosted-domains = + map + (prefix: if prefix == null then cfg.domainName else "${prefix}.${cfg.domainName}") + [ + null + "dns" + "mumble" + "mail" + "music" + "www" + "matrix" + "search" + "git" + "cloud" + "ns1" + "ns2" + ]; + +in { + imports = [ + ./options.nix + ./matrix.nix + ./fdroid.nix + ./mumble.nix + ]; + + system.stateVersion = "22.11"; + + boot = { + initrd = { + availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" "rtsx_pci_sdmmc" ]; + }; + kernelModules = [ "kvm-intel" ]; + kernelParams = [ + "consoleblank=60" + ]; + loader = { + grub = { + enable = true; + device = "nodev"; + version = 2; + efiSupport = true; + efiInstallAsRemovable = true; + gfxmodeEfi = "1920x1080"; + gfxmodeBios = "1920x1080"; + }; + efi.efiSysMountPoint = "/boot/efi"; + }; + }; + hardware.enableRedistributableFirmware = true; + fileSystems = { + "/" = { device = "none"; fsType = "tmpfs"; neededForBoot = true; + options = [ "defaults" "size=2G" "mode=755" ]; }; + "/persist" = + { device = rootPart; fsType = "btrfs"; neededForBoot = true; + options = [ "compress=zstd:15" ]; }; + "/boot" = + { device = rootPart; fsType = "btrfs"; neededForBoot = true; + options = [ "compress=zstd:15" "subvol=boot" ]; }; + "/boot/efi" = + { device = efiPart; fsType = "vfat"; }; + }; + zramSwap.enable = true; + swapDevices = [ ]; + impermanence = { + enable = true; + path = /persist; + directories = [ + { directory = /var/www/${cfg.domainName}; } + { directory = /var/lib/maubot; } + { directory = /var/lib/fdroid; } + { directory = config.mailserver.dkimKeyDirectory; } + { directory = config.mailserver.mailDirectory; } + { directory = /home/user; } + { directory = /root; } + { directory = /nix; } + ]; + }; + services.beesd = { + filesystems.root = { + spec = "UUID=${rootUuid}"; + hashTableSizeMB = 128; + extraOptions = [ "--loadavg-target" "8.0" ]; + }; + }; + i18n.defaultLocale = lib.mkDefault "en_US.UTF-8"; + i18n.supportedLocales = lib.mkDefault [ + "C.UTF-8/UTF-8" + "en_US.UTF-8/UTF-8" + "en_DK.UTF-8/UTF-8" + ]; + # ISO-8601 + i18n.extraLocaleSettings.LC_TIME = "en_DK.UTF-8"; + console.font = "${pkgs.terminus_font}/share/consolefonts/ter-v24n.psf.gz"; + networking.useDHCP = true; + networking.resolvconf.extraConfig = '' + name_servers="127.0.0.1 ::1" + ''; + networking.firewall = { + enable = true; + allowedTCPPorts = [ + # ssh + 22 + # dns + 53 853 + # http/s + 80 443 + ]; + allowedUDPPorts = [ + # dns + 53 853 + # wireguard + # 5553 + ]; + }; + + # UNBOUND + services.unbound = { + enable = true; + package = pkgs.unbound-with-systemd.override { + withPythonModule = true; + python = pkgs.python3.withPackages (pkgs: with pkgs; [ pydbus dnspython ]); + }; + localControlSocketPath = "/run/unbound/unbound.ctl"; + resolveLocalQueries = false; + settings = { + server = { + interface = [ "0.0.0.0" "::" ]; + access-control = [ "${cfg.lanCidrV4} allow" "${cfg.lanCidrV6} allow" ]; + aggressive-nsec = true; + do-ip6 = true; + module-config = ''"validator iterator"''; + local-zone = [ + ''"local." static'' + ] ++ (lib.optionals (cfg.localIpV4 != null || cfg.localIpV6 != null) [ + ''"${cfg.domainName}." typetransparent'' + ]); + local-data = builtins.concatLists (map (domain: + lib.optionals (cfg.localIpV4 != null) [ + ''"${domain}. A ${cfg.localIpV4}"'' + ] ++ (lib.optionals (cfg.localIpV6 != null) [ + ''"${domain}. A ${cfg.localIpV6}"'' + ])) hosted-domains); + }; + python.python-script = toString (pkgs.fetchurl { + url = "https://raw.githubusercontent.com/NLnetLabs/unbound/a912786ca9e72dc1ccde98d5af7d23595640043b/pythonmod/examples/avahi-resolver.py"; + sha256 = "0r1iqjf08wrkpzvj6pql1jqa884hbbfy9ix5gxdrkrva09msiqgi"; + }); + remote-control.control-enable = true; + }; + }; + systemd.services.unbound.environment.MDNS_ACCEPT_NAMES = "^.*\\.local\\.$"; + # just in case + networking.hosts."127.0.0.1" = [ "localhost" ] ++ hosted-domains; + + # CUPS + services.printing = { + enable = true; + allowFrom = [ cfg.lanCidrV4 cfg.lanCidrV6 ]; + browsing = true; + clientConf = '' + ServerName ${cfg.domainName} + ''; + defaultShared = true; + drivers = [ pkgs.hplip ]; + startWhenNeeded = false; + }; + + programs.fish.enable = true; + users.defaultUserShell = pkgs.fish; + users.users.user = { + isNormalUser = true; + extraGroups = [ "wheel" config.services.unbound.group ]; + }; + + environment.systemPackages = with pkgs; [ + comma + git + vim + wget + # rxvt-unicode-unwrapped.terminfo + kitty.terminfo + tmux + ]; + + services.postgresql.enable = true; + services.postgresql.package = pkgs.postgresql_13; + + nix = { + settings = { + allowed-users = [ "user" ]; + auto-optimise-store = true; + }; + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; + }; + package = pkgs.nixFlakes; + extraOptions = '' + experimental-features = nix-command flakes + ''; + }; + systemd.services.nix-daemon.serviceConfig.LimitSTACKSoft = "infinity"; + + # SSH + services.openssh = { + enable = true; + settings.PermitRootLogin = "no"; + settings.PasswordAuthentication = false; + listenAddresses = [{ + addr = "0.0.0.0"; + } { + addr = "::"; + }]; + }; + services.fail2ban.enable = true; + + # SEARXNG + services.searx.enable = true; + services.searx.package = pkgs.searxng.overrideAttrs (_: { + src = pkgs.fetchFromGitHub { + owner = "searxng"; + repo = "searxng"; + rev = "cb1c3741d7de1354b524589114617f183009f6a8"; + sha256 = "sha256-7erY5Bd1ZoTpAIDbhIupu64Xd1PQspaW6vBqu7knzNI="; + }; + }); + services.searx.runInUwsgi = true; + services.searx.uwsgiConfig = let inherit (config.services.searx) settings; in { + socket = "${quotePotentialIpV6 settings.server.bind_address}:${toString settings.server.port}"; + }; + users.groups.searx.members = [ "nginx" ]; + services.searx.environmentFile = "/etc/nixos/private/searx.env"; + services.searx.settings = { + use_default_settings = true; + search = { + safe_search = 0; # Filter results. 0: None, 1: Moderate, 2: Strict + autocomplete = "duckduckgo"; # Existing autocomplete backends: "dbpedia", "duckduckgo", "google", "startpage", "swisscows", "qwant", "wikipedia" - leave blank to turn it off by default + default_lang = ""; # Default search language - leave blank to detect from browser information or use codes from 'languages.py' + }; + + server = { + port = 8888; + bind_address = "::1"; + secret_key = "@SEARX_SECRET_KEY@"; + base_url = "https://search.${cfg.domainName}/"; + image_proxy = true; + default_http_headers = { + X-Content-Type-Options = "nosniff"; + X-XSS-Protection = "1; mode=block"; + X-Download-Options = "noopen"; + X-Robots-Tag = "noindex, nofollow"; + Referrer-Policy = "no-referrer"; + }; + }; + outgoing = { + request_timeout = 5.0; # default timeout in seconds, can be override by engine + max_request_timeout = 15.0; # the maximum timeout in seconds + pool_connections = 100; # Maximum number of allowable connections, or null + pool_maxsize = 10; # Number of allowable keep-alive connections, or null + enable_http2 = true; # See https://www.python-httpx.org/http2/ + }; + /* = { + name = "soundcloud"; + disabled = true; + };*/ + }; + services.nginx.virtualHosts."search.${cfg.domainName}" = let inherit (config.services.searx) settings; in { + enableACME = true; + forceSSL = true; + # locations."/".proxyPass = "http://${quotePotentialIpV6 settings.server.bind_address}:${toString settings.server.port}"; + locations."/".extraConfig = '' + uwsgi_pass "${quotePotentialIpV6 settings.server.bind_address}:${toString settings.server.port}"; + include ${config.services.nginx.package}/conf/uwsgi_params; + ''; + }; + + # NGINX + services.nginx.enable = true; + services.nginx.streamConfig = + let + cert = config.security.acme.certs."${cfg.domainName}".directory + "/fullchain.pem"; + certKey = config.security.acme.certs."${cfg.domainName}".directory + "/key.pem"; + trustedCert = config.security.acme.certs."${cfg.domainName}".directory + "/chain.pem"; + in '' + upstream dns { + zone dns 64k; + server 127.0.0.1:53; + } + server { + listen 853 ssl; + ssl_certificate ${cert}; + ssl_certificate_key ${certKey}; + ssl_trusted_certificate ${trustedCert}; + proxy_pass dns; + } + ''; + services.nginx.commonHttpConfig = "log_format postdata '{\"ip\":\"$remote_addr\",\"time\":\"$time_iso8601\",\"referer\":\"$http_referer\",\"body\":\"$request_body\",\"ua\":\"$http_user_agent\"}';"; + services.nginx.recommendedTlsSettings = true; + services.nginx.recommendedOptimisation = true; + services.nginx.recommendedGzipSettings = true; + services.nginx.recommendedProxySettings = true; + + # BLOG + services.nginx.virtualHosts."${cfg.domainName}" = { + enableACME = true; + forceSSL = true; + extraConfig = "autoindex on;"; + locations."/".root = "/var/www/${cfg.domainName}/"; + locations."/src".root = "/var/www/${cfg.domainName}/"; + locations."/src".extraConfig = "index force_dirlisting;"; + locations."/submit_comment".extraConfig = '' + access_log /var/log/nginx/comments.log postdata; + proxy_pass https://${cfg.domainName}/submit.htm; + break; + ''; + locations."/submit.htm" = { + extraConfig = '' + return 200 'Success!

Success! It may take a while for your comment to get moderated.

Please wait for 10 seconds until you get redirected back...

Or just go there manually.


'; + ''; + }; + }; + + services.nginx.virtualHosts."www.${cfg.domainName}" = { + enableACME = true; + globalRedirect = cfg.domainName; + }; + + # MAILSERVER + # roundcube + services.nginx.virtualHosts."mail.${cfg.domainName}" = { + enableACME = true; + }; + services.roundcube = { + enable = true; + package = pkgs.roundcube.withPlugins (plugins: [ plugins.persistent_login ]); + dicts = with pkgs.aspellDicts; [ en ru ]; + hostName = "mail.${cfg.domainName}"; + maxAttachmentSize = 100; + plugins = [ "persistent_login" ]; + }; + mailserver = { + enable = true; + fqdn = "mail.${cfg.domainName}"; + domains = [ cfg.domainName ]; + certificateScheme = 1; + certificateFile = config.security.acme.certs."mail.${cfg.domainName}".directory + "/fullchain.pem"; + keyFile = config.security.acme.certs."mail.${cfg.domainName}".directory + "/key.pem"; + localDnsResolver = false; + recipientDelimiter = "-"; + lmtpSaveToDetailMailbox = "no"; + hierarchySeparator = "/"; + }; + + # Only allow local connections to noreply account + mailserver.loginAccounts."noreply@${cfg.domainName}" = { + # password is set in private.nix + hashedPassword = cfg.hashedNoreplyPassword; + sendOnly = true; + }; + services.dovecot2.extraConfig = + let passwd = builtins.toFile "dovecot2-local-passwd" '' + noreply@${cfg.domainName}:{plain}${cfg.unhashedNoreplyPassword}::::::allow_nets=local,127.0.0.0/8,::1 + ''; + in '' + passdb { + driver = passwd-file + args = ${passwd} + } + ''; + + # GITEA + services.nginx.virtualHosts."git.${cfg.domainName}" = let inherit (config.services.gitea) settings; in { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://${quotePotentialIpV6 settings.server.HTTP_ADDR}:${toString settings.server.HTTP_PORT}"; + }; + services.gitea = { + enable = true; + database = { + createDatabase = false; + passwordFile = "/var/lib/gitea/db_password"; + type = "postgres"; + }; + settings = { + mailer = { + ENABLED = true; + FROM = "Gitea "; + MAILER_TYPE = "smtp"; + HOST = "mail.${cfg.domainName}:587"; + USER = "noreply@${cfg.domainName}"; + PASSWD = cfg.unhashedNoreplyPassword; + SKIP_VERIFY = true; + }; + session = { + COOKIE_SECURE = true; + }; + server = { + ROOT_URL = "https://git.${cfg.domainName}"; + HTTP_ADDR = "::1"; + HTTP_PORT = 3310; + DOMAIN = "git.${cfg.domainName}"; + # START_SSH_SERVER = true; + # SSH_PORT = 2222; + }; + service = { + DISABLE_REGISTRATION = true; + REGISTER_EMAIL_CONFIRM = true; + }; + }; + }; + + # NEXTCLOUD + services.nginx.virtualHosts."cloud.${cfg.domainName}" = { + enableACME = true; + forceSSL = true; + }; + services.nextcloud = { + enable = true; + enableBrokenCiphersForSSE = false; + package = pkgs.nextcloud26; + autoUpdateApps.enable = true; + # TODO: use socket auth and remove the next line + database.createLocally = false; + config = { + adminpassFile = "/var/lib/nextcloud/admin_password"; + dbpassFile = "/var/lib/nextcloud/db_password"; + dbtype = "pgsql"; + dbhost = "/run/postgresql"; + overwriteProtocol = "https"; + }; + hostName = "cloud.${cfg.domainName}"; + https = true; + }; + + systemd.services.pleroma.path = [ pkgs.exiftool pkgs.gawk ]; + services.nginx.virtualHosts."pleroma.${cfg.domainName}" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://127.0.0.1:9970"; + }; + + /*locations."/dns-query".extraConfig = '' + grpc_pass grpc://127.0.0.1:53453; + '';*/ + + # TODO: firefox sync? +} diff --git a/system/hosts/nixserver/fdroid.nix b/system/hosts/nixserver/fdroid.nix new file mode 100644 index 0000000..84e6307 --- /dev/null +++ b/system/hosts/nixserver/fdroid.nix @@ -0,0 +1,51 @@ +{ config +, pkgs +, lib +, ... }: + +let + cfg = config.server; + quotePotentialIpV6 = addr: + if lib.hasInfix ":" addr then "[${addr}]" else addr; +in { + services.nginx.virtualHosts."${cfg.domainName}" = { + locations."/fdroid/".alias = "/var/lib/fdroid/repo/"; + }; + users.users.fdroid = { + home = "/var/lib/fdroid"; + group = "fdroid"; + isSystemUser = true; + }; + users.groups.fdroid = { }; + systemd.timers.update-fdroid = { + wantedBy = [ "timers.target" ]; + partOf = [ "update-fdroid.service" ]; + # slightly unusual time to reduce server load + timerConfig.OnCalendar = [ "*-*-* 00:40:00" ]; # every day + }; + systemd.services.update-fdroid = { + serviceConfig = let + inherit (pkgs) fdroidserver; + fdroidScript = pkgs.writeText "update-froid.py" '' + import requests, subprocess, os, sys + + x = requests.get('https://api.github.com/repos/ppy/osu/releases').json() + + for q in x: + for w in q.get('assets', []): + if w.get('name', "").endswith('.apk'): + os.chdir('/var/lib/fdroid') + subprocess.run(['${pkgs.wget}/bin/wget', w['browser_download_url'], '-O', '/var/tmp/lazer.apk'], check=True) + os.rename('/var/tmp/lazer.apk', '/var/lib/fdroid/repo/sh.ppy.osulazer.apk') + subprocess.run(['${fdroidserver}/bin/fdroid', 'update', '--allow-disabled-algorithms']) + sys.exit() + ''; + fdroidPython = pkgs.python3.withPackages (p: with p; [ requests ]); + in { + Type = "oneshot"; + ExecStart = "${fdroidPython} ${fdroidScript}"; + }; + environment.JAVA_HOME = "${pkgs.jdk11_headless}"; + path = [ pkgs.jdk11_headless ]; + }; +} diff --git a/system/hosts/nixserver/matrix.nix b/system/hosts/nixserver/matrix.nix new file mode 100644 index 0000000..7633896 --- /dev/null +++ b/system/hosts/nixserver/matrix.nix @@ -0,0 +1,122 @@ +{ config +, pkgs +, lib +, ... }: + +let + cfg = config.server; + quotePotentialIpV6 = addr: + if lib.hasInfix ":" addr then "[${addr}]" else addr; + matrixServerJson = { + "m.server" = "matrix.${cfg.domainName}:443"; + }; + matrixClientJson = { + "m.homeserver" = { base_url = "https://matrix.${cfg.domainName}"; }; + "m.identity_server" = { base_url = "https://vector.im"; }; + }; + matrixServerConfigResponse = '' + add_header Content-Type application/json; + return 200 '${builtins.toJSON matrixServerJson}'; + ''; + matrixClientConfigResponse = '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON matrixClientJson}'; + ''; + matrixAddr = "::1"; + matrixPort = 8008; +in { + imports = [ ./maubot.nix ]; + + networking.firewall.allowedTCPPorts = [ 8008 8448 ]; + systemd.services.matrix-synapse.serviceConfig.TimeoutStartSec = 180; + + services.nginx.virtualHosts."${cfg.domainName}" = { + locations."= /.well-known/matrix/server".extraConfig = matrixServerConfigResponse; + locations."= /.well-known/matrix/client".extraConfig = matrixClientConfigResponse; + }; + + services.nginx.virtualHosts."matrix.${cfg.domainName}" = { + enableACME = true; + forceSSL = true; + locations = { + "= /.well-known/matrix/server".extraConfig = matrixServerConfigResponse; + "= /.well-known/matrix/client".extraConfig = matrixClientConfigResponse; + "/".proxyPass = "http://${quotePotentialIpV6 matrixAddr}:${toString matrixPort}"; + }; + }; + + systemd.services.heisenbridge.wants = [ "matrix-synapse.service" ]; + systemd.services.heisenbridge.after = [ "matrix-synapse.service" ]; + services.heisenbridge = { + enable = true; + homeserver = "http://${quotePotentialIpV6 matrixAddr}:${toString matrixPort}/"; + }; + # so synapse can read the registration + users.groups.heisenbridge.members = [ "matrix-synapse" ]; + + services.matrix-synapse = { + enable = true; + extraConfigFiles = [ "/var/lib/matrix-synapse/config.yaml" ]; + settings = { + app_service_config_files = [ + "/var/lib/heisenbridge/registration.yml" + ]; + allow_guest_access = true; + url_preview_enabled = true; + tls_certificate_path = config.security.acme.certs."matrix.${cfg.domainName}".directory + "/fullchain.pem"; + tls_private_key_path = config.security.acme.certs."matrix.${cfg.domainName}".directory + "/key.pem"; + public_baseurl = "https://matrix.${cfg.domainName}/"; + server_name = "matrix.${cfg.domainName}"; + max_upload_size = "100M"; + email = { + smtp_host = "mail.pavluk.org"; + smtp_port = 587; + smtp_user = "noreply"; + smtp_password = cfg.unhashedNoreplyPassword; + notif_from = "${cfg.domainName} matrix homeserver "; + app_name = cfg.domainName; + notif_for_new_users = false; + enable_notifs = true; + }; + listeners = [{ + port = matrixPort; + bind_addresses = [ matrixAddr ]; + type = "http"; + tls = false; + x_forwarded = true; + resources = [{ + names = [ "client" "federation" ]; + compress = false; + }]; + }]; + }; + }; + + # maubot + users.users.maubot = { + home = "/var/lib/maubot"; + group = "maubot"; + isSystemUser = true; + }; + users.groups.maubot = { }; + systemd.services.maubot = { + description = "Maubot"; + wants = [ "matrix-synapse.service" "nginx.service" ]; + after = [ "matrix-synapse.service" "nginx.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib"; + }; + serviceConfig = { + User = "maubot"; + Group = "maubot"; + WorkingDirectory = "/var/lib/maubot/data"; + }; + script = "${pkgs.python3.withPackages (pks: with pks; [ + pkgs.maubot (pkgs.pineapplebot.override { + magic = cfg.pizzabotMagic; + }) feedparser levenshtein python-dateutil pytz + ])}/bin/python3 -m maubot"; + }; +} diff --git a/system/hosts/nixserver/maubot.nix b/system/hosts/nixserver/maubot.nix new file mode 100644 index 0000000..d945969 --- /dev/null +++ b/system/hosts/nixserver/maubot.nix @@ -0,0 +1,45 @@ +{ config +, pkgs +, lib +, ... }: + +let + cfg = config.server; + quotePotentialIpV6 = addr: + if lib.hasInfix ":" addr then "[${addr}]" else addr; + # i've yet to create a maubot module so this is hardcoded + maubotAddr = "127.0.0.1"; + maubotPort = 29316; +in { + services.nginx.virtualHosts."matrix.${cfg.domainName}".locations = { + "/_matrix/maubot/" = { + proxyPass = "http://${quotePotentialIpV6 maubotAddr}:${toString maubotPort}"; + proxyWebsockets = true; + }; + }; + users.users.maubot = { + home = "/var/lib/maubot"; + group = "maubot"; + isSystemUser = true; + }; + users.groups.maubot = { }; + systemd.services.maubot = { + description = "Maubot"; + wants = [ "matrix-synapse.service" "nginx.service" ]; + after = [ "matrix-synapse.service" "nginx.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib"; + }; + serviceConfig = { + User = "maubot"; + Group = "maubot"; + WorkingDirectory = "/var/lib/maubot/data"; + }; + script = "${pkgs.python3.withPackages (pks: with pks; [ + pkgs.maubot (pkgs.pineapplebot.override { + magic = cfg.pizzabotMagic; + }) feedparser levenshtein python-dateutil pytz + ])}/bin/python3 -m maubot"; + }; +} diff --git a/system/hosts/nixserver/mumble.nix b/system/hosts/nixserver/mumble.nix new file mode 100644 index 0000000..ba43fad --- /dev/null +++ b/system/hosts/nixserver/mumble.nix @@ -0,0 +1,81 @@ +{ config +, lib +, ... }: + +let + cfg = config.server; + quotePotentialIpV6 = addr: + if lib.hasInfix ":" addr then "[${addr}]" else addr; +in { + services.murmur = { + enable = true; + imgMsgLength = 0; + textMsgLength = 0; + registerName = "mumble.${cfg.domainName}"; + registerHostname = "mumble.${cfg.domainName}"; + sslCa = config.security.acme.certs."mumble.${cfg.domainName}".directory + "/chain.pem"; + sslCert = config.security.acme.certs."mumble.${cfg.domainName}".directory + "/fullchain.pem"; + sslKey = config.security.acme.certs."mumble.${cfg.domainName}".directory + "/key.pem"; + # clientCertRequired = true; + extraConfig = '' + bandwidth=320000 + opusthreshold=0 + ''; + }; + # Allow murmur to read the certificate + security.acme.certs."mumble.${cfg.domainName}" = { + group = "nginxandmurmur"; + postRun = "systemctl try-reload-or-restart murmur"; + }; + users.groups.nginxandmurmur.members = [ "murmur" "nginx" ]; + + # Mumble music bot + services.nginx.virtualHosts."mumble.${cfg.domainName}" = let inherit (config.services.botamusique) settings; in { + enableACME = true; + forceSSL = true; + globalRedirect = cfg.domainName; + locations."/music".extraConfig = "return 301 https://mumble.${cfg.domainName}/music/;"; + locations."/music/".proxyPass = "http://${quotePotentialIpV6 settings.webinterface.listening_addr}:${toString settings.webinterface.listening_port}/"; + }; + + services.botamusique = { + enable = true; + settings = { + youtube_dl = { + cookiefile = "/var/lib/botamusique/cookie_ydl"; + }; + webinterface = { + enabled = true; + listening_addr = "::1"; + listening_port = 8181; + is_web_proxified = true; + access_address = "https://mumble.${cfg.domainName}/music"; + auth_method = "token"; + upload_enabled = true; + max_upload_file_size = "100MB"; + delete_allowed = true; + }; + bot = { + bandwidth = 200000; + volume = 1.0; + ducking = true; + ducking_volume = 0.75; + }; + server.certificate = "/var/lib/botamusique/cert.pem"; + }; + }; + systemd.services.botamusique.wants = [ "murmur.service" ]; + systemd.services.botamusique.after = [ "murmur.service" ]; + + networking.firewall = { + allowedTCPPorts = [ + 64738 + # Used for mumble-web signaling (not sure if it needs TCP or UDP) + # 20000 20001 20002 20003 20004 20005 20006 20007 20008 20009 20010 + ]; + allowedUDPPorts = [ + 64738 + # 20000 20001 20002 20003 20004 20005 20006 20007 20008 20009 20010 + ]; + }; +} diff --git a/system/hosts/nixserver/options.nix b/system/hosts/nixserver/options.nix new file mode 100644 index 0000000..11d3875 --- /dev/null +++ b/system/hosts/nixserver/options.nix @@ -0,0 +1,58 @@ +{ lib +, ... }: +{ + options.server = with lib; mkOption { + type = types.submodule { + options = { + domainName = mkOption { + type = types.str; + default = "pavluk.org"; + description = "domain name"; + }; + lanCidrV4 = mkOption { + type = types.str; + description = "LAN mask (IPv4)"; + example = "192.168.1.0/96"; + default = "0.0.0.0/0"; + }; + lanCidrV6 = mkOption { + type = types.str; + description = "LAN mask (IPv6)"; + example = "fd01:abcd::/64"; + default = "::/0"; + }; + localIpV4 = mkOption { + type = with types; nullOr str; + description = "server's local IPv4 address"; + example = "192.168.1.2"; + default = null; + }; + localIpV6 = mkOption { + type = with types; nullOr str; + description = "server's local IPv6 address"; + example = "fd01:abcd::2"; + default = null; + }; + noreplyPassword = mkOption { + type = types.str; + description = "noreply (only available via localhost) account password"; + default = "totallysafe"; + }; + hashedNoreplyPassword = mkOption { + type = types.str; + description = "hashed noreply password via mkpasswd -sm bcrypt"; + }; + unhashedNoreplyPassword = mkOption { + type = types.str; + description = "unhashed noreply password. \ + This should preferably be different from the password that is hashed for better security (yes, really)"; + }; + pizzabotMagic = mkOption { + type = types.str; + default = ""; + }; + }; + }; + description = "server settings"; + }; +} diff --git a/system/modules/ccache.nix b/system/modules/ccache.nix index bf0a1ff..01b4033 100644 --- a/system/modules/ccache.nix +++ b/system/modules/ccache.nix @@ -4,7 +4,7 @@ config.programs.ccache.cacheDir "/var/cache/sccache" ]; - environment.persistence."/persist".directories = lib.mkIf config.programs.ccache.enable [ + impermanence.directories = lib.mkIf config.programs.ccache.enable [ config.programs.ccache.cacheDir "/var/cache/sccache" ]; diff --git a/system/modules/impermanence.nix b/system/modules/impermanence.nix index 3f4ead7..31da131 100644 --- a/system/modules/impermanence.nix +++ b/system/modules/impermanence.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, ... }: let cfg = config.impermanence; @@ -16,7 +16,7 @@ in { description = "Default path for persistence"; }; directories = mkOption { - type = with types; listOf path; + type = with types; listOf (either path attrs); default = [ ]; description = "Extra directories to persist"; }; @@ -36,41 +36,90 @@ in { default = { }; }; config = lib.mkIf cfg.enable { + # why is this not part of base NixOS? + systemd.tmpfiles.rules = [ "d /var/lib/systemd/pstore 0755 root root 14d" ]; # as weird as it sounds, I won't use tmpfs for /tmp in case I'll have to put files over 2GB there boot.tmp.cleanOnBoot = lib.mkIf cfg.persistTmp true; environment.persistence.${toString cfg.path} = { hideMounts = true; - directories = map toString ([ + directories = map (x: + if builtins.isPath x then toString x + else if builtins.isAttrs x && x?directory && builtins.isPath x.directory then x // { directory = toString x.directory; } + else x) + ([ # nixos files - /etc/nixos - /var/lib/nixos + { directory = /etc/nixos; user = "root"; group = "root"; mode = "0755"; } + { directory = /var/lib/nixos; user = "root"; group = "root"; mode = "0755"; } - /var/log + { directory = /var/log; user = "root"; group = "root"; mode = "0755"; } # persist this since everything here is cleaned up by systemd-tmpfiles over time anyway # ...or so I'd like to believe - /var/lib/systemd - /var/tmp + { directory = /var/lib/systemd; user = "root"; group = "root"; mode = "0755"; } + { directory = /var/tmp; user = "root"; group = "root"; mode = "1777"; } + { directory = /var/spool; user = "root"; group = "root"; mode = "0777"; } ] ++ (lib.optionals cfg.persistTmp [ - /tmp + { directory = /tmp; user = "root"; group = "root"; mode = "1777"; } ]) ++ (lib.optionals config.services.mullvad-vpn.enable [ - /etc/mullvad-vpn - /var/cache/mullvad-vpn + { directory = /etc/mullvad-vpn; user = "root"; group = "root"; mode = "0700"; } + { directory = /var/cache/mullvad-vpn; user = "root"; group = "root"; mode = "0755"; } ]) ++ (lib.optionals config.virtualisation.libvirtd.enable ([ - /var/cache/libvirt - /var/lib/libvirt + # { directory = /var/cache/libvirt; user = "root"; group = "root"; mode = "0755"; } + { directory = /var/lib/libvirt; user = "root"; group = "root"; mode = "0755"; } ] ++ (lib.optionals config.virtualisation.libvirtd.qemu.swtpm.enable [ - /var/lib/swtpm-localca + { directory = /var/lib/swtpm-localca; user = "root"; group = "root"; mode = "0750"; } ]))) ++ (lib.optionals config.networking.wireless.iwd.enable [ - /var/lib/iwd + { directory = /var/lib/iwd; user = "root"; group = "root"; mode = "0700"; } ]) ++ (lib.optionals (builtins.any (x: x.useDHCP) (builtins.attrValues config.networking.interfaces) || config.networking.useDHCP) [ - /var/db/dhcpcd + { directory = /var/db/dhcpcd; user = "root"; group = "root"; mode = "0755"; } + ]) ++ (lib.optionals config.services.gitea.enable [ + { directory = /var/lib/gitea; user = "gitea"; group = "gitea"; mode = "0755"; } + ]) ++ (lib.optionals config.services.matrix-synapse.enable [ + { directory = /var/lib/matrix-synapse; user = "matrix-synapse"; group = "matrix-synapse"; mode = "0700"; } + ]) ++ (lib.optionals config.services.heisenbridge.enable [ + { directory = /var/lib/heisenbridge; user = "heisenbridge"; group = "heisenbridge"; mode = "0755"; } + ]) ++ (lib.optionals config.services.murmur.enable [ + { directory = /var/lib/murmur; user = "murmur"; group = "murmur"; mode = "0700"; } + ]) ++ (lib.optionals config.services.nextcloud.enable [ + { directory = /var/lib/nextcloud; user = "nextcloud"; group = "nextcloud"; mode = "0750"; } + ]) ++ (lib.optionals config.services.botamusique.enable [ + { directory = /var/lib/private/botamusique; user = "root"; group = "root"; mode = "0750"; } + ]) ++ (lib.optionals config.security.acme.acceptTerms [ + { directory = /var/lib/acme; user = "acme"; group = "acme"; mode = "0755"; } + ]) ++ (lib.optionals config.services.printing.enable [ + { directory = /var/lib/cups; user = "root"; group = "root"; mode = "0755"; } + ]) ++ (lib.optionals config.services.fail2ban.enable [ + { directory = /var/lib/fail2ban; user = "fail2ban"; group = "fail2ban"; mode = "0750"; } + ]) ++ (lib.optionals config.services.opendkim.enable [ + { directory = /var/lib/opendkim; user = "opendkim"; group = "opendkim"; mode = "0700"; } + ]) ++ (lib.optionals config.services.pleroma.enable [ + { directory = /var/lib/pleroma; user = "pleroma"; group = "pleroma"; mode = "0700"; } + ]) ++ (lib.optionals config.services.postfix.enable [ + { directory = /var/lib/postfix; user = "root"; group = "root"; mode = "0755"; } + ]) ++ (lib.optionals config.services.postgresql.enable [ + { directory = /var/lib/postgresql; user = "postgres"; group = "postgres"; mode = "0755"; } + ]) ++ (lib.optionals config.services.unbound.enable [ + { directory = /var/lib/unbound; user = "unbound"; group = "unbound"; mode = "0755"; } + ]) ++ (lib.optionals config.services.roundcube.enable [ + { directory = /var/lib/roundcube; user = "roundcube"; group = "roundcube"; mode = "0700"; } + ]) ++ (lib.optionals config.services.rspamd.enable [ + { directory = /var/lib/rspamd; user = "rspamd"; group = "rspamd"; mode = "0700"; } + ]) ++ (lib.optionals ( + (builtins.hasAttr "rspamd" config.services.redis.servers) + && (builtins.hasAttr "enable" config.services.redis.servers.rspamd) + && config.services.redis.servers.rspamd.enable + ) [ + { directory = /var/lib/redis-rspamd; user = "redis-rspamd"; group = "redis-rspamd"; mode = "0700"; } + ]) ++ (lib.optionals config.services.dovecot2.enable [ + { directory = /var/lib/dhparams; user = "root"; group = "root"; mode = "0755"; } + { directory = /var/lib/dovecot; user = "root"; group = "root"; mode = "0755"; } ]) ++ (lib.optionals config.security.sudo.enable [ - /var/db/sudo/lectured + { directory = /var/db/sudo/lectured; user = "root"; group = "root"; mode = "0700"; } ]) ++ cfg.directories); files = map toString ([ # hardware-related /etc/adjtime + # needed at least for /var/log /etc/machine-id ] ++ cfg.files); }; diff --git a/system/modules/vfio.nix b/system/modules/vfio.nix index 5af250b..b347887 100644 --- a/system/modules/vfio.nix +++ b/system/modules/vfio.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ options, config, lib, pkgs, ... }: let cfg = config.vfio; in { @@ -73,9 +73,10 @@ in { description = "VFIO settings"; default = { }; }; + # compatibility so this module loads on non-amd hardware config = let - enableIvshmem = config.vfio.lookingGlass.enable && (builtins.length config.vfio.lookingGlass.ivshmem) > 0; - in lib.mkIf config.vfio.enable { + enableIvshmem = cfg.lookingGlass.enable && (builtins.length cfg.lookingGlass.ivshmem) > 0; + in lib.mkIf cfg.enable { # add a custom kernel param for early loading vfio drivers # because if we change boot.initrd options in a specialization, two initrds will be built # and we don't want to build two initrds @@ -151,9 +152,13 @@ in { SUBSYSTEM=="kvmfr", KERNEL=="kvmfr${toString i}", OWNER="${ivshmem.owner}", GROUP="kvm", MODE="0660" '') cfg.lookingGlass.ivshmem)); - # disable early KMS so GPU can be properly unbound - hardware.amdgpu.loadInInitrd = lib.mkIf (!cfg.nvidiaGpu) false; - hardware.opengl.enable = true; + hardware = { + opengl.enable = true; + } // (lib.optionalAttrs (cfg.enable && !(cfg.nvidiaGpu)) { + # disable early KMS so GPU can be properly unbound + # can't use mkif because the option may not even exist + amdgpu.loadInInitrd = false; + }); # needed for virt-manager programs.dconf.enable = true; virtualisation.libvirtd = { diff --git a/system/pkgs/default.nix b/system/pkgs/default.nix index b09397b..cda8b20 100644 --- a/system/pkgs/default.nix +++ b/system/pkgs/default.nix @@ -1,3 +1,5 @@ { pkgs, ... }: let inherit (pkgs) callPackage; in { - system76-scheduler = callPackage ../pkgs/system76-scheduler.nix { }; + system76-scheduler = callPackage ./system76-scheduler.nix { }; + maubot = callPackage ./maubot.nix { }; + pineapplebot = callPackage ./pineapplebot.nix { }; } diff --git a/system/pkgs/maubot.nix b/system/pkgs/maubot.nix new file mode 100644 index 0000000..2da0c85 --- /dev/null +++ b/system/pkgs/maubot.nix @@ -0,0 +1,95 @@ +{ lib +, fetchpatch +, python3 +, runCommand +, encryptionSupport ? true +}: + +let + python = python3.override { + packageOverrides = self: super: { + sqlalchemy = super.buildPythonPackage rec { + pname = "SQLAlchemy"; + version = "1.3.24"; + + src = super.fetchPypi { + inherit pname version; + sha256 = "sha256-67t3fL+TEjWbiXv4G6ANrg9ctp+6KhgmXcwYpvXvdRk="; + }; + + postInstall = '' + sed -e 's:--max-worker-restart=5::g' -i setup.cfg + ''; + + doCheck = false; + }; + }; + }; + + self = with python.pkgs; buildPythonPackage rec { + pname = "maubot"; + version = "0.4.1"; + disabled = pythonOlder "3.8"; + + src = fetchPypi { + inherit pname version; + sha256 = "sha256-Ro2PPgF8818F8JewPZ3AlbfWFNNHKTZkQq+1zpm3kk4="; + }; + + patches = [ + # add entry point + (fetchpatch { + url = "https://patch-diff.githubusercontent.com/raw/maubot/maubot/pull/146.patch"; + sha256 = "0yn5357z346qzy5v5g124mgiah1xsi9yyfq42zg028c8paiw8s8x"; + }) + ]; + + propagatedBuildInputs = [ + # requirements.txt + mautrix + aiohttp + yarl + sqlalchemy + asyncpg + aiosqlite + CommonMark + ruamel-yaml + attrs + bcrypt + packaging + click + colorama + questionary + jinja2 + ] + # optional-requirements.txt + ++ lib.optionals encryptionSupport [ + python-olm + pycryptodome + unpaddedbase64 + ]; + + passthru.tests = { + simple = runCommand "${pname}-tests" { } '' + ${self}/bin/mbc --help > $out + ''; + }; + + # Setuptools is trying to do python -m maubot test + dontUseSetuptoolsCheck = true; + + pythonImportsCheck = [ + "maubot" + ]; + + meta = with lib; { + description = "A plugin-based Matrix bot system written in Python"; + homepage = "https://github.com/maubot/maubot"; + changelog = "https://github.com/maubot/maubot/blob/v${version}/CHANGELOG.md"; + license = licenses.agpl3Plus; + maintainers = with maintainers; [ chayleaf ]; + }; + }; + +in +self diff --git a/system/pkgs/pineapplebot.nix b/system/pkgs/pineapplebot.nix new file mode 100644 index 0000000..66787fd --- /dev/null +++ b/system/pkgs/pineapplebot.nix @@ -0,0 +1,34 @@ +{ python3 +, fetchFromGitHub +, rustPlatform +, magic ? "" +, ... }: + +python3.pkgs.buildPythonPackage rec { + pname = "pineapplebot"; + version = "0.1.0"; + src = fetchFromGitHub { + owner = "chayleaf"; + repo = "pizzabot_v3"; + rev = "master"; + sha256 = "sha256-ZLskMlllZfmqIlbSr0pNHHJehDycohiwqgYbuEYP7Qc="; + }; + preBuild = '' + head -n13 Cargo.toml > Cargo.toml.new + mv Cargo.toml.new Cargo.toml + ''; + sourceRoot = "source/pineapplebot"; + cargoDeps = rustPlatform.fetchCargoTarball { + inherit src sourceRoot; + name = "${pname}-${version}"; + sha256 = "14jxgykwg1apy97gy1j8mz7ny2cqg4q9s03a2bk9kx2y6ibm4668"; + }; + nativeBuildInputs = with rustPlatform; [ + cargoSetupHook + maturinBuildHook + ]; + doCheck = false; + doInstallCheck = true; + pythonImportsCheck = [ "pineapplebot" ]; + PIZZABOT_MAGIC = magic; +} diff --git a/system/private.nix.sample b/system/private.nix.sample deleted file mode 100644 index 498b965..0000000 --- a/system/private.nix.sample +++ /dev/null @@ -1,5 +0,0 @@ -{ - nixmsi = { ... }: { - # insert private config here - }; -} diff --git a/system/private.sample.nix b/system/private.sample.nix new file mode 100644 index 0000000..372fbd2 --- /dev/null +++ b/system/private.sample.nix @@ -0,0 +1,9 @@ +{ + nixmsi = { pkgs, lib, ... }: { + # insert private config here + # time.timeZone = ...; + # users.users.root.initialHashedPassword = ...; + # users.users.user.initialHashedPassword = ...; + }; + nixserver = { ... }: { }; +}