{ config, lib, pkgs, ... }: { imports = [ ./hardware.nix ./variables.nix ./packages.nix ./jobs.nix ./matrix.nix ./email.nix ./magnetico.nix ./nameserver.nix ./custom ./secrets ./fish.nix ./neovim.nix ]; ### State # Stateful things to do before updating: # 1. Postgres migration (https://www.postgresql.org/docs/current/upgrading.html) # 2. Matrix Synapse migration (https://matrix-org.github.io/synapse/latest/upgrade.html) system.stateVersion = "23.05"; nixpkgs.source = builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/c505ebf77752.tar.gz"; sha256 = "0sk76hr3l7gd85aa3j9y5j1napvx18xb762fdqz3ibf717s8br5g"; }; boot.kernelPackages = pkgs.linuxPackages_latest; boot.tmp.useTmpfs = true; boot.kernel.sysctl = { # avoid OOM hangs "vm.admin_reserve_kbytes" = 262144; }; time.timeZone = "Europe/Rome"; i18n.defaultLocale = "en_US.UTF-8"; systemd.enableEmergencyMode = false; systemd.oomd.enable = false; networking = { hostName = "maxwell"; firewall.allowedTCPPorts = [ 53 # dns 443 80 # reverse proxy 993 # imaps server 25 465 # smtp(s) server 18080 # monero p2p 22000 # syncthing transfer 64738 # mumble server ]; firewall.allowedUDPPorts = [ 500 # ipsec 53 # dns 21027 # syncthing discovery 64738 # mumble server ]; nftables.enable = true; firewall.extraInputRules = '' meta l4proto esp counter accept comment "allow IPsec" ip saddr 192.168.1.0/24 tcp dport apcupsd accept comment "allow UPS from LAN" ''; usePredictableInterfaceNames = false; nameservers = [ "127.0.0.1" ]; # ensure hostname work without DNS hosts = with config.var; { ${ipv4LanAddress} = [ hostname ]; ${ipv6Address} = [ hostname ]; }; }; # Only declarative users and no password logins users.mutableUsers = false; users.users ={ # Only needed for local (read emergency) shell access root.hashedPasswordFile = config.secrets.passwords.root; # Admin rnhmjoj = { uid = 1000; extraGroups = [ "wheel" ]; isNormalUser = true; shell = pkgs.fish; openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.rnhmjoj ]; }; # Admin fazo = { extraGroups = [ "wheel" ]; isNormalUser = true; openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.fazo ]; }; # User meme = { isNormalUser = true; shell = pkgs.fish; openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.meme ]; }; # Hosts the cactalogo giu = { isNormalUser = true; shell = pkgs.fish; openssh.authorizedKeys.keyFiles = with config.secrets.publicKeys; [ rnhmjoj giu ]; }; # Needed to perform remote builds on Maxwell builder = { description = "Remote Nix builds user"; isNormalUser = true; openssh.authorizedKeys.keyFiles = with config.secrets.publicKeys; [ rnhmjoj-builder giu-builder ]; }; # Use "git" instead of the default name to make # SSH operation handier, example: # git clone git@maxwell:user/repo git = { group = "git"; description = "Git server user"; home = "/var/lib/gitea"; isSystemUser = true; useDefaultShell = true; }; }; users.groups.git = { }; # Generate Diffie-Hellman parameters # for TLS applications, like nginx. security.dhparams = { enable = true; params.nginx = 2048; # prime modulus bits }; security.sudo = { enable = true; # Users don't have a password wheelNeedsPassword = false; }; security.pam.loginLimits = [ # Limit user process to stop fork bombs { domain = "@users"; type = "hard"; item = "nproc"; value = "400"; } # Disable core dumping { domain = "*"; type = "soft"; item = "core"; value = "0"; } ]; ### ACME certificates security.acme = { defaults.email = "rnhmjoj@inventati.org"; acceptTerms = true; certs."maxwell.eurofusion.eu" = { group = "maxwell-eurofusion-eu"; }; certs."eurofusion.eu" = { group = "eurofusion-eu"; }; }; # Allow read access to ACME certificate # to specific (service) users. users.groups."maxwell-eurofusion-eu".members = [ "murmur" "nginx" ]; users.groups."eurofusion-eu".members = [ "nginx" ]; services.openssh = { enable = true; settings.PermitRootLogin = "no"; settings.PasswordAuthentication = false; settings.KbdInteractiveAuthentication = false; }; # Traceroute easter egg services.fakeroute = { enable = true; route = [ "89.111.117.32" "99.97.110.110" "111.116.32.104" "105.100.101.46" "32.73.32.115" "101.101.32.121" "111.117.46.32" "84.104.101.114" "101.32.105.115" "32.110.111.32" "108.105.102.101" "32.105.110.32" "116.104.101.32" "118.111.105.100" "46.32.79.110" "108.121.32.100" "101.97.116.104" ]; }; ### Mumble server services.murmur = { enable = true; registerHostname = config.var.hostname; registerName = "Maxwell Mumble"; registerPassword = "$REG_PASSWORD"; password = "$JOIN_PASSWORD"; users = 10; environmentFile = config.secrets.environments.murmur; sslCert = "/var/lib/acme/${config.var.hostname}/fullchain.pem"; sslKey = "/var/lib/acme/${config.var.hostname}/key.pem"; }; ### Syncthing node services.syncthing = { enable = true; }; ### Monero node with local RPC services.monero = { enable = true; mining = { enable = false; threads = 4; address = config.secrets.monero.address; }; limits = { upload = 250; download = 625; threads = 4; }; rpc.user = config.secrets.monero.user; rpc.password = config.secrets.monero.password; }; ### URL shortner services.breve = with config.secrets; { enable = true; hostname = "localhost"; baseUrl = "https://brve.bit/"; port = 2000; certificate = certs.breve.crt; key = certs.breve.key; }; ### Git server services.gitea = with config.var; { enable = true; appName = "Maxwell git server"; user = "git"; database.user = "git"; settings = { server.ROOT_URL = "https://${hostname}/git/"; server.domain = hostname; session.COOKIE_SECURE = true; log.LEVEL = "Error"; service.DISABLE_REGISTRATION = false; # increase cookie expiration time security.LOGIN_REMEMBER_DAYS = 365; # file upload size (MB) attachment.MAX_SIZE = 10; # new users can only create PR/issues service.DEFAULT_ALLOW_CREATE_ORGANIZATION = false; repository.MAX_CREATION_LIMIT = 0; # somewhat limit spam service.EMAIL_DOMAIN_BLOCKLIST = "gmail.com"; # allow the notify webhook to use matrix webhook.ALLOWED_HOST_LIST = "maxwell.eurofusion.eu"; }; }; ### Searx instance services.searx = { enable = true; environmentFile = config.secrets.environments.searx; package = pkgs.searxng; # Use nginx+uWSGI runInUwsgi = true; uwsgiConfig = { disable-logging = true; # serve using the uwsgi protocol socket = "/run/searx/uwsgi.sock"; chmod-socket = "660"; # use /searx as url "mountpoint" mount = "/srx=searx.webapp:application"; module = ""; manage-script-name = true; # caching cache2 = lib.concatStringsSep "," [ "name=searxcache" "items=2000" "blocks=2000" "blocksize=4096" "bitmap=1" ]; }; settings = { general.instance_name = "searxwell"; server.base_url = "https://${config.var.hostname}/"; server.secret_key = "@SEARX_SECRET@"; # Replace DOI links with Sci-Hub default_doi_resolver = "sci-hub.st"; ## Use authenticated APIs for some services engines = [ { name = "wolframalpha"; api_key = "@WOLFRAM_API_KEY@"; } { name = "youtube"; api_key = "@YOUTUBE_API_KEY@"; } ]; }; }; # Allow nginx access to the uwsgi socket users.groups."searx".members = [ "nginx" ]; ### Reverse Proxy services.nginx = with config.var; let disableLog = '' error_log syslog:server=unix:/dev/log crit; access_log off; ''; enableSTS = '' add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; ''; in rec { enable = true; enableReload = true; recommendedTlsSettings = true; recommendedGzipSettings = true; recommendedProxySettings = true; appendHttpConfig = disableLog; # Large enough to allow file uploads. clientMaxBodySize = "1000M"; sslDhparam = "${config.security.dhparams.path}/nginx.pem"; # Maxwell virtualHosts."${hostname}" = { enableACME = true; forceSSL = true; default = true; extraConfig = enableSTS; # Returns IP address locations."/ip".extraConfig = '' default_type text/plain; return 200 $remote_addr; ''; # Asjon code coverage reports locations."/asjon/report/" = { index = "index.html"; alias = "/run/nginx/static/asjon/"; }; # Searx instance locations."/srx/".extraConfig = '' include ${pkgs.nginx}/conf/uwsgi_params; uwsgi_pass unix:/run/searx/uwsgi.sock; ''; locations."/srx/static/".alias = "${config.services.searx.package}/share/static/"; # Git server locations."/git/".proxyPass = "http://localhost:3000/"; # Syncthing locations."/sync/".proxyPass = "http://localhost:8384/"; # User static files locations."/~rnhmjoj/" = { alias = "/run/nginx/static/rnhmjoj/"; extraConfig = '' charset UTF-8; # directories with listing location ~ /~rnhmjoj/[^/]+.index/ { autoindex on; } ''; }; locations."/~giu/" = { alias = "/run/nginx/static/giu/"; extraConfig = "charset UTF-8;"; }; }; # Breve URL shortner virtualHosts."brve.bit" = with config.secrets; { forceSSL = true; sslCertificate = certs.breve.crt; sslCertificateKey = certs.breve.key; locations."/" = { proxyPass = "https://localhost:2000"; extraConfig = "proxy_ssl_verify off;"; }; extraConfig = disableLog; }; # The Cactalogue virtualHosts."cacta.bit" = { root = "/run/nginx/static/cactalogue"; extraConfig = disableLog; }; virtualHosts."cacta.eurofusion.eu" = virtualHosts."cacta.bit"; }; # Bind mount directories for Nginx # This avoids giving nginx traversal permission systemd.mounts = let bindNginx = from: to: { what = from; where = "/run/nginx/static/" + to; type = "none"; options = "bind"; wantedBy = [ "nginx.service" ]; }; in [ (bindNginx "/home/rnhmjoj/www" "rnhmjoj") (bindNginx "/home/giu/www" "giu") (bindNginx "/home/giu/cactalogue" "cactalogue") (bindNginx "/var/lib/asjon/tree/report" "asjon") ]; ### IPsec mesh environment.etc."ipsec.d/mesh.secrets".source = config.secrets.passwords.mesh; services.libreswan.enable = true; services.libreswan.connections.mesh = '' leftid=@wes left=2a01:e11:1001:53ea::1 rightid=@maxwell right=${config.var.ipv6Address} authby=secret type=transport auto=ondemand failureshunt=drop negotiationshunt=hold ''; ### Misc. services services.asjon.enable = true; # Needed for the Asjon memory module services.redis.servers."asjon" = { enable = true; user = "asjon"; }; # Emergency SSH access via tor services.tor = { enable = true; client.enable = false; relay.onionServices.emergency-access.map = [ 22 ]; }; ### Program configuration programs = { fuse.userAllowOther = true; fish.enable = true; tmux = { enable = true; newSession = true; baseIndex = 1; escapeTime = 0; historyLimit = 4096; keyMode = "vi"; terminal = "screen-256color"; customPaneNavigationAndResize = true; extraConfig = '' set -g mouse on # bindings bind | split-window -h bind - split-window -v bind : command-prompt bind -n C-k clear-history # colors set -g pane-border-style fg=brightblack set -g pane-active-border fg=green set -g message-style fg=white,bg=black set -g status-style fg=brightblue,bg=black setw -g mode-style fg=black,bg=cyan # status line set -g status on set -g status-justify left set -g status-left "" set -g status-right-length 60 set -g status-right '#[fg=yellow]#(cut -d\ -f 1-3 /proc/loadavg) | #[fg=brightgreen]%a %H:%M' setw -g window-status-format "#[fg=black#,bg=brightblack] #I #[fg=blue#,bg=black] #W " setw -g window-status-current-format "#[fg=white#,bg=cyan] #I #[fg=black#,bg=brightblack] #W " ''; }; }; nix.settings = { # Can connect to the Nix daemon # and upload/run code as root! trusted-users = [ "builder" "rnhmjoj" ]; # Use at most half the cores cores = 8; max-jobs = 16; # Always keep at least 256MiB free min-free = 268435456; }; environment.sessionVariables = { PATH = [ "$HOME/bin" ]; XDG_CONFIG_HOME = "$HOME/etc"; XDG_DATA_HOME = "$HOME/var/lib"; XDG_CACHE_HOME = "$HOME/var/cache"; SYSTEMD_COLORS = "16"; }; }