{ config, lib, pkgs, ... }:

{
  imports = [
    ./hardware.nix
    ./variables.nix
    ./packages.nix
    ./jobs.nix
    ./matrix.nix
    ./magnetico.nix
    ./nameserver.nix
    ./custom
    ./secrets
  ];

  ### State
  # Stateful things to do before updating:
  # 1. Postgres migration
  # 2. Matrix Synapse migration
  system.stateVersion = "20.03";

  boot.kernelPackages = pkgs.linuxPackages_latest;
  boot.tmpOnTmpfs = 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;

  networking = {
    hostName = "maxwell";

    firewall.allowedTCPPorts = [
      443 80 # reverse proxy 
      8080   # hubot
      5349   # turn server
      5350   # turn server
      3551   # apcups
      5001   # iperf server
      18080  # monero p2p
      20000  # syncthing transfert
      64738  # mumble server
    ];
    firewall.allowedUDPPorts = [
      53    # powerdns
      1194  # dnscrypt
      21027 # syncthing discovery
      64738 # mumble server
    ];
    firewall.allowedUDPPortRanges = [
      { from=49152; to=49999; } # turn relay
    ];

    usePredictableInterfaceNames = false;
    nameservers = [ "127.0.0.1" ];
    hosts."127.0.0.1" = [ config.var.hostname ];
  };

  # Only declarative users and no password logins
  users.mutableUsers = false;

  users.users ={
    # Only needed for local (read emergency) shell access
    root.passwordFile = 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];
    };

    # Runs two chatbots
    meme = {
      extraGroups  = [ "ubino" "miguelbridge" ];
      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 = [ config.secrets.publicKeys.rnhmjoj-builder ];
    };

    # Use "git" instead of the default name to make
    # SSH operation handier, example:
    #   git clone git@maxwell:user/repo
    git = {
      description = "Git server user";
      home = "/var/lib/gitea";
      useDefaultShell = true;
    };
  };

  # 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;
    extraConfig =
    let 
      path = "/run/current-system/sw/bin";
      journal  = name: "${path}/journalctl -* ${name}";
      services = lib.concatMapStringsSep "," (name: "${journal name}");
    in ''
      # Allow meme to see his logs.
      Cmnd_Alias MEME_UNITS = ${services ["ubino" "miguelbridge"]}
      meme ALL=(root) NOPASSWD: MEME_UNITS
    '';
  };

  security.polkit.extraConfig = ''
    // Allow meme to manage his services.
    polkit.addRule(function(action, subject) {
      if (action.id == "org.freedesktop.systemd1.manage-units" &&
          subject.user == "meme" &&
          (action.lookup("unit") == "ubino.service" ||
           action.lookup("unit") == "miguelbridge.service")) {
        return polkit.Result.YES;
      }
    });
  '';

  # Limit user process to stop fork bombs
  security.pam.loginLimits = [ 
    { domain = "@users";
      type   = "hard"; 
      item   = "nproc";
      value  = "400";
    }
  ];
  
  ### ACME certificates
  security.acme = with config.var; {
    email = "rnhmjoj@inventati.org";
    acceptTerms = true;

    certs."${hostname}" = {
      group = "maxwell-ydns-eu";
    };

    certs."riot.${hostname}" = {
      group = "riot-maxwell-ydns-eu";
    };
  };

  # Allow read access to ACME certificate
  # to specific (service) users.
  users.groups."maxwell-ydns-eu".members = [ "murmur" "turnserver" ];
  users.groups."riot-maxwell-ydns-eu".members = [ "nginx" ];


  services.openssh = {
    enable = true;
    permitRootLogin = "no";
    passwordAuthentication = false;
    challengeResponseAuthentication = 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;
    password = "allwellthatmaxwell";
    registerHostname = config.var.hostname;
    registerName = "Maxwell Mumble";
    registerPassword = config.secrets.murmur.password;
    users = 10;
    extraConfig = with config.var; ''
      sslCert=/var/lib/acme/${hostname}/fullchain.pem
      sslKey=/var/lib/acme/${hostname}/key.pem
    '';
  };

  ### Syncthing node
  services.syncthing = {
    enable = true;
    openDefaultPorts = 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;
    domain  = hostname;
    appName = "Maxwell git server";
    rootUrl = "https://${hostname}/git/";
    user    = "git";
    database.user = "git";
    log.level           = "Error";
    cookieSecure        = true;
    disableRegistration = false;
    settings = {
      security.LOGIN_REMEMBER_DAYS = 365;
      attachment.MAX_SIZE = 10;
    };
  };

  ### Searx instance
  services.searx = {
    enable = true;
    configFile = ./assets/searx-settings.yml;
  };


  ### 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
  {
    enable = true;
    enableReload = true;
    commonHttpConfig = ''
      # recommendedTlsSettings  = true;
      # android doesn't like this one:
      # ssl_ecdh_curve secp384r1;
      ssl_session_cache shared:SSL:42m;
      ssl_session_timeout 23m;
      ssl_prefer_server_ciphers on;
      ssl_stapling on;
      ssl_stapling_verify on; 
    '';
    recommendedGzipSettings  = true;
    recommendedProxySettings = true;

    # 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 = disableLog + enableSTS;

      # Returns IP address
      locations."/ip".extraConfig = "return 200 $remote_addr;";

      # Asjon code coverage reports 
      locations."/asjon/report/" = {
        index = "index.html";
        alias = "/var/lib/asjon/tree/report/";
      };

      # Searx instance
      locations."/srx/" = {
        proxyPass = "http://localhost:8083/";
        extraConfig = ''
          proxy_set_header X-Scheme $scheme;
          proxy_set_header X-Script-Name /srx/;
          proxy_buffering off;
        '';
      };
      
      # Git server
      locations."/git/" .proxyPass = "http://localhost:3000/";

      # Syncthing
      locations."/sync/".proxyPass = "http://localhost:8384/";
    };

    # 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" = {
      locations."/".alias = "/home/giu/cactalogue/";
      extraConfig = disableLog;
    };
  };


  ### Misc. services
  services.ubino.enable = true;
  services.miguelbridge.enable = true;
  services.asjon.enable = true;

  # Needed for the Asjon memory module
  services.redis.enable = true;


  ### Program configuration
  programs = {
    fish.enable = true;
    mosh.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 = {
    useSandbox = true;
    # Can connect to the Nix daemon
    # and upload/run code as root!
    trustedUsers = [ "builder" "rnhmjoj" ];
    # Use at most half the cores
    buildCores = 8;
    extraOptions = ''
      # Always keep at least 256MiB free
      min-free = 268435456
    '';
  };

  environment.variables = {
    PATH            = "$HOME/.local/bin/:$PATH";
    XDG_CONFIG_HOME = "$HOME/.config";
    XDG_DATA_HOME   = "$HOME/.local/share";
    XDG_CACHE_HOME  = "$HOME/.cache";
    NIX_PROFILE     = "$XDG_CONFIG_HOME/nix/profile";
  };

  # Needed to make the mosh server survive a
  # user logout: systemd kills everything by default
  environment.shellAliases = {
    mosh-server = "systemd-run --user --scope mosh-server";
  };

}