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 = { ... }: { };
+}