+# Build or reuse .cache/nix/
source_env .lib/nix/envrc.sh
+
+# Rebuild the .cache/nix/ when a file changes in shell/
+# and its subdirectories.
while read -r dir
do watch_file "$dir"
done <<EOF
-$(find build -type d)
+$(find shell/ -type d)
EOF
.bin
.cache/
+.config/nix/nix.conf
.mnt/
.sec/
.old/
[submodule "config/gitolite"]
path = config/gitolite
url = ./config/gitolite
-[submodule ".lib/nixpkgs-sourcephile"]
- path = .lib/nixpkgs-sourcephile
- url = ../src/nix/nixpkgs-sourcephile
+++ /dev/null
-Subproject commit 4e671efa83a5c074730f7ea165b1b37eb130fee6
include .lib/nix/Makefile.make
include .lib/nixops/Makefile.make
-include bootstrap/mermet/Makefile.make
+include machines/mermet/Makefile
#
## init
init: build
result/bin/init
+
+#sudo zfs mount rpool/ROOT/$(machine)
+#sudo zfs create -o canmount=noauto -o mountpoint=legacy bpool/BOOT/$(machine)
+#sudo zfs mount bpool/BOOT/$(machine)
+#blkid -t TYPE=ext2 $(disk)-part1; test $$? != 2 || \
+#sudo $$(which mkfs.ext2) $(disk)-part1
+#sudo $$(which tune2fs) -L mermet-root -m 5 $(disk)-part1
+#blkid -t TYPE=swap $(disk)-part2; test $$? != 2 || \
+#sudo $$(which mkswap) -L mermet-swap $(disk)-part2
+#blkid -t TYPE=ext4 $(disk)-part3; test $$? != 2 || \
+#sudo $$(which mkfs.ext4) $(disk)-part3
+#sudo $$(which tune2fs) -L mermet-root -m 5 $(disk)-part3
+#blkid -t TYPE=ext4 $(disk)-part4; test $$? != 2 || \
+#sudo $$(which mkfs.ext4) $(disk)-part4
+#sudo $$(which tune2fs) -L mermet-data -m 0 $(disk)-part4
+#sudo $$(which gdisk) -l $(disk)
+#sudo $$(which mdadm) --create /disk/md/$(machine) --level raid1 --raid-devices=2 --metadata=1.2 $(disk) missing
+++ /dev/null
-# Fichier principal de configuration de NixOS
-# TODO: penser à git commit les modifications.
-#
-# Commandes usuelles pour configurer NixOS :
-# - nix-upgrade : Applique au système la présente configuration.
-# NOTE: à faire pour prendre en compte toute modification,
-# comme l’ajout ou la suppression d’un logiciel.
-#
-# - nix-rollback : Applique au système la précédente configuration disponible.
-# NOTE: utile pour retrouver un état fonctionnel
-# si problème avec la nouvelle configuration.
-#
-# - nix-history : Liste les anciennes configurations encore diponibles.
-#
-# - nix-clean : Supprime les anciennes configurations
-# (vers lesquelles faire un nix-rollback)
-# et libère l’espace utilisé par les paquets inutilisés
-# (avec le ramasse-miettes, aka. garbage collector).
-# NOTE: à faire si le système convient depuis un moment
-# ou si manque d’espace sur la partition racine.
-#
-# - nix-catalog : Liste les catalogues des paquets disponibles.
-#
-# - nix-update : Récupère les mise-à-jours depuis les catalogues des paquets disponibles.
-# NOTE: à faire de temps en temps avant un nix-upgrade.
-#
-# - nix-upstream : Affiche la version de NixOS stabilisée bi-annuellement
-# (de mars ou de septembre) et utilisée
-# pour récupérer le catalogue des paquets (avec nix-update).
-# NOTE: À changer grosso-modo en avril et en octobre,
-# par exemple en avril 2019 :
-# $ sudo nix-channel --remove nixos
-# $ sudo nix-channel --add https://nixos.org/channels/nixos-19.03 nixos
-# et en octobre 2019 :
-# $ sudo nix-channel --remove nixos
-# $ sudo nix-channel --add https://nixos.org/channels/nixos-19.09 nixos
-#
-# C’est une manip' qui pourrait être évitée en suivant
-# https://nixos.org/channels/nixos-unstable,
-# mais c’est plus... stable comme ça.
-
-# Entête usuelle d”une configuration de NixOS.
-{ config, pkgs, ... }: {
-
-# Import d’autres fichiers de configuration.
-imports =
- [ ./hardware.nix # Fichier de configuration du matériel.
- # NOTE: à modifier seulement en cas de changement
- # d’ordinateur ou de disque dur.
- ./system.nix # Fichier de configuration de base du système.
- # NOTE: pour la configuration experte.
- ];
-
-# Configuration de l’user sevy
-users.users.sevy = {
- isNormalUser = true;
- uid = 1000;
- extraGroups = [
- "wheel" # Permet d’utiliser sudo.
- "networkmanager"
- ];
-};
-
-# Liste de programmes à installer
-environment.systemPackages = with pkgs; [
- # Internet
- amule
- chromium
- firefox
- liferea
- transmission-gtk
- thunderbird
-
- # Bureautique
- #djview
- evince
- geeqie
- gimp
- git
- gnome3.gnome-calculator
- hledger
- imagemagick # convert, mogrify, ...
- parcellite # Presse papier
- libreoffice
- vimHugeX # gvim
- xsane # scan
-
- # Sécurité
- gnupg
- networkmanager-openvpn
- pass # Trousseau de clés
- # - Créer : pass generate example.com/mon-compte@mon-mail.coop 24
- # - Récupérer : pass -c example.com/mon-compte@mon-mail.coop
- #torbrowser
- firefoxPackages.tor-browser
-
- # Multimédia
- libdvdcss # Clés pour lire les DVD
- mplayer
- vlc
- #youtubeDL
-
- # Jeux
- #freeciv_gtk
-];
-# Le nom du paquet est suggéré en essayant
-# de lancer dans un terminal une commande qu’il fournit,
-# par exemple :
-# $ inkscape
-# The program ‘inkscape’ is currently not installed. You can install it by typing:
-# nix-env -iA nixos.inkscape
-#
-# NOTE: installer avec nix-env est possible
-# mais cela revient à fonctionner comme avec apt install sous Debian
-# c’est-à-dire à perdre la reproductibilité que fournit
-# la déclaration complète de ce que l’on veut en l’écrivant
-# dans le présent fichier de configuration.
-# Mais ça peut être pertinent pour tester de manière jetable.
-#
-# Alternativement, on peut chercher un paquet ainsi :
-# $ nix search inkscape
-# * nixpkgs.inkscape (inkscape)
-# Vector graphics editor
-
-# VirtualBox
-virtualisation.virtualbox.host.enable = true;
-users.extraUsers.sevy.extraGroups = ["vboxusers"];
-}
+++ /dev/null
-{ config, lib, pkgs, ... }: {
-imports =
- [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix>
- ];
-
-# https://bugzilla.kernel.org/show_bug.cgi?id=110941
-boot.kernelParams = [ "intel_pstate=no_hwp" ];
-boot.kernelModules = [ "kvm-intel" ];
-boot.extraModulePackages = [
- config.boot.kernelPackages.exfat-nofuse
-];
-boot.extraModprobeConfig = ''
- options thinkpad_acpi fan_control=1
-'';
-
-boot.loader.grub = {
- enable = true;
- version = 2;
- device = "/dev/disk/by-id/ata-Samsung_SSD_860_EVO_250GB_S3YJNX0K863141Y";
- #enableCryptodisk = true;
-};
-
-boot.initrd.availableKernelModules = [
- "aes_x86_64"
- "aesni_intel"
- "ahci"
- "cryptd"
- "dm_crypt"
- "dm_mod"
- "drbg"
- "ehci_pci"
- "gf128mul"
- "hmac"
- "sd_mod"
- "sha256_generic"
- "uas"
- "xts"
- ];
-boot.initrd.luks.cryptoModules = ["aes_x86_64" "sha256" "sha1" "xts"];
-boot.initrd.luks.devices = [
- {
- name = "crypted";
- device = "/dev/disk/by-id/ata-Samsung_SSD_860_EVO_250GB_S3YJNX0K863141Y-part2";
- allowDiscards = true;
- #preLVM = false;
- }
-];
-
-boot.kernel.sysctl = {
- "vm.swappiness" = 10;
- "vm.vfs_cache_pressure" = 50;
-};
-
-fileSystems."/boot" =
- { device = "/dev/disk/by-uuid/8f84431f-f646-4da9-9f34-af0f7d9bb914";
- fsType = "ext4";
- };
-
-fileSystems."/" =
- {
- device = "/dev/disk/by-uuid/30c38c75-4fc7-4185-a7db-b5e2c989e853";
- fsType = "ext4";
- options = [ "noatime" "nodiratime" "discard" ];
- };
-fileSystems."/home" =
- { device = "/dev/disk/by-uuid/62ca342a-9c6e-47bf-9eac-bc8c28b491bb";
- fsType = "ext4";
- options = [ "noatime" "nodiratime" "discard" ];
- };
-swapDevices =
- [ { device = "/dev/disk/by-uuid/27af6bf3-5884-49aa-aa5b-dae43c2fd634"; }
- ];
-
-nix.maxJobs = lib.mkDefault 4;
-powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
-}
+++ /dev/null
-{ config, pkgs, ... }: {
-
-nix = {
- extraOptions = ''
- auto-optimise-store = true
- '';
- gc = {
- automatic = true;
- dates = "weekly";
- options = "--delete-older-than 30d";
- };
-};
-
-nixpkgs.config = {
- allowUnfree = false;
-};
-
-time.timeZone = "Europe/Paris";
-
-i18n = { # internationalization
- consoleFont = "Lat2-Terminus16";
- consoleKeyMap = "fr";
- defaultLocale = "fr_FR.UTF-8";
-};
-
-networking = {
- networkmanager = {
- enable = true;
- };
- firewall = {
- enable = true;
- allowedTCPPorts = [
- 51413 # transmission-gtk
- 4662 # edonkey
- ];
- allowedUDPPorts = [
- 51413 # transmission-gtk
- 4667 # edonkey
- 4672 # edonkey
- ];
- };
-};
-
-sound.enable = true;
-hardware.pulseaudio.enable = true;
-
-environment = {
- variables = {
- EDITOR = "gvim";
- PAGER = "less -R";
- };
- systemPackages = with pkgs; [
- acpi
- binutils
- coreutils
- cryptsetup
- curl
- direnv
- dstat
- e2fsprogs
- elementary-xfce-icon-theme
- file
- glib # gio
- gnome3.defaultIconTheme
- gnome3.file-roller
- gnome3.gnome-keyring
- gnome3.seahorse
- gvfs
- gnumake
- gnupg
- gparted
- ffmpeg
- hicolor-icon-theme
- htop
- less
- libfaketime
- lsof
- man
- miniupnpc
- ncdu
- ncurses
- networkmanagerapplet
- ntfs3g
- #pasystray
- pavucontrol
- powertop
- procps
- python
- redshift
- sudo
- tig
- tmux
- tree
- utillinux
- wget
- which
- xdg_utils
- xfce.thunar-volman
- ];
-};
-
-programs = {
- bash = {
- interactiveShellInit = ''
- bind '"\e[A":history-search-backward'
- bind '"\e[B":history-search-forward'
-
- # Ignore duplicate commands, ignore commands starting with a space
- export HISTCONTROL=erasedups:ignorespace
- export HISTSIZE=42000
- # Append to the history instead of overwriting (good for multiple connections)
- shopt -s histappend
- fan () {
- if [ $# -gt 0 ]
- then sudo tee /proc/acpi/ibm/fan <<<"level $1"
- else grep '^\(level\|speed\):' /proc/acpi/ibm/fan
- fi
- acpi -t
- }
- '';
- shellAliases = {
- cl = "clear";
- l = "ls -alh";
- ll = "ls -l";
- ls = "ls --color=tty";
-
- s="sudo systemctl";
- s-u="systemctl --user";
-
- nix-clean="sudo nix-collect-garbage -d";
- nix-catalog="sudo nix-channel --list";
- nix-history="sudo nix-env --list-generations --profile /nix/var/nix/profiles/system";
- nix-rollback="sudo nixos-rebuild switch --rollback";
- nix-update="sudo nix-channel --update";
- nix-upgrade="sudo nixos-rebuild switch";
- nix-upstream="sudo nix-channel --list";
- nix-config="gvim ~/.config/nixos/*.nix";
- };
- };
-
- dconf.enable = true;
-
- gnupg.agent = {
- enable = true;
- enableSSHSupport = true;
- };
-
- mtr.enable = true;
-};
-
-services = {
- avahi = {
- enable = true;
- nssmdns = true;
- };
-
- dbus = {
- packages = [ pkgs.gnome3.dconf ];
- };
-
- gnome3 = {
- gvfs = {
- enable = true;
- };
- };
-
- physlock = {
- enable = true;
- allowAnyUser = true;
- # NOTE: xfconf-query -c xfce4-session -p /general/LockCommand -s "physlock" --create -t string
- };
-
- printing = {
- enable = true;
- drivers = [
- pkgs.gutenprint
- ];
- };
-
- xserver = {
- enable = true;
- layout = "fr";
- xkbOptions = "eurosign:e";
- libinput.enable = true;
-
- desktopManager = {
- default = "xfce";
- xfce = {
- enable = true;
- thunarPlugins = [
- pkgs.xfce.thunar-archive-plugin
- ];
- };
- xterm.enable = false;
- };
-
- };
-};
-
-# This value determines the NixOS release with which your system is to be
-# compatible, in order to avoid breaking some software such as database
-# servers. You should change this only after NixOS release notes say you should.
-system.stateVersion = "19.09"; # Did you read the comment?
-}
+++ /dev/null
-{config, ...}:
-{
- imports = [
- modules/gnupg.nix
- modules/nix-plugins.nix
- ];
- config = {
- init = {
- enable = true;
- };
- };
-}
+++ /dev/null
-{ config, ... }:
-{
- config = {
- nix-plugins = {
- enable = true;
- };
- };
-}
+++ /dev/null
-{
- network = {
- description = "Sourcephile";
- #enableRollback = true;
- };
-
- defaults = {
- imports = [ ];
- };
-
- # machines
- #friot = import logical/friot.nix;
- mermet = import mermet/configuration.nix;
-}
+++ /dev/null
-{pkgs, lib, config, system, ...}:
-let inherit (builtins.extraBuiltins) pass;
- inherit (lib) types;
- inherit (config) networking;
- inherit (config.services) dovecot2;
- userPass = name: pass "${networking.domainBase}/${networking.hostName}/login/${name}";
-in {
-imports = [
- <nixpkgs-sourcephile/install/modules.nix>
- ../options.nix
- ../overlays/tools/networking/shorewall/service.nix
- ../overlays/tools/networking/shorewall6/service.nix
- ../overlays/servers/mail/rspamd/service.nix
- friot/dovecot.nix
- friot/gitolite.nix
- friot/nginx.nix
- friot/nsd.nix
- friot/postfix.nix
- friot/postgrey.nix
- friot/postgresql.nix
- #friot/rmilter.nix
- friot/rspamd.nix
- #friot/redmine.nix
- friot/shorewall.nix
- friot/openldap.nix
- #friot/discourse.nix
-];
-config = {
- nix = {
- extraOptions = ''
- auto-optimise-store = true
- '';
- gc = {
- automatic = true;
- dates = "weekly";
- options = "--delete-older-than 30d";
- };
- };
-
- nixpkgs = {
- config = {
- allowUnfree = false;
- packageOverrides = pkgs: {
- postfix = pkgs.postfix.override {
- withLDAP = true;
- };
- };
- };
- overlays = import ../overlays.nix;
- };
-
- boot = {
- initrd = {
- network = {
- enable = config.deployment.targetEnv != "virtualbox";
- ssh = {
- enable = true;
- authorizedKeys = [ (pass "${networking.domain}/ssh/pub/julm") ];
- };
- };
- };
- kernel = {
- sysctl = {
- "vm.swappiness" = 10;
- "vm.vfs_cache_pressure" = 50;
- };
- };
- };
-
- time = {
- timeZone = "Europe/Paris";
- };
-
- i18n = {
- consoleFont = "Lat2-Terminus16";
- consoleKeyMap = "fr";
- defaultLocale = "fr_FR.UTF-8";
- };
-
- networking = {
- domainBase = "sourcephile";
- domain = "${networking.domainBase}.fr";
- domainAliases = [
- #"${networking.domainBase}.coop"
- ];
- };
-
- users = {
- mutableUsers = false;
- users = {
- root.initialPassword = userPass "root";
- root.password = config.users.users.root.initialPassword;
- julm = {
- uid = 1000;
- extraGroups = [ "sudo" ];
- description = "Julien Moutinho";
- home = "/home/julm";
- shell = lib.mkDefault config.users.defaultUserShell;
- group = "users"; # FIXME: unknown group
- initialPassword = userPass "julm";
- password = config.users.users.julm.initialPassword;
- };
- };
- groups = {
- julm = {
- gid = config.users.users.julm.uid;
- };
- };
- };
-
- documentation.nixos = {
- enable = false; # NOTE: useless on this machine, and CPU intensive.
- };
-
- services = {
- redis = {
- enable = true;
- };
- disnix = {
- enable = false;
- };
- openssh = {
- enable = true;
- extraConfig = ''
- '';
- };
- gitea = {
- enable = false;
- };
- sssd = {
- enable = false;
- };
- dovecot2 = {
- #debug = true;
- };
- journald = {
- extraConfig = ''
- SystemMaxUse=50M
- '';
- };
- x509 = {
- domains =
- lib.concatMap
- (dom: map (sub: "${sub}.${dom}")
- ["www" "git" "mail"])
- ([networking.domain] ++ networking.domainAliases)
- ++ networking.domainAliases;
- };
- #postfix.aliases = {
- # "root@${networking.domain}" = [ "test@${networking.domain}" ];
- # "postmaster@${networking.domain}" = [ "test@${networking.domain}" ];
- # "abuse@${networking.domain}" = [ "test@${networking.domain}" ];
- #};
- #dovecot2.domains = {
- # "${networking.domain}" = {
- # accounts = {
- # julm = {
- # password = pass "${networking.domain}/mail/julm";
- # # "${networking.domain}/dovecot2/julm";
- # # "{SSHA512}uyjL1KYx4z7HpfNvnKzuVxpMLD2KVueGGBvOcj7AF1EZCTVhT++IIKUVOC4xpZtWdqVD0OVmZqgYr2qpn/3t3Aj4oU0=";
- # aliases = ["julien.moutinho@${networking.domain}"];
- # quota = "512M";
- # };
- # test = {
- # password = pass "${networking.domain}/mail/test";
- # # "${networking.domain}/dovecot2/test";
- # # "{SSHA512}uyjL1KYx4z7HpfNvnKzuVxpMLD2KVueGGBvOcj7AF1EZCTVhT++IIKUVOC4xpZtWdqVD0OVmZqgYr2qpn/3t3Aj4oU0=";
- # aliases = ["test-alias@${networking.domain}"];
- # quota = "512M";
- # };
- # };
- # };
- #};
- };
-
- environment = {
- systemPackages = with pkgs; [
- htop
- tree
- vim
- tcpdump
- #mysql
- #procmail
- postgrey
- duplicity
- pypolicyd-spf
- unbound
- dropbear
- cryptsetup
- openssl
- postgresql
- openldap
- #mail
- #sympa
- multitail
- dnsutils
- inetutils
- binutils
- mailutils
- ncdu
- cgit
- #sssd
- #docker
- #nss_ldap
- #nss_pam_ldapd
- tmux
- socat
- users-init
- which
- ];
- etc."inputrc".text = ''
- # /etc/inputrc - global inputrc for libreadline
- # See readline(3readline) and `info rluserman' for more information.
-
- # Be 8 bit clean.
- set input-meta on
- set output-meta on
-
- # To allow the use of 8bit-characters like the german umlauts, uncomment
- # the line below. However this makes the meta key not work as a meta key,
- # which is annoying to those which don't need to type in 8-bit characters.
-
- # set convert-meta off
-
- # try to enable the application keypad when it is called. Some systems
- # need this to enable the arrow keys.
- # set enable-keypad on
-
- # see /usr/share/doc/bash/inputrc.arrows for other codes of arrow keys
-
- # do not bell on tab-completion
- # set bell-style none
- # set bell-style visible
-
- # some defaults / modifications for the emacs mode
- $if mode=emacs
-
- # allow the use of the Home/End keys
- "\e[1~": beginning-of-line
- "\e[4~": end-of-line
-
- # allow the use of the Delete/Insert keys
- "\e[3~": delete-char
- "\e[2~": quoted-insert
-
- # mappings for "page up" and "page down" to step to the beginning/end
- # of the history
- # "\e[5~": beginning-of-history
- # "\e[6~": end-of-history
-
- # alternate mappings for "page up" and "page down" to search the history
- # "\e[5~": history-search-backward
- # "\e[6~": history-search-forward
-
- # mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving
- "\e[1;5C": forward-word
- "\e[1;5D": backward-word
- "\e[5C": forward-word
- "\e[5D": backward-word
- "\e\e[C": forward-word
- "\e\e[D": backward-word
-
- $if term=rxvt
- "\e[7~": beginning-of-line
- "\e[8~": end-of-line
- "\eOc": forward-word
- "\eOd": backward-word
- $endif
-
- # for non RH/Debian xterm, can't hurt for RH/Debian xterm
- # "\eOH": beginning-of-line
- # "\eOF": end-of-line
-
- # for freebsd console
- # "\e[H": beginning-of-line
- # "\e[F": end-of-line
-
- $endif
- '';
- };
-
- programs = {
- bash = {
- interactiveShellInit = ''
- bind '"\e[A":history-search-backward'
- bind '"\e[B":history-search-forward'
-
- # Ignore duplicate commands, ignore commands starting with a space
- export HISTCONTROL=erasedups:ignorespace
- export HISTSIZE=42000
- # Append to the history instead of overwriting (good for multiple connections)
- shopt -s histappend
- '';
- shellAliases = {
- cl = "clear";
- l = "ls -alh";
- ll = "ls -l";
- ls = "ls --color=tty";
-
- s="sudo systemctl";
- s-u="systemctl --user";
-
- nix-clean="sudo nix-collect-garbage -d";
- nix-history="sudo nix-env --list-generations --profile /nix/var/nix/profiles/system";
- nix-rollback="sudo nixos-rebuild switch --rollback";
- nix-update="sudo nix-channel --update";
- nix-upgrade="sudo nixos-rebuild switch";
- nix-upstream="sudo nix-channel --list";
- nix-config="gvim ~/.config/nixos/*.nix";
- };
- };
-
- /*
- dconf.enable = true;
-
- gnupg.agent = {
- enable = true;
- enableSSHSupport = true;
- };
- */
-
- mtr.enable = true;
- };
-};
-}
+++ /dev/null
-{config, lib, pkgs, ...}:
-{
- options = {
- services.discourse = {
- hostname = lib.mkOption {
- type = lib.types.str;
- };
-
- config = lib.mkOption {
- type = lib.types.str;
- default =
- ''
- ## this is the all-in-one, standalone Discourse Docker container template
- ##
- ## After making changes to this file, you MUST rebuild
- ## /var/discourse/launcher rebuild app
- ##
- ## BE *VERY* CAREFUL WHEN EDITING!
- ## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
- ## visit http://www.yamllint.com/ to validate this file as needed
-
- templates:
- - "templates/postgres.template.yml"
- - "templates/redis.template.yml"
- - "templates/web.template.yml"
- - "templates/web.ratelimited.template.yml"
- ## Uncomment these two lines if you wish to add Lets Encrypt (https)
- #- "templates/web.ssl.template.yml"
- #- "templates/web.letsencrypt.ssl.template.yml"
-
- ## which TCP/IP ports should this container expose?
- ## If you want Discourse to share a port with another webserver like Apache or nginx,
- ## see https://meta.discourse.org/t/17247 for details
- expose:
- - "80:80" # http
- - "443:443" # https
-
- params:
- db_default_text_search_config: "pg_catalog.english"
-
- ## Set db_shared_buffers to a max of 25% of the total memory.
- ## will be set automatically by bootstrap based on detected RAM, or you can override
- db_shared_buffers: "256MB"
-
- ## can improve sorting performance, but adds memory usage per-connection
- #db_work_mem: "40MB"
-
- ## Which Git revision should this container use? (default: tests-passed)
- #version: tests-passed
-
- env:
- LANG: en_US.UTF-8
- # DISCOURSE_DEFAULT_LOCALE: en
-
- ## How many concurrent web requests are supported? Depends on memory and CPU cores.
- ## will be set automatically by bootstrap based on detected CPUs, or you can override
- UNICORN_WORKERS: 2
-
- ## TODO: The domain name this Discourse instance will respond to
- DISCOURSE_HOSTNAME: ${config.networking.domain}
-
- ## Uncomment if you want the container to be started with the same
- ## hostname (-h option) as specified above (default "$hostname-$config")
- #DOCKER_USE_HOSTNAME: true
-
- ## TODO: List of comma delimited emails that will be made admin and developer
- ## on initial signup example 'user1@example.com,user2@example.com'
- DISCOURSE_DEVELOPER_EMAILS: 'julm@autogeree.net'
-
- ## TODO: The SMTP mail server used to validate new accounts and send notifications
- DISCOURSE_SMTP_ADDRESS: smtp.${config.networking.domain}
- DISCOURSE_SMTP_PORT: 2525
- DISCOURSE_SMTP_USER_NAME: discourse@${config.networking.domain}
- DISCOURSE_SMTP_PASSWORD: password
- #DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true)
-
- ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
- #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com
-
- ## The CDN address for this Discourse instance (configured to pull)
- ## see https://meta.discourse.org/t/14857 for details
- #DISCOURSE_CDN_URL: //discourse-cdn.example.com
-
- ## The Docker container is stateless; all data is stored in /shared
- volumes:
- - volume:
- host: /var/discourse/shared/standalone
- guest: /shared
- - volume:
- host: /var/discourse/shared/standalone/log/var-log
- guest: /var/log
-
- ## Plugins go here
- ## see https://meta.discourse.org/t/19157 for details
- hooks:
- after_code:
- - exec:
- cd: $home/plugins
- cmd:
- - git clone https://github.com/discourse/docker_manager.git
- - git clone https://github.com/teozkr/discourse-plugin-discord-auth.git
-
- ## Any custom commands to run after building
- run:
- - exec: echo "Beginning of custom commands"
- ## If you want to set the 'From' email address for your first registration, uncomment and change:
- ## After getting the first signup email, re-comment the line. It only needs to run once.
- - exec: rails r "SiteSetting.notification_email='noreply@${config.networking.domain}'"
- - replace:
- filename: "/etc/nginx/conf.d/discourse.conf"
- from: /listen 80;/
- to: |
- listen 80;
- set_real_ip_from 0.0.0.0/0;
- - exec: echo "End of custom commands"
- '';
- };
- };
- };
-
- config = {
- virtualisation.docker.enable = true;
- #networking.firewall.enable = false;
- environment.systemPackages = [ pkgs.git ];
- systemd.services.discourse-setup = {
- wants = [ "docker.service" ];
- after = [ "network.target" "docker.service" ];
- wantedBy = [ "multi-user.target" ];
- path = [ pkgs.git pkgs.bash pkgs.nettools pkgs.which pkgs.gawk pkgs.docker ];
- script =
- ''
- if [[ ! -e /var/discourse ]]; then
- git clone --depth 1 https://github.com/discourse/discourse_docker.git /var/discourse
- fi
- cp ${pkgs.writeText "discourse-app.yml" config.services.discourse.config} \
- /var/discourse/containers/app.yml
- cd /var/discourse
- git pull --depth 1
- bash ./launcher rebuild app
- '';
- serviceConfig = {
- Type = "simple";
- RemainAfterExit = "yes";
- };
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, system, ...}:
-let inherit (builtins) toString toFile;
- inherit (lib) types;
- inherit (pkgs.lib) unlines unlinesAttrs unlinesValues unwords;
- inherit (config) networking;
- inherit (config.services) dovecot2 postfix x509 openldap;
- when = x: y: if x == null then "" else y;
- extSep = postfix.recipientDelimiter;
- dirSep = extSep;
- stateDir = "/var/lib/dovecot";
- # NOTE: nixpkgs' dovecot2.stateDir is currently not exported
- mailDir = "${stateDir}/mail";
- sieveDir = "${stateDir}/sieve";
- authDir = "${stateDir}/auth";
- authUser = dovecot2.mailUser; # TODO: php_roundcube
- authGroup = dovecot2.mailGroup; # TODO: php_roundcube
- escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
- || "0"<=c && c<="9"
- || c=="-"
- then c else "_");
- domainGroup = escapeGroup "${networking.domainBase}";
- etc_dovecot = [
- { target = "dovecot/${networking.domain}/dovecot-ldap.conf";
- source = pkgs.writeText "dovecot-ldap.conf" ''
- ${lib.optionalString dovecot2.debug ''
- debug_level = 1
- ''}
-
- # LDAP database
- uris = ldapi://
- base = ou=posix,${openldap.domainSuffix}
- scope = subtree
- deref = never
- blocking = no
- # NOTE: sufficient for small systems and uses less resources.
-
- # LDAP auth
- sasl_bind = yes
- sasl_mech = EXTERNAL
- #dn = cn=admin,${openldap.domainSuffix}
- #dnpass = useless with sasl_mech=EXTERNAL
- auth_bind = no
- #auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=${openldap.domainSuffix}
-
- # dovecot passdb query
- # DOC: http://wiki2.dovecot.org/PasswordDatabase/ExtraFields
- pass_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
- pass_attrs = userPassword=password,\
- uidNumber=userdb_uid,\
- gidNumber=userdb_gid,\
- mailGroupMember=userdb_mail_access_groups=${domainGroup},\
- quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes},\
- =user=%n@%d
- #homeDirectory=userdb_home
- # TODO: userdb_quota_rule=*:storage=
- default_pass_scheme = CRYPT
-
- # dovecot userdb query
- # DOC: http://wiki2.dovecot.org/UserDatabase/ExtraFields
- user_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
- #user_filter = (&(objectClass=inetOrgPerson)(uid=%n))
- #user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid
- #user_attrs = mailHomeDirectory=home,\
- # mailStorageDirectory=mail,\
- # mailUidNumber=uid,\
- # mailGidNumber=gid,\
- # mailQuota=quota_rule=*:bytes=%$
-
- # doveadm user query
- iterate_attrs = =user=%{ldap:uid}@${networking.domain}
- iterate_filter = (&(objectClass=posixAccount)(mailEnabled=TRUE))
- '';
- }
- ];
- dovecot-virtual = pkgs.writeTextFile {
- name = "dovecot-virtual";
- destination = "/pop3/INBOX/dovecot-virtual";
- text = ''
- all
- all+*
- all
- '';
- };
-
- sieve-rspamd-filter =
- pkgs.stdenv.mkDerivation {
- name = "sieve-rspamd-filter";
- nativeBuildInputs = [ pkgs.makeWrapper ];
- phases = [ "installPhase" ];
- installPhase = ''
- mkdir -p $out/bin
- cat > $out/bin/learn-spam.sh <<EOF
- #!/bin/sh
- exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd.sock learn_spam
- EOF
- cat > $out/bin/learn-ham.sh <<EOF
- #!/bin/sh
- exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd.sock learn_ham
- EOF
- chmod +x $out/bin/*.sh
- '';
- };
-in
-{
- imports = [
- dovecot/autoconfig.nix
- ];
- options = {
- services.dovecot2.sieves = {
- global = lib.mkOption {
- description = "global scripts.";
- type = types.attrsOf types.str;
- default = {};
- };
- before = lib.mkOption {
- description = "before scripts.";
- type = types.attrsOf types.str;
- default = {};
- };
- after = lib.mkOption {
- description = "after scripts.";
- type = types.attrsOf types.str;
- default = {};
- };
- };
- };
- config = {
- environment.etc = etc_dovecot;
- #services.postfix.mapFiles."transport-dovecot" =
- # toFile "transport-dovecot"
- # (unlines
- # (lib.mapAttrsToList
- # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
- # dovecot2.domains));
- systemd.services.dovecot2 = {
- after = [
- "postfix.service"
- "openldap.service"
- ];
- restartTriggers =
- map (f: f.source) etc_dovecot;
- environment = {
- #LD_LIBRARY_PATH = config.system.nssModules.path;
- };
- preStart = unlinesValues {
- installMailDir = ''
- # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
- install -D -d -m 0771 \
- -o ${dovecot2.mailUser} \
- -g ${dovecot2.mailGroup} \
- ${mailDir}
- '';
-
- installSieve = ''
- rm -rf "${sieveDir}"
- install -D -d -m 0755 -o root -g root \
- "${sieveDir}/bin"
- '' + unlinesAttrs (dir: sieves: ''
- install -D -d -m 0755 -o root -g root \
- ${sieveDir} ${sieveDir}/${dir}.d
- '' + unlinesAttrs (name: text: ''
- src=${pkgs.writeText "${name}.sieve" text}
- dst="${sieveDir}/${dir}.d/${name}.sieve"
- ln -fns "$src" "$dst"
- ${pkgs.dovecot_pigeonhole}/bin/sievec "$dst"
- '') sieves
- ) dovecot2.sieves;
-
- installDomains = ''
- # NOTE: make sure nslcd cache is in sync with the LDAP data
- systemctl restart nslcd
- install -D -d -m 1770 \
- -o ${dovecot2.mailUser} \
- -g ${domainGroup} \
- ${mailDir}/${networking.domain} \
- ${stateDir}/control/${networking.domain} \
- ${stateDir}/index/${networking.domain}
-
- # NOTE: do not set the sticky bit (+t)
- # on acl/<domain>/, to let dovecot
- # rename acl.db.lock (own by new user)
- # to acl.db (own by old user)
- install -D -d -m 0770 \
- -o ${dovecot2.mailUser} \
- -g ${domainGroup} \
- ${stateDir}/acl/${networking.domain}
-
- # NOTE: domainAliases point to the very same mailboxes as domain's.
- for domainAlias in ${unwords networking.domainAliases}
- do
- ln -fns ${networking.domain} ${mailDir}/$domainAlias
- ln -fns ${networking.domain} ${stateDir}/control/$domainAlias
- ln -fns ${networking.domain} ${stateDir}/index/$domainAlias
- ln -fns ${networking.domain} ${stateDir}/acl/$domainAlias
- done
- '';
- };
- };
- services.dovecot2 = {
- enable = true;
- debug = true;
- mailUser = "dovemail";
- mailGroup = "dovemail";
- modules = [
- #pkgs.dovecot_antispam
- pkgs.dovecot_pigeonhole
- ];
- sieves = {
- global = {
- list = ''
- require
- [ "date"
- , "fileinto"
- , "mailbox"
- , "variables"
- ];
-
- if currentdate :matches "year" "*" { set "year" "''${1}"; }
- if currentdate :matches "month" "*" { set "month" "''${1}"; }
-
- if exists "List-ID" {
- if header :matches "List-ID" "*<*.*.*.*>*" {
- set "list" "''${2}";
- set "domain" "''${4}";
- }
- elsif header :matches "List-ID" "*<*.*.*>*" {
- set "list" "''${2}";
- set "domain" "''${3}";
- }
- fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
- stop;
- }
- '';
- spam = ''
- require
- [ "imap4flags"
- ];
-
- if header :contains "X-Spam-Level" "***" {
- addflag "Junk";
- }
- '';
- /*
- require ["fileinto","mailbox"];
-
- if header :contains "X-Spam" "Yes" {
- fileinto :create "INBOX.Junk";
- stop;
- }
- */
- extension = ''
- require
- [ "envelope"
- , "fileinto"
- , "mailbox"
- , "subaddress"
- , "variables"
- ];
- if envelope :matches :detail "TO" "*" {
- set "extension" "''${1}";
- }
- if not string :is "''${extension}" "" {
- fileinto :create "Plus+''${extension}";
- stop;
- }
- '';
- report-spam = ''
- require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
-
- if environment :matches "imap.user" "*" {
- set "username" "''${1}";
- }
-
- pipe :copy "learn-spam.sh" [ "''${username}" ];
- '';
- report-ham = ''
- require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
-
- if environment :matches "imap.mailbox" "*" {
- set "mailbox" "''${1}";
- }
-
- if string "''${mailbox}" "Trash" {
- stop;
- }
-
- if environment :matches "imap.user" "*" {
- set "username" "''${1}";
- }
-
- pipe :copy "learn-ham.sh" [ "''${username}" ];
- '';
- };
- };
- configFile = toString (pkgs.writeText "dovecot.conf" ''
- passdb {
- driver = ldap
- args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
- default_fields = userdb_mail_access_groups=${domainGroup}
- override_fields =
- }
- userdb {
- driver = prefetch
- }
- userdb {
- # NOTE: this userdb is only used by lda.
- driver = ldap
- args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
- default_fields = mail_access_groups=${domainGroup}
- override_fields =
- skip = found
- }
- #passdb {
- # driver = passwd-file
- # args = scheme=crypt username_format=%n ${authDir}/%d/passwd
- #}
- #userdb {
- # # NOTE: this userdb is only used by lda.
- # driver = passwd-file
- # args = username_format=%n ${authDir}/%d/passwd
- # #default_fields = home=${mailDir}/%d/%n
- # skip = found
- #}
- mail_home = ${mailDir}/%d/%n
- # NOTE: if needed, may be overrided by userdb_mail
- mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${stateDir}/index/%d/%n:INDEXPVT=${stateDir}/index/%d/%n:CONTROL=${stateDir}/control/%d/%n
- # NOTE: if needed, may be overrided by userdb_mail
- # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
- # SEE: http://wiki2.dovecot.org/Quota/FS
- auth_mechanisms = plain login
- auth_ssl_require_client_cert = no
- # NOTE: postfix does not supply a client cert.
- auth_ssl_username_from_cert = yes
- auth_verbose = yes
- auth_username_format = %Lu
- # NOTE: lowercase the username, help with LDAP?
- ${lib.optionalString dovecot2.debug ''
- auth_debug = yes
- mail_debug = yes
- verbose_ssl = yes
- ''}
- default_internal_user = ${dovecot2.user}
- default_internal_group = ${dovecot2.group}
- disable_plaintext_auth = yes
- first_valid_uid = 10000
- # NOTE: sync with LDAP's data.
- lda_mailbox_autocreate = yes
- lda_mailbox_autosubscribe = yes
- listen = *
- log_timestamp = "%Y-%m-%d %H:%M:%S "
- #maildir_copy_with_hardlinks = yes
- namespace inbox {
- # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
- type = private
- inbox = yes
- location =
- list = yes
- prefix =
- separator = ${dirSep}
- }
- namespace {
- type = shared
- #list = children
- list = yes
- # NOTE: always listed in the LIST command.
- location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${stateDir}/index/%%d/%%n/Shared:INDEXPVT=${stateDir}/index/%d/%n/Shared/%%n:CONTROL=${stateDir}/control/%d/%n/Shared
- # NOTE: how to access the other users' mailboxes.
- # NOTE: %var expands to the logged in user's variable, while
- # %%var expands to the other users' variables.
- # NOTE: INDEX and CONTROL are shared, INDEXPVT is not.
- prefix = Partages+%%n+
- separator = ${dirSep}
- subscriptions = yes
- }
- mail_plugins = $mail_plugins acl quota virtual
- #mail_uid = ${dovecot2.mailUser}
- #mail_gid = ${dovecot2.mailGroup}
- # NOTE: each user has a dedicated (uid,gid) pair
- #mail_privileged_group = mail
- #mail_access_groups =
- plugin {
- acl = vfile:/etc/dovecot/acl/global.d
- acl_anyone = allow
- acl_shared_dict = file:${stateDir}/acl/%d/acl.db
- # NOTE: to let users LIST mailboxes shared by other users,
- # Dovecot needs a shared mailbox dictionary.
- # FIXME: %d not working with userdb ldap
- ##antispam_allow_append_to_spam = yes
- # # NOTE: pour offlineimap
- #antispam_backend = pipe
- ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm
- #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm
- #antispam_crm_binary = /usr/bin/crm
- #antispam_debug_target = syslog
- ##antispam_crm_env = HOME=%h;USER=%u
- #antispam_ham_keywords = NonJunk
- #antispam_pipe_program = /usr/bin/crm
- #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force
- #antispam_pipe_program_notspam_arg = --learnnonspam
- #antispam_pipe_program_spam_arg = --learnspam
- #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam
- #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam
- #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp
- #antispam_signature = X-CRM114-CacheID
- #antispam_signature_missing = move
- #antispam_spam = Junk
- #antispam_spam_keywords = Junk
- #antispam_trash = Trash
- #antispam_unsure = Unsure
- #antispam_verbose_debug = 0
- quota = maildir:User quota
- quota_rule = *:storage=256M
- quota_rule2 = Trash:storage=+64M
- quota_max_mail_size = 20M
- #quota_exceeded_message = </path/to/quota_exceeded_message.txt
- quota_warning = storage=95%% quota-warning 95 %u
- quota_warning2 = storage=80%% quota-warning 80 %u
- quota_warning3 = -storage=100%% quota-warning below %u
- # NOTE: user is no longer over quota
- recipient_delimiter = ${extSep}
- sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
- #sieve_default = file:${mailDir}/%u/default.sieve
- #sieve_default_name = default
- sieve_after = ${sieveDir}/after.d/
- sieve_before = ${sieveDir}/before.d/
- sieve_dir = ${mailDir}/%d/%n/sieve/
- #sieve_extensions = +spamtest +spamtestplus
- sieve_global_dir = ${sieveDir}/global.d/
- sieve_max_script_size = 1M
- sieve_quota_max_scripts = 0
- sieve_quota_max_storage = 10M
- sieve_spamtest_max_value = 10
- sieve_spamtest_status_header = X-Spam-Score
- sieve_spamtest_status_type = strlen
- sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
-
- sieve_plugins = sieve_imapsieve sieve_extprograms
- sieve_pipe_bin_dir = ${sieve-rspamd-filter}/bin
- sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
- # From elsewhere to Spam folder
- imapsieve_mailbox1_name = Spam
- imapsieve_mailbox1_causes = COPY
- imapsieve_mailbox1_before = file:${sieveDir}/global.d/report-spam.sieve
-
- # From Spam folder to elsewhere
- imapsieve_mailbox2_name = *
- imapsieve_mailbox2_from = Spam
- imapsieve_mailbox2_causes = COPY
- imapsieve_mailbox2_before = file:${sieveDir}/global.d/report-ham.sieve
- }
- # If you have Dovecot v2.2.8+ you may get a significant performance improvement with fetch-headers:
- imapc_features = $imapc_features fetch-headers
- # Read multiple mails in parallel, improves performance
- mail_prefetch_count = 20
- service quota-warning {
- executable = script ${
- pkgs.writeScript "quota-warning" ''
- #!/bin/sh -eu
- PERCENT=$1
- USER=$2
- cat << EOF | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -d $USER -o
- "plugin/quota=maildir:User quota:noenforcing"
- From: postmaster@${networking.domain}
- Subject: [WARNING] your mailbox is now $PERCENT% full.
-
- Please remove some mails to make room for new ones.
- EOF
- ''
- }
- # use some unprivileged user for executing the quota warnings
- user = ${dovecot2.user}
- unix_listener quota-warning {
- }
- }
- protocol imap {
- #mail_max_userip_connections = 10
- mail_plugins = $mail_plugins imap_acl imap_quota imap_sieve # antispam
- namespace inbox {
- inbox = yes
- location =
- list = yes
- mailbox Drafts {
- special_use = \Drafts
- }
- mailbox Junk {
- special_use = \Junk
- auto = subscribe
- #autoexpunge = 30d
- }
- mailbox Sent {
- special_use = \Sent
- auto = subscribe
- }
- mailbox "Sent Messages" {
- special_use = \Sent
- auto = subscribe
- }
- mailbox Trash {
- special_use = \Trash
- auto = subscribe
- #autoexpunge = 30d
- }
- prefix =
- separator = ${dirSep}
- }
- }
- protocol lda {
- auth_socket_path = /var/run/dovecot/auth-userdb
- hostname = ${networking.domain}
- info_log_path =
- log_path =
- mail_plugins = $mail_plugins sieve
- namespace inbox {
- inbox = yes
- location =
- list = yes
- prefix =
- separator = ${dirSep}
- }
- postmaster_address = postmaster${extSep}dovecot${extSep}lda@${networking.domain}
- syslog_facility = mail
- }
- protocol lmtp {
- #info_log_path = /tmp/dovecot-lmtp.log
- mail_plugins = $mail_plugins sieve
- namespace inbox {
- inbox = yes
- location =
- list = yes
- prefix =
- separator = ${dirSep}
- }
- postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${networking.domain}
- }
- protocol pop3 {
- #mail_max_userip_connections = 10
- namespace all {
- # NOTE: used by ${dovecot-virtual}/pop3/INBOX/dovecot-virtual
- hidden = yes
- list = no
- location =
- prefix = all+
- separator = ${dirSep}
- }
- # Virtual namespace for the virtual INBOX.
- # Use a global directory for dovecot-virtual files.
- namespace inbox {
- inbox = yes
- hidden = yes
- list = no
- location = virtual:${dovecot-virtual}/pop3:INDEX=${stateDir}/index/%d/%n/POP3:LAYOUT=fs
- prefix = pop3+
- separator = ${dirSep}
- }
- pop3_client_workarounds =
- pop3_fast_size_lookups = yes
- pop3_lock_session = yes
- pop3_no_flag_updates = yes
- # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
- pop3_uidl_format = %g
- }
- protocol sieve {
- #mail_max_userip_connections = 10
- #managesieve_implementation_string = Dovecot Pigeonhole
- managesieve_max_compile_errors = 5
- #managesieve_max_line_length = 65536
- #managesieve_notify_capability = mailto
- #managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave
- }
- protocols = imap lmtp pop3 sieve
- service lmtp {
- #executable = lmtp -L
- process_min_avail = 2
- unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
- user = ${postfix.user}
- group = ${postfix.group}
- mode = 0600
- }
- #user = mail
- }
- service auth {
- user = root
- # FIXME: may be user=dovecot-auth with LDAP?
- unix_listener auth-userdb {
- user = ${dovecot2.user}
- group = ${dovecot2.group}
- mode = 0660
- }
- unix_listener /var/lib/postfix/queue/private/auth {
- user = ${postfix.user}
- group = ${postfix.group}
- mode = 0660
- }
- }
- service imap {
- # Most of the memory goes to mmap()ing files.
- # You may need to increase this limit if you have huge mailboxes.
- #vsz_limit =
- process_limit = 1024
- }
- service imap-login {
- #inet_listener imap {
- # address = 127.0.0.1
- # port = 143
- # ssl = no
- #}
- inet_listener imaps {
- port = 993
- ssl = yes
- }
- }
- service pop3 {
- process_limit = 1024
- }
- service pop3-login {
- inet_listener pop3s {
- port = 995
- ssl = yes
- }
- }
- ssl = required
- ssl_dh = <${x509.dir}/dh.pem
- ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL
- ssl_cert = <${x509.cert}
- ssl_key = <${x509.key}
- #ssl_ca = <''${caPath}
- #ssl_verify_client_cert = yes
- '');
- #${lib.concatMapStringsSep "\n"
- # (dom: ''
- # local_name mail.${dom} {
- # #ssl_ca = <''${caPath}
- # ssl_cert = <${x509.cert dom}
- # ssl_key = <${x509.key dom}
- # }
- # '')
- # dovecot2.domains
- #}
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins) attrNames;
- inherit (config.services) dovecot2;
-in
-{
-config = {
- services.nginx = {
- virtualHosts."autoconfig" =
- let servers = lib.concatMapStringsSep " "
- (dom: "autoconfig.${dom}")
- (attrNames dovecot2.domains);
- in
- {
- serverName = "autoconfig.${config.networking.domain}";
- serverAliases =
- map (domainAlias: "autoconfig." + domainAlias)
- config.networking.domainAliases;
- #addSSL = true;
- extraConfig = ''
- access_log off;
- log_not_found off;
- '';
- root = pkgs.writeTextFile {
- name = "autoconfig";
- destination = "/mail/config-v1.1.xml";
- text = ''
- <?xml version="1.0"?>
- <clientConfig version="1.1">
- <emailProvider id="%EMAILDOMAIN%">
- <!-- <displayName></displayName> -->
- <!-- <displayShortName></displayShortName> -->
- <domain>%EMAILDOMAIN%</domain>
- <incomingServer type="imap">
- <hostname>mail.%EMAILDOMAIN%</hostname>
- <port>993</port>
- <socketType>SSL</socketType>
- <username>%EMAILADDRESS%</username>
- <authentication>password-cleartext</authentication>
- </incomingServer>
- <incomingServer type="pop3">
- <hostname>mail.%EMAILDOMAIN%</hostname>
- <port>995</port>
- <socketType>SSL</socketType>
- <username>%EMAILADDRESS%</username>
- <authentication>password-cleartext</authentication>
- <pop3>
- <leaveMessagesOnServer>false</leaveMessagesOnServer>
- <downloadOnBiff>true</downloadOnBiff>
- </pop3>
- </incomingServer>
- <outgoingServer type="smtp">
- <hostname>mail.%EMAILDOMAIN%</hostname>
- <port>465</port>
- <socketType>SSL</socketType> <!-- see above -->
- <username>%EMAILADDRESS%</username> <!-- if smtp-auth -->
- <authentication>password-cleartext</authentication>
- <!-- <restriction>client-IP-address</restriction> -->
- <addThisServer>true</addThisServer>
- <useGlobalPreferredServer>false</useGlobalPreferredServer>
- </outgoingServer>
- </emailProvider>
- <!-- <clientConfigUpdate url="https://www.example.com/config/mozilla.xml" /> -->
- </clientConfig>
- '';
- };
- };
- };
-};
-}
+++ /dev/null
-{pkgs, lib, config, system, ...}:
-let inherit (builtins.extraBuiltins) pass;
- inherit (lib) types;
- inherit (config) networking;
- inherit (config.services) gitolite;
- inherit (config.users) users groups;
- gitolite-admin = "julm";
-in
-{
- config = {
- environment.systemPackages = [ pkgs.gitolite ];
- # NOTE: make confortable to call gitolite from a shell
- # (but mind the sudo -u git).
-
- services = {
- gitolite = {
- enable = true;
- user = "git";
- group = users."git-daemon".name;
- adminPubkey = pass "${networking.domainBase}/ssh/pub/${gitolite-admin}";
- extraGitoliteRc = ''
- $RC{UMASK} = 0027; # NOTE: no quote around in Perl, so it's octal
- $RC{LOG_DEST} = 'repo-log,syslog';
- $RC{LOG_FACILITY} = 'local0';
- #$RC{GIT_CONFIG_KEYS} = 'hooks.* gitweb.*';
- $RC{GIT_CONFIG_KEYS} = '.*';
- #$RC{LOCAL_CODE} = "$rc{GL_ADMIN_BASE}/local"
- # if -d "$rc{GL_ADMIN_BASE}/local";
- $RC{LOCAL_CODE} = "$ENV{HOME}/local";
- push(@{$RC{ENABLE}}, ( 'Alias'
- , 'cgit'
- # NOTE: without this "cgit" option,
- # the repositories' "description" files are not modified
- , 'D'
- , 'Shell ${gitolite-admin}'
- , 'create'
- , 'expand-deny-messages'
- , 'fork'
- , 'keysubdirs-as-groups'
- , 'readme'
- , (-d "$ENV{HOME}/local" ? 'repo-specific-hooks' : ())
- , 'ssh-authkeys-split'
- ));
- '';
- };
- };
- systemd.services.gitolite-init = {
- preStart = ''
- chmod g+x "${gitolite.dataDir}"
- # NOTE: allow git-daemon to enter ~git
- install -D -d -o ${gitolite.user} -g ${gitolite.group} -m 750 \
- ${gitolite.dataDir}/local \
- ${gitolite.dataDir}/local/hooks \
- ${gitolite.dataDir}/local/hooks/common \
- ${gitolite.dataDir}/local/hooks/repo-specific
- '';
- };
- systemd.services.git-daemon = {
- # NOTE: not using nixpkgs' gitDaemon, to avoid running it as root.
- after = [ "network.target" ];
- wantedBy = [ "multi-user.target" ];
- serviceConfig = {
- User = users."git-daemon".name;
- Group = groups."git-daemon".name;
- Restart = "always";
- RestartSec = 5;
- };
- script = "${pkgs.git}/bin/git daemon --verbose --reuseaddr"
- + " --base-path=${gitolite.dataDir}/repositories"
- #+ (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ")
- #+ "--port=${toString cfg.port} "
- ;
- };
- users.users = lib.singleton
- { name = "git-daemon";
- uid = config.ids.uids.git;
- description = "Git daemon user";
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, system, ...}:
-let inherit (lib) types;
- inherit (config.services) nginx x509;
- domainDir = dom: lib.concatStringsSep "/" (lib.reverseList (lib.splitString "." dom));
-in
-{
-imports = [
- nginx/gitweb.nix
-];
-options.services.nginx = {
- webDir = lib.mkOption {
- type = types.str;
- default = "/var/lib/nginx";
- };
- logDir = lib.mkOption {
- type = types.str;
- default = "/var/log/nginx";
- };
-};
-config = {
- security.dhparams = {
- enable = true;
- params = {
- nginx = 1024;
- };
- };
- systemd.services.nginx = {
- preStart = lib.mkBefore ''
- install -D -d -o ${nginx.user} -g ${nginx.group} -m 0700 \
- ${nginx.webDir} \
- ${nginx.logDir}
- '';
- };
- services.nginx = {
- enable = true;
- stateDir = "/dev/shm/nginx";
- eventsConfig = ''
- multi_accept on;
- use epoll;
- worker_connections 1024;
- '';
- clientMaxBodySize = "20m";
- recommendedProxySettings = true;
- recommendedTlsSettings = true;
- serverTokens = false;
- sslCiphers = "HIGH:!ADH:!MD5:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL";
- #sslCiphers = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL;";
- #sslProtocols = "TLSv1.2";
- commonHttpConfig = ''
- log_format main '$remote_addr - $remote_user [$time_local] "$request" '
- '$status $body_bytes_sent "$http_referer" '
- '"$http_user_agent" "$http_x_forwarded_for"';
- #charset UTF-8;
- '' +
- lib.concatStringsSep "\n" (lib.attrValues {
- default = ''
- default_type application/octet-stream;
- root ${nginx.webDir};
- '';
- security = ''
- error_page 403 = 404;
- #ssl_certificate ${x509.cert};
- #ssl_certificate_key ${x509.key};
- '';
- log = ''
- access_log ${nginx.logDir}/access.log main buffer=32k;
- error_log ${nginx.logDir}/error.log warn;
- open_log_file_cache max=1000 inactive=20s min_uses=2 valid=1m;
- '';
- proxy = ''
- proxy_cache_use_stale updating;
- proxy_temp_path ${nginx.stateDir}/proxy_temp 1 2;
- '';
- fastcgi = ''
- # DOC: http://wiki.nginx.org/HttpFastcgiModule
- fastcgi_buffer_size 128k;
- fastcgi_buffers 256 4k;
- fastcgi_busy_buffers_size 256k;
- fastcgi_cache_key "$request_method $scheme://$http_host$request_uri";
- fastcgi_cache_path ${nginx.stateDir}/fastcgi_cache
- inactive=10m
- keys_zone=microcache:2M
- levels=1:2
- loader_files=100000
- loader_sleep=1
- loader_threshold=2592000000
- max_size=64M;
- fastcgi_connect_timeout 60;
- fastcgi_ignore_client_abort off;
- fastcgi_intercept_errors on;
- fastcgi_max_temp_file_size 2M;
- #fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
- fastcgi_param SCRIPT_FILENAME $request_filename;
- fastcgi_temp_path ${nginx.stateDir}/fastcgi_temp 1 2;
- '';
- connexion = ''
- sendfile on;
- send_timeout 60;
- # ^ If the client stops reading data,
- # free up the stale client connection after this much time.
- tcp_nopush on;
- # ^ Causes nginx to attempt to send its HTTP response head
- # in one packet, instead of using partial frames.
- # This is useful for prepending headers before calling sendfile,
- # or for throughput optimization.
- tcp_nodelay on;
- # ^ Don't buffer data-sends (disable Nagle algorithm).
- # Good for sending frequent small bursts of data in real time.
- keepalive_timeout 20;
- reset_timedout_connection on;
- server_names_hash_bucket_size 128;
- types_hash_max_size 2048;
- '';
- map = ''
- # User agents that are to be blocked.
- #map $http_user_agent $bad_bot {
- # default 0;
- # libwww-perl 1;
- # ~(?i)(httrack|htmlparser|libwww) 1;
- #}
- # Referrers that are to be blocked.
- #map $http_referer $bad_referer {
- # default 0;
- # ~(?i)(babes|casino|click|diamond|forsale|girl|jewelry|love|nudit|organic|poker|porn|poweroversoftware|replica|sex|teen|webcam|zippo) 1;
- #}
- #geo $not_local {
- # default 1;
- # 127.0.0.1 0;
- #}
- '';
- gzip = ''
- gzip on;
- gzip_buffers 16 8k;
- gzip_comp_level 6;
- gzip_disable "MSIE [1-6]\.";
- gzip_http_version 1.1;
- gzip_min_length 1024;
- gzip_proxied any;
- gzip_static on;
- gzip_vary on;
- gzip_types application/atom+xml
- application/javascript
- application/json
- application/rss+xml
- application/vnd.ms-fontobject
- application/x-font-ttf
- application/x-javascript
- application/xml
- application/xml+rss
- font/opentype
- font/truetype
- image/svg+xml
- text/css
- text/javascript
- text/plain
- text/x-component
- text/xml;
- '';
- cache = ''
- client_body_buffer_size 4K;
- # ^ getconf PAGESIZE
- # 4096
- client_body_temp_path ${nginx.stateDir}/client_body_temp 1 2;
- client_body_timeout 60;
- client_header_buffer_size 1k;
- client_header_timeout 60;
- large_client_header_buffers 4 8k;
-
- open_file_cache max=200000 inactive=20s;
- open_file_cache_errors on;
- open_file_cache_min_uses 2;
- open_file_cache_valid 30s;
- '';
- });
- appendConfig = ''
- worker_processes 2;
- '';
- };
-};
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (config) networking;
- inherit (config.services) gitweb gitolite nginx;
- package = pkgs.gitweb.override (lib.optionalAttrs gitweb.gitwebTheme {
- gitwebTheme = true;
- });
- RuntimeDirectory = "gitweb";
- gitwebSocket = "/run/${RuntimeDirectory}/gitweb.sock";
- static-custom = pkgs.writeTextFile {
- name = "static-custom";
- destination = "/static-custom/style.css";
- text = ''
- .project_list {
- width:100%;
- }
- '';
- };
-in
-{
- config = {
- services.nginx = {
- virtualHosts."git" = {
- serverName = "git.${networking.domain}";
- serverAliases =
- map (domainAlias: "git." + domainAlias)
- config.networking.domainAliases;
- #listen = [
- # { addr = "0.0.0.0"; port = 80; ssl = false; }
- #];
- #default = true;
- locations = {
- "/" = {
- extraConfig = ''
- include ${pkgs.nginx}/conf/fastcgi_params;
- fastcgi_param PATH_INFO $fastcgi_script_name;
- # NOTE: used by gitweb's pathinfo feature.
- fastcgi_param GITWEB_CONFIG ${gitweb.gitwebConfigFile};
- fastcgi_pass unix:${gitwebSocket};
- '';
- };
- "/static/" = {
- alias = "${pkgs.gitweb}/static/";
- };
- "/static-custom/" = {
- alias = "${static-custom}/static-custom/";
- };
- };
- };
- };
- systemd.services.gitweb = {
- description = "GitWeb FastCGI service";
- script = "${pkgs.gitweb}/gitweb.cgi --fastcgi --nproc=1";
- environment = {
- FCGI_SOCKET_PATH = gitwebSocket;
- FCGI_SOCKET_PERM = "432"; # decimal of 660 in octal, since current CGI::Fast doesn't use perl's oct()
- };
- serviceConfig = {
- User = gitolite.user;
- Group = nginx.group;
- RuntimeDirectory = [ RuntimeDirectory ];
- Restart = "always";
- RestartSec = 10;
- };
- wantedBy = [ "multi-user.target" ];
- };
- services.gitweb = {
- gitwebTheme = false;
- projectroot = "${gitolite.dataDir}/repositories";
- extraConfig = ''
- use utf8;
- my $s = $cgi->https() ? "s" : "";
- @extra_breadcrumbs = (["${networking.domainBase}" => "http''${s}://${networking.domain}"]);
- $home_link_str = "git";
- $projects_list = "${gitolite.dataDir}/projects.list";
- $projects_list_group_categories = 1;
- $default_projects_order = "age";
- $omit_owner = 1;
- $export_ok = "git-daemon-export-ok";
- $prevent_xss = 0;
- @git_base_url_list =
- ( "git://git.${networking.domain}"
- , "git\@git.${networking.domain}:"
- );
- $feature{'pathinfo'}{'default'} = [1];
- # NOTE: more readable URL.
- @stylesheets = ( "/static/gitweb.css"
- , "/static-custom/style.css"
- );
- $logo = "/static/git-logo.png";
- $favicon = "/static/git-favicon.png";
- $javascript = "/static/gitweb.js";
- $feature{'highlight'}{'default'} = [1];
- # FIX: gitweb bug: FCGI is not Unicode aware.
- if ($first_request) {
- my $enc = Encode::find_encoding('UTF-8');
- my $org = \&FCGI::Stream::PRINT;
- no warnings 'redefine';
- *FCGI::Stream::PRINT = sub {
- my @OUTPUT = @_;
- for (my $i = 1; $i < @_; $i++) {
- $OUTPUT[$i] = $enc->encode($_[$i], Encode::FB_CROAK|Encode::LEAVE_SRC);
- }
- @_ = @OUTPUT;
- goto $org;
- };
- };
- '';
- };
-
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (config.services) nsd;
-in
-{
- imports = [
- nsd/sourcephile.nix
- ];
- config = {
- services.nsd = {
- enable = true;
- ipv4 = true;
- ipv6 = true;
- verbosity = 5;
- # SEE: http://www.nlnetlabs.nl/blog/2012/10/11/nsd-ratelimit/
- ratelimit.size = 10000;
- extraConfig = ''
- '';
- interfaces = lib.unique [
- "127.0.0.1"
- "::1"
- config.networking.zones.lan.ipv4
- config.networking.zones.net.ipv4
- ];
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins) toString toPath readFile;
- inherit (builtins.extraBuiltins) pass;
- inherit (pkgs.lib) unlinesAttrs;
- inherit (config) networking;
- inherit (config.services) nsd dkim;
- serial = zone: toString (builtins.extraBuiltins.git ./. [ "log" "-1" "--format=%ct" "--" (zone + ".nix") ]);
- /*
- serial = file: lib.removeSuffix "\n" (readFile
- (pkgs.runCommand "zone-serial"
- { buildInputs = [ pkgs.git ];
- buildDepends = [ (toPath ./. + file) ];
- preferLocalBuild = true;
- allowSubstitutes = false;
- } ''
- cd ${toPath ./.}
- ${pkgs.git}/bin/git log -1 --format="%ct" -- ${file} >$out
- ''));
- */
- ipv4 = networking.zones.net.ipv4;
- sourcephileZone = domain: ''
- ; A (DNS -> IPv4)
- @ A ${ipv4}
- autoconfig A ${ipv4}
- git A ${ipv4}
- imap A ${ipv4}
- mail A ${ipv4}
- ns A ${ipv4}
- pop A ${ipv4}
- redmine A ${ipv4}
- smtp A ${ipv4}
- submission A ${ipv4}
- www A ${ipv4}
-
- ; SPF (Sender Policy Framework)
- @ 3600 IN SPF "v=spf1 mx ip4:${ipv4} -all"
- @ 3600 IN TXT "v=spf1 mx ip4:${ipv4} -all"
-
- ; MX (Mail eXchange)
- @ 180 MX 5 mail
-
- ; SRV (SeRVice)
- _git._tcp.git 18000 IN SRV 0 0 9418 git
-
- ; TXT (TeXT)
- ''
- + unlinesAttrs
- (selector: sel: sel.dns)
- dkim.domains."${domain}".selectors;
-in
-{
- config = {
- services.dkim.domains =
- let domain = rec {
- selector = "2020010101";
- selectors = {
- "2020010101" = {
- key = pass "${networking.domainBase}/dkim/2020010101/key" + "\n";
- dns = ''
- 2020010101._domainkey IN TXT ( "v=DKIM1; k=rsa; "
- "p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7EKzverbG+5JF+yFjH3MrxLyauiHyLqBbV/8LEMunoKXF8sqhBpQtAQXruLqsyUkxR/4CAyPMyzmcdrU43boMj9yFqLrg/kEz2RIvai9jXBqRoWRW1y7F0LbZmdtOTncuDSP8Zzo02XUzsOC4f/C3tEQHS5rchzfhU5FY1CeO6eBMV79qKBOvGMKahQTrrtU6olAAJxOhn6wRuwSf"
- "+m3on1OqiuXYYIgNHKdRhJ8gDwIm/3LEpYMD0gTgJiyclCLoLGHGtKZy1Wf9xV9/7V6fHE4JW5SDivwslVTL+KPXOlIpo5NDHpMxPYOcIg2K4Rj/j7jhavo+fG43q1LhwaPkEMQMbplgnjeMY8300odRiklTkMMpH0m35ZNeHQJSRpEtV8y5xUNxVaGzfqX5iStwV/mQ1KnZSe8ORTNq+eTTFnDk6zdUXjagcf0wO6QsSTeAz/G8CqOBbwmrU+q"
- "F8WbGAeRnhz51mH6fTTfsQ1nwjAiF4ou+eQGTkTMN23KkCKpuozJnxqx4DCEr6J1bL83fhXw7CgcfgKgTOk/HFJpeiGhqodw18r4DWBA6G57z9utm7Mr/9SoVnMq6iK9iEcbCllLR8Sz4viatLSRzhodbk7hfvXS3jmCFjILAjFmA7aMTemDMBDQhpAGF9F8sjFUbEJIZjKrWWtSTdO8DilDqN8CAwEAAQ=="
- ) ;
- '';
- };
- };
- }; in
- {
- "${networking.domainBase}.fr" = domain;
- };
- services.nsd = {
- zones = {
- # NOTE: cannot use networking.domain as attr key: infinite recursion
- "sourcephile.fr" = {
- data = ''
- $ORIGIN ${networking.domainBase}.fr.
- $TTL 86400
-
- ; SOA (Start Of Authority)
- @ SOA ns admin (
- ${serial networking.domainBase} ; Serial number
- 1d ; Refresh
- 15m ; Retry
- 2592000 ; Expire
- 1d ; TTL (Time To Live) minimum
- )
-
- ; NS (Name Server)
- @ NS ns
- ;@ NS ns6.gandi.net.
- ''
- + sourcephileZone "${networking.domainBase}.fr";
- };
- };
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins) baseNameOf readFile;
- inherit (lib) types;
- inherit (pkgs.lib) unlinesAttrs;
- inherit (config.services) openldap;
- inherit (config.users) ldap;
- copyFile = file: pkgs.writeText (baseNameOf file) (readFile file);
- configLDIF = pkgs.writeText "cn=config.ldif" (''
- dn: cn=config
- objectClass: olcGlobal
- #olcPidFile: /run/slapd/slapd.pid
- # List of arguments that were passed to the server
- #olcArgsFile: /run/slapd/slapd.args
- # Read slapd-config(5) for possible values
- olcLogLevel: none
- # The tool-threads parameter sets the actual amount of CPU's
- # that is used for indexing.
- olcToolThreads: 1
-
- dn: olcDatabase={-1}frontend,cn=config
- objectClass: olcDatabaseConfig
- objectClass: olcFrontendConfig
- # The maximum number of entries that is returned for a search operation
- olcSizeLimit: 500
- # Allow unlimited access to local connection from the local root user
- olcAccess: to *
- by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
- by * break
- # Allow unauthenticated read access for schema and base DN autodiscovery
- olcAccess: to dn.exact=""
- by * read
- olcAccess: to dn.base="cn=Subschema"
- by * read
-
- dn: olcDatabase=config,cn=config
- objectClass: olcDatabaseConfig
- olcRootDN: cn=admin,cn=config
- # Access to cn=config, system root can be manager
- # with SASL mechanism (-Y EXTERNAL) over unix socket (-H ldapi://)
- olcAccess: to *
- by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
- by * break
-
- dn: cn=schema,cn=config
- objectClass: olcSchemaConfig
-
- include: file://${pkgs.openldap}/etc/schema/core.ldif
- include: file://${pkgs.openldap}/etc/schema/cosine.ldif
- include: file://${pkgs.openldap}/etc/schema/nis.ldif
- include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
- include: file://${copyFile openldap/schema/postfix-book.ldif}
-
- dn: cn=module{0},cn=config
- objectClass: olcModuleList
- # Where the dynamically loaded modules are stored
- #olcModulePath: /usr/lib/ldap
- olcModuleLoad: back_mdb
-
- '' + unlinesAttrs (olcSuffix: {conf, olcDbDirectory, ...}:
- "include: file://" + pkgs.writeText "config.ldif" (conf + ''
- olcSuffix: ${olcSuffix}
- olcDbDirectory: ${olcDbDirectory}
- '')
- ) openldap.databases);
-in
-{
- imports = [
- openldap/sourcephile.nix
- ];
- options = {
- services.openldap.domainSuffix = lib.mkOption {
- type = types.str;
- default = "dc=${lib.concatStringsSep ",dc=" (lib.splitString "." config.networking.domain)}";
- description = ''
- LDAP suffix for config.networking.domain.
- '';
- };
- services.openldap.databases = lib.mkOption {
- default = {};
- type = types.attrsOf (types.submodule ({name, options, config, ...}: {
- options = {
- conf = lib.mkOption {
- type = types.lines;
- description = "The database's config in LDIF.";
- };
- data = lib.mkOption {
- type = types.lines;
- description = "The database's data in LDIF.";
- };
- olcDbDirectory = lib.mkOption {
- type = types.str;
- description = "The directory where the database is stored.";
- default = "${openldap.dataDir}/${name}";
- };
- resetData = lib.mkOption {
- type = types.bool;
- description = "Whether to reset the data at each start of the slapd service.";
- default = false;
- };
- };
- }));
- };
- };
- config = {
- users.ldap = {
- enable = true;
- server = "ldapi:///";
- base = "ou=posix,${openldap.domainSuffix}";
- bind = {
- #distinguishedName = "cn=admin,${openldap.domainSuffix}";
- };
- daemon = {
- enable = true;
- extraConfig = ''
- sasl_mech EXTERNAL
- # NOTE: nslcd cannot use SASL to bind to rootpwmoddn
- # which is the DN used by nslcd when passwd is run by root
- # to change the userPassword of an LDAP user.
- # SEE: https://www.reddit.com/r/linuxadmin/comments/53sxpl/how_do_i_configure_nslcd_to_use_a_sasl_external/d7w9awd/
- # Thus, use: ldappasswd -H ldapi:// -Y EXTERNAL uid=$SomeUID,ou=accounts,ou=posix,dc=sourcephile,dc=fr
- '';
- };
- };
- services.openldap = {
- enable = true;
- dataDir = "/var/db/ldap";
- configDir = "/var/db/slapd";
- urlList = [ "ldapi:///" ]; # UNIX socket
- };
- systemd.services.openldap = {
- preStart = ''
- set -e
- # NOTE: slapd's config is always re-initialized.
- rm -rf "${openldap.configDir}"/cn=config \
- "${openldap.configDir}"/cn=config.ldif
- install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
- # NOTE: olcDbDirectory must be created before adding the config.
- '' +
- unlinesAttrs (olcSuffix: {data, olcDbDirectory, resetData, ...}:
- lib.optionalString resetData ''
- rm -rf "${olcDbDirectory}"
- '' + ''
- install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${olcDbDirectory}"
- '') openldap.databases
- + ''
- # NOTE: slapd is supposed to have been stopped by systemd
- # before entering this preStart,
- # hence slap* commands can safely be used.
- #
- # NOTE: slapadd(8):
- # To populate the config database slapd-config(5),
- # use -n 0 as it is always the first database.
- # It must physically exist on the filesystem prior to this, however.
- umask 0077
- ${pkgs.openldap}/bin/slapadd -n 0 \
- -F "${openldap.configDir}" \
- -l ${configLDIF}
- chown -R "${openldap.user}:${openldap.group}" "${openldap.configDir}"
- '' +
- unlinesAttrs (olcSuffix: {data, olcDbDirectory, resetData, ...}:
- lib.optionalString resetData ''
- ${pkgs.openldap}/bin/slapadd \
- -F "${openldap.configDir}" \
- -l ${pkgs.writeText "data.ldif" data}
- '' + ''
- test ! -e "${olcDbDirectory}" ||
- chown -R "${openldap.user}:${openldap.group}" "${olcDbDirectory}"
- '') openldap.databases;
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (config) networking;
- inherit (config.services) openldap;
- inherit (config.users) users groups;
- inherit (pkgs.lib) unlines;
- domainSuffix = openldap.domainSuffix;
- posixAccount =
- { uid
- , uidNumber ? null
- , gidNumber ? uidNumber
- , cn ? ""
- , sn ? ""
- , userPassword ? "{SSHA}JtC8S4nzm+eX9cVgbyL6gquPWDZD4xXY"
- # NOTE: doveadm pw -s SSHA -u $user -p $pass
- , mailAlias ? []
- , loginShell ? "/run/current-system/sw/bin/bash"
- , mailEnabled ? true
- , mailForwardingAddress ? []
- , domain ? networking.domain
- }: "\n" + lib.concatStringsSep "\n\n" [
- (unlines ([ ''
- dn: uid=${uid},ou=accounts,ou=posix,${domainSuffix}
- objectClass: person
- objectClass: posixAccount
- objectClass: shadowAccount
- objectClass: PostfixBookMailAccount
- objectClass: PostfixBookMailForward
- cn: ${cn}
- sn: ${sn}
- mail: ${uid}${lib.optionalString (networking.domain != "") "@${networking.domain}"}
- mailEnabled: ${if mailEnabled then "TRUE" else "FALSE"}
- #mailGroupMember: ${networking.domainBase}
- homeDirectory: /home/${uid}
- uidNumber: ${toString uidNumber}
- gidNumber: ${toString gidNumber}
- loginShell: ${loginShell}'' ]
- ++ lib.optional (userPassword != "") "userPassword: ${userPassword}"
- ++ map (forward: "mailForwardingAddress: ${forward}") mailForwardingAddress
- ++ map (alias: "mailAlias: ${alias}@${networking.domain}") mailAlias
- ++ lib.optional (mailAlias == []) "mailAlias:"
- # NOTE: required by PostfixBookMailForward
- ))
- ''
- dn: cn=${uid},ou=groups,ou=posix,${domainSuffix}
- objectClass: top
- objectClass: posixGroup
- gidNumber: ${toString gidNumber}
- memberUid: ${uid}
- ''
- ];
-in
-{
- config = lib.mkIf config.users.ldap.enable {
- services.openldap = {
- databases = {
- "${domainSuffix}" = {
- resetData = true;
- conf = ''
- # sudo ldapsearch -LLL -H ldapi:// -D cn=admin,cn=config -Y EXTERNAL -b 'olcDatabase={1}mdb,cn=config' -s sub
- dn: olcBackend={1}mdb,cn=config
- objectClass: olcBackendConfig
-
- dn: olcDatabase={1}mdb,cn=config
- objectClass: olcDatabaseConfig
- objectClass: olcMdbConfig
- # NOTE: checkpoint the database periodically in case of system failure
- # and to speed slapd shutdown.
- olcDbCheckpoint: 512 30
- # Database max size is 1G
- olcDbMaxSize: 1073741824
- olcLastMod: TRUE
- # NOTE: database superuser. Needed for syncrepl.
- olcRootDN: cn=admin,${domainSuffix}
- # NOTE: superuser password, generated with slappasswd -h "{SSHA}" -s "$password"
- #olcRootPW: {SSHA}COkATGNe7rs/g8vWcYP5rqt4u5sWdMgP
- #
- olcDbIndex: objectClass eq
- olcDbIndex: cn,uid eq
- olcDbIndex: uidNumber,gidNumber eq
- olcDbIndex: member,memberUid eq
- olcDbIndex: mail eq
- olcDbIndex: mailAlias eq
- olcDbIndex: mailEnabled eq
- #
- olcAccess: to attrs=userPassword
- by self write
- by anonymous auth
- by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
- by * none
- olcAccess: to attrs=shadowLastChange
- by self write
- by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
- by * none
- olcAccess: to dn.sub="ou=posix,${domainSuffix}"
- by self read
- by dn="gidNumber=${toString groups.nslcd.gid}+uidNumber=${toString users.nslcd.uid},cn=peercred,cn=external,cn=auth" read
- by dn="gidNumber=${toString groups.postfix.gid}+uidNumber=${toString users.postfix.uid},cn=peercred,cn=external,cn=auth" read
- by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
- # NOTE: dovecot/auth runs as root, hence the gidNumber=0+uidNumber=0
- olcAccess: to *
- by self read
- by * none
- '';
- data = ''
- dn: ${domainSuffix}
- objectClass: top
- objectClass: dcObject
- objectClass: organization
- o: ${networking.domainBase}
-
- dn: cn=admin,${domainSuffix}
- objectClass: simpleSecurityObject
- objectClass: organizationalRole
- description: ${networking.domainBase} LDAP administrator
- roleOccupant: ${domainSuffix}
- userPassword:
- #userPassword: {SSHA}NONVwwKnKsCBmFxkMqTCFekdu3SJQHc9
-
- dn: ou=posix,${domainSuffix}
- objectClass: top
- objectClass: organizationalUnit
-
- dn: ou=accounts,ou=posix,${domainSuffix}
- objectClass: top
- objectClass: organizationalUnit
-
- dn: ou=groups,ou=posix,${domainSuffix}
- objectClass: top
- objectClass: organizationalUnit
-
- dn: cn=${networking.domainBase},ou=groups,ou=posix,${domainSuffix}
- objectClass: top
- objectClass: posixGroup
- gidNumber: 20000
- memberUid: ju
- memberUid: sevy
-
- ''
- + lib.concatMapStrings posixAccount [
- { uid="ju"; uidNumber=10000; cn="Julien M."; sn="julm"; mailAlias = ["juju"]; }
- { uid="sevy"; uidNumber=10001; cn="Séverine P."; sn="sévy"; mailAlias = ["severine.popek" "ouais-ouais"]; }
- { uid="nomail"; uidNumber=10002; mailAlias = ["noalias"]; mailEnabled = false; }
- { uid="post"; domain="friot"; mailForwardingAddress = ["ju@${networking.domain}"]; }
- { uid="host"; mailForwardingAddress = ["ju@${networking.domain}"]; }
- ];
- };
- };
- };
- };
-}
+++ /dev/null
-# SOURCE: https://github.com/variablenix/ldap-mail-schema/
-dn: cn=postfix-book,cn=schema,cn=config
-objectClass: olcSchemaConfig
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.1
- NAME 'mailHomeDirectory'
- DESC 'The absolute path to the mail user home directory'
- EQUALITY caseExactIA5Match
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
- SINGLE-VALUE )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.2
- NAME 'mailAlias'
- DESC 'RFC822 Mailbox - mail alias'
- EQUALITY caseIgnoreIA5Match
- SUBSTR caseIgnoreIA5SubstringsMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.3
- NAME 'mailUidNumber'
- DESC 'UID required to access the mailbox'
- EQUALITY integerMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
- SINGLE-VALUE )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.4
- NAME 'mailGidNumber'
- DESC 'GID required to access the mailbox'
- EQUALITY integerMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
- SINGLE-VALUE )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.5
- NAME 'mailEnabled'
- DESC 'TRUE to enable, FALSE to disable account'
- EQUALITY booleanMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
- SINGLE-VALUE )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.6
- NAME 'mailGroupMember'
- DESC 'Name of a mail distribution list'
- EQUALITY caseExactIA5Match
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.7
- NAME 'mailQuota'
- DESC 'Mail quota limit in kilobytes'
- EQUALITY caseExactIA5Match
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.8
- NAME 'mailStorageDirectory'
- DESC 'The absolute path to the mail users mailbox'
- EQUALITY caseExactIA5Match
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
- SINGLE-VALUE )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.9
- NAME 'mailSieveRuleSource'
- DESC 'Sun ONE Messaging Server defined attribute'
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
- X-ORIGIN 'Sun ONE Messaging Server' )
-olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.10
- NAME 'mailForwardingAddress'
- DESC 'Address(es) to forward all incoming messages to.'
- EQUALITY caseIgnoreIA5Match
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{320} )
-olcObjectClasses: ( 1.3.6.1.4.1.29426.1.2.2.1
- NAME 'PostfixBookMailAccount'
- DESC 'Mail account used in Postfix Book'
- SUP top AUXILIARY
- MUST mail
- MAY ( mailHomeDirectory $ mailAlias $ mailGroupMember $
- mailUidNumber $ mailGidNumber $ mailEnabled $
- mailQuota $ mailStorageDirectory $ mailSieveRuleSource ) )
-olcObjectClasses: ( 1.3.6.1.4.1.29426.1.2.2.2
- NAME 'PostfixBookMailForward'
- DESC 'Mail forward used in Postfix Book'
- SUP top AUXILIARY
- MUST ( mail $ mailAlias )
- MAY mailForwardingAddress )
+++ /dev/null
-{pkgs, lib, config, nodes, ...}:
-let inherit (builtins) attrNames toFile;
- inherit (lib) types;
- inherit (pkgs.lib) unlines unlinesAttrs;
- inherit (config) networking;
- inherit (config.services) x509 postfix dovecot2 openldap;
- unwords = lib.concatStringsSep " ";
- when = x: y: if x == null then "" else y;
-
- submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" ''
- # Removes sensitive headers from mails handed in via the submission or smtps port.
- # See https://thomas-leister.de/mailserver-debian-stretch/
- # Uses "pcre" style regex.
-
- /^Received:/ IGNORE
- /^User-Agent:/ IGNORE
- /^X-Enigmail:/ IGNORE
- /^X-Mailer:/ IGNORE
- /^X-Originating-IP:/ IGNORE
- '';
-in
-{
-options.services.postfix.aliases = lib.mkOption {
- type = with types; attrsOf (listOf str);
- default = {};
- example = { "root@${networking.domain}" = [
- "user1@${networking.domain}"
- "user2@${networking.domain}"
- ];
- "@example.coop" = ["user1@${networking.domain}"];
- };
-};
-config = {
- systemd.services.postfix.after =
- [ "openldap.service" ] ++
- (if x509.scheme == "letsencrypt"
- then [ "nginx.service" ] # XXX: not sure if this is enough
- else []);
- services.postfix = {
- enable = true;
- #hostname = networking.domain;
- #domain = "localdomain";
- networksStyle = "host";
- #mapFiles."valias" = toFile "valias" (unlines (all_valiases_postfix ++ catchAllPostfix));
- # See https://blog.grimneko.de/2011/12/24/a-bunch-of-tips-for-improving-your-postfix-setup/
- # for details on how this file looks. By using the same file as valias,
- # every alias is uniquely owned by its user.
- # The user's own address is already in all_valiases_postfix.
- #mapFiles."vaccounts" = toFile "vaccounts" (unlines all_valiases_postfix);
- mapFiles."virtual_alias_maps" =
- toFile "virtual_alias_maps"
- (unlinesAttrs
- (from: to: "${from} ${unwords to}")
- postfix.aliases);
- mapFiles."ldap-virtual_alias_maps.cf" =
- toFile "ldap-virtual_alias_maps.cf" ''
- version = 3
- debuglevel = 0
- server_host = ldapi://
- bind = sasl
- sasl_mechs = EXTERNAL
- search_base = ou=posix,${openldap.domainSuffix}
- scope = sub
- dereference = 0
- query_filter = (&(mailAlias=%s)(mailEnabled=TRUE))
- result_format = %s
- result_attribute = mail
- '';
- mapFiles."ldap-forward.cf" =
- toFile "ldap-forward.cf" ''
- version = 3
- debuglevel = 0
- server_host = ldapi://
- bind = sasl
- sasl_mechs = EXTERNAL
- search_base = ou=posix,${openldap.domainSuffix}
- scope = sub
- dereference = 0
- query_filter = (&(mail=%s)(mailEnabled=TRUE))
- result_format = %s
- result_attribute = mailForwardingAddress
- '';
- sslCert = x509.cert;
- sslKey = x509.key;
- #enableSubmission = true;
- #enableSmtp = true;
- destination = [
- "localhost"
- "localhost.localdomain"
- networking.hostName
- "${networking.hostName}.localdomain"
- ];
- networks = [
- "127.0.0.0/8"
- "[::1]/128"
- ];
- recipientDelimiter = "+";
- config = {
- # Appending .domain is the MUA's job
- append_dot_mydomain = false;
- # No console bell on new mail
- biff = false;
- body_checks = "";
- #content_filter = "amavisfeed:[127.0.0.1]:10024";
- #debug_peer_level = 4;
- #debug_peer_list = ".$myhostname";
- default_extra_recipient_limit = "5000";
- # Uncomment the next line to generate "delayed mail" warnings
- #delay_warning_time = "4h";
- # Stops some techniques used to harvest email addresses
- disable_vrfy_command = true;
- duplicate_filter_limit = "5000";
- enable_long_queue_ids = false;
- # Pass unexisting $mydestination recipients to dovecot
- fallback_transport = "lmtp:unix:private/dovecot-lmtp";
- forward_path = [
- ''$home/.forward''${recipient_delimiter}''${extension}''
- "$home/.forward"
- ];
- #header_checks = "regexp:/var/lib/postfix/conf/header_checks";
- #inet_interfaces = "all";
- line_length_limit = "2048";
-
- # Let $fallback_transport check existence of recipients
- local_recipient_maps = "";
- #mail_spool_directory = "/var/spool/mail";
- # NOTE: nixpkgs's default
- #local_header_rewrite_clients = "";
- #home_mailbox = "Maildir/";
- #mailbox_command = ''
- # ${pkgs.procmail}/bin/procmail -t -a "$SENDER" -a "$RECIPIENT" -a "$USER" -a "$EXTENSION" -a "$DOMAIN" -a "$ORIGINAL_RECIPIENT" "$HOME/.procmailrc"
- #'';
- mailbox_size_limit = "204800000";
-
- masquerade_classes = [ "envelope_sender" "header_sender" "header_recipient" ];
- masquerade_domains = "";
- masquerade_exceptions = "root";
- maximal_queue_lifetime = "5d";
- message_size_limit = "20480000";
- mime_header_checks = "";
- milter_header_checks = "";
- nested_header_checks = "";
- #non_smtpd_milters = "";
- parent_domain_matches_subdomains = [
- #"debug_peer_list"
- #"fast_flush_domains"
- #"mynetworks"
- #"permit_mx_backup_networks"
- #"qmqpd_authorized_clients"
- #"smtpd_access_maps"
- ];
- permit_mx_backup_networks = "";
- #policy-spf_time_limit = "3600s";
- propagate_unmatched_extensions = [ "canonical" "virtual" "alias" ];
- queue_minfree = "0";
- #receive_override_options = "no_address_mappings";
- # no_unknown_recipient_checks
- # Do not try to reject unknown recipients (SMTP server only).
- # This is typically specified AFTER an external content filter.
- # no_address_mappings
- # Disable canonical address mapping, virtual alias map expansion,
- # address masquerading, and automatic BCC (blind carbon-copy) recipients.
- # This is typically specified BEFORE an external content filter (eg. amavis).
- # no_header_body_checks
- # Disable header/body_checks. This is typically specified AFTER
- # an external content filter.
- # no_milters
- # Disable Milter (mail filter) applications.
- # This is typically specified AFTER an external content filter.
- # Parse the extension in email address, eg. contact+extension@
- relayhost = "";
- #relay_clientcerts = hash:/var/lib/postfix/conf/relay_clientcerts
- # This is where to put backup MX domains
- relay_domains = "$mydestination";
- relay_recipient_maps = "";
- smtp_body_checks = "";
- #smtp_cname_overrides_servername = false;
- smtp_connect_timeout = "60s";
- #smtp_header_checks = "regexp:/var/lib/postfix/smtp_header_checks";
- smtp_mime_header_checks = "";
- smtp_nested_header_checks = "";
- smtp_tls_exclude_ciphers = [ "ADH" "MD5" "CAMELLIA" "SEED" "3DES" "DES" "RC4" "eNULL" "aNULL" ];
- #smtp_tls_fingerprint_digest = "sha1";
- smtp_tls_loglevel = "1";
- #smtp_tls_note_starttls_offer = true;
- #smtp_tls_policy_maps = "hash:/var/lib/postfix/conf/tls_policy";
- # Only allow TLSv* protocols
- smtp_tls_protocols = [ "!SSLv2" "!SSLv3" ];
- smtp_tls_scert_verifydepth = "5";
- #smtp_tls_secure_cert_match = [ "nexthop" "dot-nexthop" ];
- smtp_tls_security_level = "may";
- smtp_tls_session_cache_database = "btree:$data_directory/smtp_tls_session_cache";
- #smtp_tls_session_cache_timeout = "3600s";
- #smtp_tls_verify_cert_match = "hostname";
- # Useful to test restrictions
- smtpd_authorized_xclient_hosts = "127.0.0.1";
- smtpd_banner = "${networking.fqdn} ESMTP $mail_name (NixOS)";
- smtpd_client_connection_count_limit = "50";
- smtpd_client_connection_rate_limit = "0";
- smtpd_client_event_limit_exceptions = "$mynetworks";
- smtpd_client_message_rate_limit = "0";
- smtpd_client_new_tls_session_rate_limit = "0";
- smtpd_client_port_logging = false;
- smtpd_client_recipient_rate_limit = "0";
- smtpd_client_restrictions = [
- #"check_client_access hash:/var/lib/postfix/conf/client_blacklist"
- ];
- smtpd_data_restrictions = [
- "reject_unauth_pipelining"
- # Force the smtp client to wait OK before sending
- "permit"
- ];
- # Disable opportunistic encryption
- smtpd_discard_ehlo_keywords = "starttls";
- #smtpd_end_of_data_restrictions = "";
- # Ban 5 sec on error
- smtpd_error_sleep_time = "5";
- smtpd_helo_required = true;
- smtpd_helo_restrictions = [
- "reject_invalid_helo_hostname"
- "reject_non_fqdn_helo_hostname"
- #"reject_unknown_helo_hostname"
- # May be useful to fight spam
- "permit"
- ];
- #smtpd_milters = "";
- smtpd_peername_lookup = true;
- smtpd_recipient_limit = "5000";
- smtpd_recipient_overshoot_limit = "5000";
- smtpd_recipient_restrictions = [
- "reject_non_fqdn_recipient"
- #"reject_invalid_hostname"
- "reject_unknown_recipient_domain"
- #"reject_non_fqdn_sender"
- "reject_unauth_pipelining"
- #"check_policy_service inet:localhost:12340"
- # check quota
- "permit_mynetworks"
- #"permit_tls_clientcerts"
- "permit_sasl_authenticated"
- "reject_unverified_recipient"
- # $fallback_transport is responsible of checking the existence of the recipient
- # WARNING: verify(8) has a cache, dumpable if verify(8) is stopped, with:
- # postmap -s btree:/var/lib/postfix/data/verify_cache
- # Bypass SPF check and postgrey if the recipient is not for us or someone in backup_mx
- "reject_unauth_destination"
- # Check SPF
- #"check_policy_service unix:private/spfcheck"
- # Greylisting using postgrey
- #"check_policy_service unix:${postgrey.socket.path}"
- "permit_auth_destination"
- "reject"
- #"reject_unknown_sender_domain"
- # Maybe better in smtpd_sender_restrictions
- #"reject_rbl_client bl.spamcop.net"
- #"reject_rbl_client list.dsbl.org"
- #"reject_rbl_client zen.spamhaus.org"
- #"reject_rbl_client dnsbl.sorbs.net"
- ];
- smtpd_relay_restrictions = [
- "permit_mynetworks"
- "permit_sasl_authenticated"
- # NOTE: permit auth through dovecot's SASL
- "reject_unauth_destination"
- ];
- #smtpd_restriction_classes = "";
- broken_sasl_auth_clients = false;
- #smtpd_sasl_auth_enable = true;
- #smtpd_sasl_path = "private/auth";
- #smtpd_sasl_security_options = "noanonymous";
- #smtpd_sasl_type = "dovecot";
- smtpd_sender_restrictions = [
- "permit_mynetworks"
- #"permit_tls_clientcerts"
- "permit_sasl_authenticated"
- # NOTE: permit auth through dovecot's SASL
- #"check_sender_access hash:/var/lib/postfix/conf/sender_access"
- "reject_unauth_pipelining"
- "reject_non_fqdn_sender"
- #"reject_sender_login_mismatch"
- #"reject_unknown_sender_domain"
- "permit"
- ];
- smtpd_starttls_timeout = "300s";
- #smtpd_tls_always_issue_session_ids = true;
- # No SASL AUTH without TLS
- smtpd_tls_auth_only = true;
- #smtpd_tls_CApath = "/etc/postfix/x509/ca/";
- smtpd_tls_ask_ccert = false;
- #smtpd_tls_ccert_verifydepth = "5";
- smtpd_tls_ciphers = "high";
- smtpd_tls_eecdh_grade = "ultra";
- # Disable weak ciphers as reported by https://ssl-tools.net
- # https://serverfault.com/questions/744168/how-to-disable-rc4-on-postfix
- smtpd_tls_exclude_ciphers = [ "RC4" "aNULL" ];
- smtpd_tls_fingerprint_digest = "sha512";
- # Log only a summary message on TLS handshake completion
- smtpd_tls_loglevel = "1";
- smtpd_tls_mandatory_ciphers = "high";
- smtpd_tls_mandatory_protocols = "TLSv1"; # FIXME: TLSv1.3
- # Only allow TLSv*
- smtpd_tls_protocols = [ "!SSLv2" "!SSLv3" ];
- #smtpd_tls_received_header = false;
- smtpd_tls_req_ccert = false;
- # Postfix 2.3 and later
- # encrypt
- # Mandatory TLS encryption: announce STARTTLS support to SMTP clients, and require that clients use TLS
- # encryption. According to [1720]RFC 2487 this MUST NOT be applied in case of a publicly-referenced
- # SMTP server. Instead, this option should be used only on dedicated servers.
- smtpd_tls_security_level = "may";
- smtpd_tls_session_cache_database = "btree:$data_directory/smtpd_tls_session_cache";
- #smtpd_tls_session_cache_timeout = "3600s";
- # Stops mail from poorly written software
- strict_rfc821_envelopes = true;
- #sympa_destination_recipient_limit = "1";
- #sympabounce_destination_recipient_limit = "1";
- # postconf(5) discourages to change this
- #tls_high_cipherlist = "AES256-SHA";
- #tls_random_bytes = "32";
- # Must not be in a chroot
- #tls_random_exchange_name = "$data_directory/prng_exch";
- #tls_random_prng_update_period = "3600s";
- #tls_random_reseed_period = "3600s";
- # Use a non blocking source of randomness
- tls_random_source = "dev:/dev/urandom";
- transport_maps = [
- #"ldap:transport"
- #"hash:/etc/postfix/transport-dovecot"
- #"hash:/etc/postfix/$mydomain/transport"
- #"hash:/etc/dovecot/transport"
- #"regexp:/etc/sympa/transport"
- ];
- # Rejects immediately what $fallback_transport rejects
- unverified_recipient_reject_code = "550";
- # Do not specify virtual alias domain names in mydestination
- # or relay_domains configuration parameters
- #
- # With a virtual alias domain, the Postfix SMTP server
- # accepts mail for known-user@virtual-alias.domain, and
- # rejects mail for unknown-user@virtual-alias.domain as
- # undeliverable.
- virtual_alias_domains = [];
- virtual_alias_maps = [
- #"hash:/etc/postfix/virtual_alias_maps"
- #"hash:/etc/postfix/virtual_domain_alias_maps"
- "ldap:/etc/postfix/ldap-forward.cf"
- "ldap:/etc/postfix/ldap-virtual_alias_maps.cf"
- #"hash:/etc/postfix/virtual_alias-dovecot"
- #"hash:/var/lib/postfix/conf/valias"
- #"regexp:/etc/sympa/virtual_alias"
- ];
- #virtual_uid_maps = "static:5000";
- #virtual_gid_maps = "static:5000";
- #virtual_mailbox_base = dovecot2.mailDir;
- virtual_mailbox_domains = [ networking.domain ] ++ networking.domainAliases;
- #virtual_mailbox_maps = "hash:/etc/postfix/virtual_mailbox_maps";
- virtual_transport = "lmtp:unix:private/dovecot-lmtp";
- };
- #submissionOptions = {
- # smtpd_tls_security_level = "encrypt";
- # smtpd_sasl_auth_enable = "yes";
- # smtpd_sasl_type = "dovecot";
- # smtpd_sasl_path = "private/auth";
- # smtpd_sasl_security_options = "noanonymous";
- # smtpd_sasl_local_domain = "$myhostname";
- # smtpd_client_restrictions = "permit_sasl_authenticated,reject";
- # smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts";
- # smtpd_sender_restrictions = "reject_sender_login_mismatch";
- # smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
- # cleanup_service_name = "submission-header-cleanup";
- #};
- extraMasterConf = ''
- #spfcheck unix - n n - 0 spawn
- # user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl
- 465 inet n - - - - smtpd
- -o milter_macro_daemon_name=ORIGINATING
- -o smtpd_client_restrictions=permit_sasl_authenticated,reject
- -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
- -o smtpd_sasl_auth_enable=yes
- -o smtpd_sasl_local_domain=$myhostname
- -o smtpd_sasl_path=private/auth
- -o smtpd_sasl_security_options=noanonymous
- -o smtpd_sasl_type=dovecot
- -o smtpd_tls_ask_ccert=no
- -o smtpd_tls_auth_only=yes
- -o smtpd_tls_ccert_verifydepth=0
- -o smtpd_tls_loglevel=1
- -o smtpd_tls_req_ccert=no
- -o smtpd_tls_security_level=encrypt
- -o smtpd_tls_wrappermode=yes
- # -o smtpd_sender_restrictions=reject_sender_login_mismatch
- # -o smtpd_sender_login_maps=hash:/etc/postfix/vaccounts
- # -o cleanup_service_name=submission-header-cleanup
- submission-header-cleanup unix n - n - 0 cleanup
- -o header_checks=pcre:${submissionHeaderCleanupRules}
- #spfcheck unix - n n - 0 spawn
- # user=policyd-spf argv=/usr/bin/postfix-policyd-spf-perl
- #uucp unix - n n - - pipe
- # flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
- #smtp inet n - - - - smtpd
- # -o cleanup_service_name=pre-cleanup
- # -o content_filter=amavis:[127.0.0.1]:10024
- # -o smtpd_sender_restrictions=reject_unauth_pipelining,reject_non_fqdn_sender,permit
- # -o receive_override_options=no_address_mappings
- #amavis unix - - n - 2 lmtp
- # -o lmtp_data_done_timeout=1200
- # -o lmtp_send_xforward_command=yes
- # -o lmtp_tls_note_starttls_offer=no
- #127.0.0.1:10025 inet n - n - - smtpd
- # -o content_filter=
- # -o local_header_rewrite_clients=
- # -o local_recipient_maps=
- # -o mynetworks=127.0.0.0/8
- # -o receive_override_options=no_header_body_checks,no_milters,no_unknown_recipient_checks
- # -o relay_recipient_maps=
- # -o smtpd_client_connection_count_limit=0
- # -o smtpd_client_connection_rate_limit=0
- # -o smtpd_client_restrictions=permit_mynetworks,reject
- # -o smtpd_data_restrictions=reject_unauth_pipelining
- # -o smtpd_delay_reject=no
- # -o smtpd_end_of_data_restrictions=
- # -o smtpd_error_sleep_time=0
- # -o smtpd_hard_error_limit=1000
- # -o smtpd_helo_restrictions=
- # -o smtpd_milters=
- # -o smtpd_recipient_restrictions=permit_mynetworks,reject
- # -o smtpd_restriction_classes=
- # -o smtpd_sender_restrictions=
- # -o smtpd_soft_error_limit=1001
- # -o strict_rfc821_envelopes=yes
- #submission inet n - - - - smtpd
- # -o cleanup_service_name=pre-cleanup
- # -o content_filter=amavis:[127.0.0.1]:10024
- # -o milter_macro_daemon_name=ORIGINATING
- # -o receive_override_options=no_address_mappings
- # -o smtpd_sender_restrictions=permit_tls_clientcerts,reject
- # -o smtpd_tls_ask_ccert=yes
- # -o smtpd_tls_auth_only=yes
- # -o smtpd_tls_ccert_verifydepth=2
- # -o smtpd_tls_loglevel=1
- # -o smtpd_tls_req_ccert=yes
- # -o smtpd_tls_security_level=encrypt
- #smtps inet n - - - - smtpd
- # -o milter_macro_daemon_name=ORIGINATING
- # -o smtpd_client_restrictions=permit_sasl_authenticated,reject
- # -o smtpd_sasl_auth_enable=yes
- # -o smtpd_tls_ask_ccert=yes
- # -o smtpd_tls_auth_only=yes
- # -o smtpd_tls_ccert_verifydepth=0
- # -o smtpd_tls_loglevel=1
- # -o smtpd_tls_req_ccert=no
- # -o smtpd_tls_security_level=encrypt
- # -o smtpd_tls_wrappermode=yes
- #pickup fifo n - - 60 1 pickup
- # -o cleanup_service_name=pre-cleanup
- # -o content_filter=amavis:[127.0.0.1]:10024
- #pre-cleanup unix n - - - 0 cleanup
- # -o virtual_alias_maps=
- #cleanup unix n - - - 0 cleanup
- # -o mime_header_checks=
- # -o nested_header_checks=
- # -o body_checks=
- # -o header_checks=
- #-- SYMPA begin
- #sympa unix - n n - - pipe
- # flags=R user=sympa argv=/usr/lib/sympa/bin/queue ''${recipient}
- #sympabounce unix - n n - - pipe
- # flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ''${recipient}
- #-- SYMPA end
- '';
- #noclue unix - n n - - pipe
- # flags=q user=noclue argv=/usr/local/bin/noclue-delivery ${recipient} ${sender}
- };
-};
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (lib) types;
- inherit (pkgs.lib) unlines unlinesAttrs;
- inherit (config) networking users;
- inherit (config.services) postgresql;
- psql = "${pkgs.postgresql}/bin/psql --set ON_ERROR_STOP=1 -U ${postgresql.superUser}";
- psqlRun = args: sql: "${psql} ${args} -f - <<EOF\n" + sql + "\nEOF\n";
- initLanguages = initLanguagePLPGSQL;
- initLanguagePLPGSQL =
- psqlRun "template1" ''
- CREATE OR REPLACE FUNCTION create_language_plpgsql()
- RETURNS BOOLEAN AS \$\$
- CREATE LANGUAGE plpgsql;
- SELECT TRUE;
- \$\$ LANGUAGE SQL;
- SELECT CASE WHEN NOT (
- SELECT TRUE AS exists
- FROM pg_language
- WHERE lanname = 'plpgsql'
- UNION
- SELECT FALSE AS exists
- ORDER BY exists DESC
- LIMIT 1
- )
- THEN
- create_language_plpgsql()
- ELSE
- FALSE
- END AS plpgsql_created;
- DROP FUNCTION create_language_plpgsql();
- '';
- initSchemas =
- initSchemaPublic +
- psqlRun "" (unlinesAttrs (schema: {owner, extraConfig, ...}: ''
- DO LANGUAGE plpgsql \$\$
- BEGIN
- IF NOT EXISTS (SELECT * FROM pg_catalog.pg_namespace WHERE nspname = "${schema}" LIMIT 1) THEN
- CREATE SCHEMA ${schema}
- AUTHORIZATION ${owner};
- END IF;
- END;
- \$\$;
- ${extraConfig}
- '') postgresql.schemas);
- initSchemaPublic =
- psqlRun "template1" ''
- -- NOTE: deny access to public schema,
- -- so that users do not see others' databases
- REVOKE ALL ON DATABASE template1 FROM public;
- REVOKE ALL ON SCHEMA public FROM public;
- GRANT ALL ON SCHEMA public TO ${postgresql.superUser};
- '' +
- psqlRun "template1" ''
- -- NOTE: deny public access to all tables
- REVOKE ALL ON ALL TABLES IN SCHEMA pg_catalog FROM public;
- REVOKE ALL ON SCHEMA pg_catalog FROM public;
- '';
- initUsers = psqlRun ""
- (unlinesAttrs (user: {auth, extraConfig, ...}: ''
- DO LANGUAGE plpgsql \$\$
- BEGIN
- IF NOT EXISTS (SELECT * FROM pg_catalog.pg_user WHERE usename = '${user}' LIMIT 1) THEN
- CREATE ROLE ${user}
- LOGIN
- NOCREATEDB
- NOCREATEROLE
- NOINHERIT
- NOSUPERUSER;
- END IF;
- END;
- \$\$;
- GRANT USAGE ON SCHEMA public TO ${user};
- ${extraConfig}
- '') postgresql.users);
- initRoles = psqlRun ""
- (unlinesAttrs (role: {...}: ''
- DO LANGUAGE plpgsql \$\$
- BEGIN
- IF NOT EXISTS (SELECT * FROM pg_catalog.pg_roles WHERE rolname = "${role}" LIMIT 1) THEN
- CREATE ROLE ${role}
- NOLOGIN
- NOCREATEDB
- NOCREATEROLE
- NOINHERIT
- NOSUPERUSER;
- END IF;
- END;
- \$\$;
- '') postgresql.roles);
- initDatabases =
- unlinesAttrs (db: {owner ? db, users, extraConfig, ...}: ''
- # NOTE: CREATE DATABASE cannot be done inside a function or multi-lines input.
- exists="$(${psql} template1 -t -c "SELECT datname FROM pg_catalog.pg_database WHERE datname = '${db}' LIMIT 1")"
- test -n "$exists" ||
- ${psql} template1 -c "CREATE DATABASE ${db} WITH OWNER=${owner};"
- '' + psqlRun "template1" ''
- REVOKE ALL ON DATABASE ${db} FROM public;
- ${unlines (map (user: "GRANT CONNECT,TEMPORARY ON DATABASE ${db} TO ${user};") users)}
- '' +
- psqlRun db ''
- GRANT ALL ON SCHEMA public TO ${owner} WITH GRANT OPTION;
- ${extraConfig}
- ''
- ) postgresql.databases;
-in
-{
- options = {
- services.postgresql.databases = lib.mkOption {
- default = {};
- type = types.attrsOf (types.submodule ({name, options, config, ...}: {
- options = {
- data = lib.mkOption {
- type = types.lines;
- description = "The database's data in SQL.";
- };
- owner = lib.mkOption {
- type = types.str;
- description = "The database's owner.";
- default = name;
- };
- users = lib.mkOption {
- type = types.listOf types.str;
- description = "Databases' users.";
- default = [];
- };
- resetData = lib.mkOption {
- type = types.bool;
- description = "Whether to reset the data at each start of the postgresql service.";
- default = false;
- };
- extraConfig = lib.mkOption {
- type = types.lines;
- description = "Extra SQL config code (run on the database).";
- default = "";
- };
- };
- }));
- };
- services.postgresql.schemas = lib.mkOption {
- default = {};
- type = types.attrsOf (types.submodule ({name, options, config, ...}: {
- options = {
- data = lib.mkOption {
- type = types.lines;
- description = "The schema's data in SQL.";
- };
- resetData = lib.mkOption {
- type = types.bool;
- description = "Whether to reset the data at each start of the postgresql service.";
- default = false;
- };
- extraConfig = lib.mkOption {
- type = types.lines;
- description = "Extra SQL config code.";
- default = "";
- };
- };
- }));
- };
- services.postgresql.users = lib.mkOption {
- default = {};
- type = types.attrsOf (types.submodule ({name, options, config, ...}: {
- options = {
- auth = lib.mkOption {
- type = types.enum [ "unix" "password" ];
- description = "Authentification method.";
- default = "unix";
- };
- extraConfig = lib.mkOption {
- type = types.lines;
- description = "Extra SQL config code.";
- default = "";
- };
- };
- }));
- };
- services.postgresql.roles = lib.mkOption {
- default = {};
- type = types.attrsOf (types.submodule ({name, options, config, ...}: {
- options = {
- };
- }));
- };
- };
- config = {
- users.users = lib.mapAttrs (user: {auth, ...}:
- { extraGroups = lib.optionals (auth == "unix")
- [ users.users.postgres.group ];
- }
- ) postgresql.users;
- systemd.services.postgresql = {
- postStart = unlines [
- "set -x"
- initLanguages
- initSchemas
- initUsers
- initRoles
- initDatabases
- ];
- #serviceConfig = {
- # RuntimeDirectory = [ "postgresql" ];
- # RuntimeDirectoryMode = "0775";
- #};
- };
- services = {
- postgresql = {
- enable = true;
- extraConfig = ''
- #unix_socket_directories = '/run/postgresql'
- #unix_socket_group = '${postgresql.superUser}-data'
- unix_socket_permissions = 0770 # begin with 0 to use octal notation
- '';
- identMap = lib.mkForce ''
- # MAPNAME SYSTEM-USERNAME PG-USERNAME
- admin ${postgresql.superUser} ${postgresql.superUser}
- admin root ${postgresql.superUser}
- ${unlinesAttrs (user: {...}: ''user root ${user}'') postgresql.users}
- user /^(.*)$ \1
- '';
- authentication = lib.mkForce ''
- # CONNECTION DATABASE USER AUTH OPTIONS
- local all postgres peer map=admin
- local all backup peer
- local sameuser all peer map=user
- local samerole all peer map=role
- '';
- };
- postgresqlBackup = {
- enable = true;
- };
- };
- #users.groups."${postgresql.superUser}-data" = {
- # name = "${postgresql.superUser}-data";
- #};
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (config.services) postgrey;
-in
-{
- config = {
- services.postgrey = {
- enable = true;
- autoWhitelist = 5;
- maxAge = 35;
- delay = 65;
- privacy = true;
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins.extraBuiltins) pass;
- inherit (config) networking;
- inherit (config.services) redmine postgresql gitolite;
- redmine_git_hosting_settings = pkgs.writeText "settings.yml" ''
- ---
- # Gitolite SSH Config
- gitolite_user: '${gitolite.user}'
- gitolite_server_host: 'localhost'
- gitolite_server_port: '22'
- #gitolite_ssh_private_key: <%= Rails.root.join('plugins', 'redmine_git_hosting', 'ssh_keys', 'redmine_gitolite_admin_id_rsa') %>
- #gitolite_ssh_public_key: <%= Rails.root.join('plugins', 'redmine_git_hosting', 'ssh_keys', 'redmine_gitolite_admin_id_rsa.pub') %>
- gitolite_ssh_private_key: '${redmine.stateDir}/.ssh/id_ed25519'
- gitolite_ssh_public_key: '${redmine.stateDir}/.ssh/id_ed25519.pub'
-
- # Gitolite Storage Config
- gitolite_global_storage_dir: 'repositories/'
- gitolite_redmine_storage_dir: ""
- gitolite_recycle_bin_dir: 'recycle_bin/'
- gitolite_lib_dir: '${pkgs.gitolite}/bin/lib'
- gitolite_local_code_dir: 'local/'
-
- # Gitolite Config File
- gitolite_config_file: 'gitolite.conf'
- gitolite_identifier_prefix: 'redmine_'
- gitolite_identifier_strip_user_id: 'false'
-
- # Gitolite Global Config
- gitolite_temp_dir: <%= Rails.root.join('tmp', 'redmine_git_hosting') %>
- gitolite_recycle_bin_expiration_time: '24.0'
- gitolite_log_level: 'info'
- git_config_username: 'Redmine Git Hosting'
- git_config_email: 'redmine@${networking.domain}'
-
- # Gitolite Hooks Config
- gitolite_overwrite_existing_hooks: 'true'
- gitolite_hooks_are_asynchronous: 'false'
- gitolite_hooks_debug: 'false'
- gitolite_hooks_url: 'http://localhost:3000'
-
- # Gitolite Cache Config
- gitolite_cache_max_time: '86400'
- gitolite_cache_max_size: '16'
- gitolite_cache_max_elements: '2000'
- gitolite_cache_adapter: 'database'
-
- # Gitolite Access Config
- ssh_server_domain: 'localhost'
- http_server_domain: 'localhost'
- https_server_domain: 'localhost'
- http_server_subdir: ""
- show_repositories_url: 'true'
- gitolite_daemon_by_default: 'false'
- gitolite_http_by_default: '1'
-
- # Redmine Config
- redmine_has_rw_access_on_all_repos: 'true'
- all_projects_use_git: 'false'
- init_repositories_on_create: 'false'
- delete_git_repositories: 'true'
-
- # This params work together!
- # When hierarchical_organisation = true unique_repo_identifier MUST be false
- # When hierarchical_organisation = false unique_repo_identifier MUST be true
- hierarchical_organisation: 'true'
- unique_repo_identifier: 'false'
-
- # Download Revision Config
- download_revision_enabled: 'true'
-
- # Git Mailing List Config
- gitolite_notify_by_default: 'false'
- gitolite_notify_global_prefix: '[REDMINE]'
- gitolite_notify_global_sender_address: 'redmine@${networking.domain}'
- gitolite_notify_global_include: []
- gitolite_notify_global_exclude: []
-
- # Sidekiq Config
- gitolite_use_sidekiq: 'false'
- '';
-in
-{
- config = {
- services = {
- redmine = {
- enable = true;
- package = with pkgs.redmine.plugins; pkgs.redmineWithPlugins [
- #redmine_git_hosting
- #clipboard_image_paste
- #redmine_revision_branches
- ];
- database = {
- type = "postgresql";
- host = "/tmp";
- port = postgresql.port;
- };
- config = {
- "configuration.yml" = lib.mkForce ''
- default:
- scm_git_command: ${pkgs.git}/bin/git
- '';
- };
- };
- postgresql = {
- users."${redmine.user}" = {
- auth = "unix";
- };
- databases."${redmine.database.name}" = {
- owner = redmine.user;
- users = [ redmine.user ];
- extraConfig = ''
- GRANT USAGE ON SCHEMA pg_catalog TO ${redmine.user};
- GRANT SELECT ON ALL TABLES IN SCHEMA pg_catalog TO ${redmine.user};
- '';
- };
- };
- nginx = {
- upstreams."redmine" = {
- servers = { "localhost:3000" = {}; };
- };
- virtualHosts."redmine" = {
- serverName = "redmine.${networking.domain}";
- serverAliases =
- map (domainAlias: "redmine." + domainAlias)
- config.networking.domainAliases;
- locations = {
- "/" = {
- extraConfig = ''
- proxy_next_upstream error timeout
- invalid_header http_500 http_502 http_503;
- proxy_pass http://localhost:3000;
- '';
- };
- };
- };
- };
- };
- systemd.services.redmine = {
- path = lib.mkForce [
- pkgs.gitAndTools.git
- pkgs.imagemagickBig
- pkgs.coreutils
- pkgs.findutils
- pkgs.gnused
- /*
- pkgs.gitolite
- pkgs.coreutils
- pkgs.openssh
- (config.security.wrapperDir + "/..")
- */
- ];
- #environment.REDMINE_LANG = lib.mkForce "fr";
- /*
- path = [
- pkgs.gitolite
- pkgs.coreutils
- pkgs.openssh
- (config.security.wrapperDir + "/..")
- ];
- after = [ "keys.target" ];
- preStart = ''
- # comply with openssh's strict mode
- install -D -d -o ${redmine.user} -g ${redmine.group} -m 0700 \
- ${redmine.stateDir}/.ssh
- install -o ${redmine.user} -g ${redmine.group} -m 0400 \
- /run/keys/redmine_git_hosting_id_ed25519 \
- ${redmine.stateDir}/.ssh/id_ed25519
- install -o ${redmine.user} -g ${redmine.group} -m 0400 \
- ${pkgs.writeText "redmine_git_hosting_id_ed25519.pub"
- (builtins.readFile ../../../sec/var/ssh/redmine_git_hosting/id_ed25519.pub)} \
- ${redmine.stateDir}/.ssh/id_ed25519.pub
- install -o ${redmine.user} -g ${redmine.group} -m 0400 \
- ${pkgs.writeText "config" ''
- Host localhost
- PasswordAuthentication no
- PreferredAuthentications publickey
- StrictHostKeyChecking no
- UserKnownHostsFile /dev/null
- ''} \
- ${redmine.stateDir}/.ssh/config
-
- # push settings.yml
- ln -fns ${redmine_git_hosting_settings} \
- ${redmine.stateDir}/redmine_git_hosting.yml
- ${redmine.stateDir}/bundle exec rake redmine_git_hosting:update_settings
- install hooks and parameters
- ${redmine.stateDir}/bundle exec rake redmine_git_hosting:install_gitolite_hooks
- '';
- */
- };
- users.users."${redmine.user}" = {
- extraGroups = [
- gitolite.group
- ];
- };
- deployment.keys.redmine_git_hosting_id_ed25519 = {
- text = pass "${networking.domain}/${networking.hostName}/redmine_git_hosting/ssh" + "\n";
- #destDir = "${redmine.stateDir}/.ssh";
- #path = "${redmine.stateDir}/.ssh/id_ed25519";
- user = redmine.user;
- group = redmine.group;
- permissions = "0400"; # XXX: not enforced when deployment.storeKeysOnMachine = true
- };
- security.sudo.extraRules = [
- { users = [ redmine.user ];
- groups = [ redmine.group ];
- runAs = gitolite.user;
- commands = [ { command = "ALL"; options = [ "SETENV" "NOPASSWD" ]; } ];
- }
- ];
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins) attrNames;
- inherit (lib) types;
- inherit (config.services) dkim dovecot2 rmilter;
-
- createDomainDkimCert = domain:
- let dkim_key = "${dkim.keyDir}/${domain}.${dkim.selector}.key";
- dkim_txt = "${dkim.keyDir}/${domain}.${dkim.selector}.txt";
- in ''
- if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ]
- then
- ${pkgs.opendkim}/bin/opendkim-genkey \
- --domain "${domain}" \
- --selector "${dkim.selector}" \
- --directory="${dkim.keyDir}"
- mv "${dkim.keyDir}/${dkim.selector}.private" "${dkim_key}"
- mv "${dkim.keyDir}/${dkim.selector}.txt" "${dkim_txt}"
- fi
- '';
-in
-{
- options.services.dkim = lib.mkOption {
- default = {};
- type = types.submodule {
- options = {
- keyDir = lib.mkOption {
- type = types.path;
- default = "/var/lib/dkim";
- description = ''
- '';
- };
- selector = lib.mkOption {
- type = types.str;
- default = "mail";
- description = ''
- '';
- };
- };
- };
- };
- config = {
- services.rspamd = {
- enable = true;
- };
- /*
- services.redis = {
- enable = true;
- };
- */
- services.rmilter = {
- enable = true;
- #debug = true;
- postfix = {
- enable = true;
- };
- rspamd = {
- enable = true;
- extraConfig = "extended_spam_headers = yes;";
- };
- extraConfig = ''
- use_redis = true;
- max_size = 20M;
- #clamav {
- # servers = /var/run/clamav/clamd.ctl;
- #};
- # NOTE: domain = "*"; causes rmilter to try to search key in the key path
- # as keypath/domain.selector.key for any domain.
- dkim {
- domain {
- domain = "*";
- key = "${dkim.keyDir}";
- selector = "${dkim.selector}";
- };
- sign_alg = sha256;
- auth_only = yes;
- };
- '';
- bindSocket.type = "unix";
- bindSocket.path = "/run/rmilter.sock";
- # NOTE: fix default which is in wiped out directory /run/rmilter/rmilter.sock
- };
- #systemd.sockets.rmilter.socketConfig.Accept = false;
- systemd.services.rmilter = {
- requires = [ "rmilter.socket" ];
- after = [ "rmilter.socket" ];
- preStart = ''
- install -D -d -o rmilter -g rmilter ${dkim.keyDir}
- ${lib.concatStringsSep "\n" (map createDomainDkimCert (attrNames dovecot2.domains))}
- chown -R rmilter:rmilter "${dkim.keyDir}"
- '';
- };
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins) attrNames;
- inherit (builtins.extraBuiltins) pass;
- inherit (lib) types;
- inherit (pkgs.lib) unlinesAttrs;
- inherit (config) networking;
- inherit (config.services) rspamd-upstream dkim;
- /*
- localConfig = pkgs.writeText "local.conf" ''
- classifier "bayes" {
- autolearn = true;
- }
- dkim_signing {
- path = "/var/lib/rspamd/dkim/$domain.$selector.key";
- selector = "default";
- allow_username_mismatch = true;
- }
- arc {
- path = "/var/lib/rspamd/dkim/$domain.$selector.key";
- selector = "default";
- allow_username_mismatch = true;
- }
- milter_headers {
- use = ["authentication-results", "x-spam-status"];
- authenticated_headers = ["authentication-results"];
- }
- replies {
- action = "no action";
- }
- url_reputation {
- enabled = true;
- }
- phishing {
- openphish_enabled = true;
- phishtank_enabled = true;
- }
- '';
- */
-in
-{
- options.services.dkim = lib.mkOption {
- default = {};
- type = types.submodule {
- options = {
- domains = lib.mkOption {
- default = {};
- type = types.attrsOf (types.submodule {
- options = {
- selector = lib.mkOption {
- type = types.str;
- description = ''Current selector.'';
- };
- selectors = lib.mkOption {
- default = {};
- description = ''Available selectors.'';
- type = types.attrsOf (types.submodule {
- options = {
- key = lib.mkOption {
- type = types.str;
- description = ''Private key.'';
- };
- dns = lib.mkOption {
- type = types.str;
- description = ''DNS record.'';
- };
- };
- });
- };
- };
- });
- };
- };
- };
- };
- config = {
- deployment.keys = builtins.listToAttrs (map
- (domain:
- let selector = dkim.domains."${domain}".selector; in
- { name = "dkim.${domain}.${selector}.key";
- value = {
- text = pass "${networking.domainBase}/dkim/${selector}/key" + "\n";
- #destDir = "${redmine.stateDir}/.ssh";
- #path = "${redmine.stateDir}/.ssh/id_ed25519";
- user = rspamd-upstream.user;
- group = rspamd-upstream.group;
- permissions = "0400"; # XXX: not enforced when deployment.storeKeysOnMachine = true
- };
- })
- ([ networking.domain ] ++ networking.domainAliases));
-
- systemd.services.rspamd-upstream = {
- path = [
- pkgs.coreutils
- ];
- after = [ "keys.target" ];
- preStart = unlinesAttrs (domain: dom: ''
- install -D -o ${rspamd-upstream.user} -g ${rspamd-upstream.group} -m 0400 \
- /run/keys/dkim.${domain}.${dom.selector}.key \
- /var/lib/rspamd/dkim/${domain}.${dom.selector}.key
- '') dkim.domains;
- };
-
- services.rspamd-upstream = {
- enable = true;
- debug = false;
- postfix = {
- enable = true;
- };
- locals =
- let selector_map_file =
- pkgs.writeText "dkim_selectors.map"
- (pkgs.lib.unlinesAttrs
- (domain: dom: "${domain} ${dom.selector}")
- dkim.domains); in {
- "dkim_signing.conf".text = ''
- path = "/var/lib/rspamd/dkim/$domain.$selector.key";
- selector_map = ${selector_map_file};
- allow_username_mismatch = true;
- '';
- "arc.conf".text = ''
- path = "/var/lib/rspamd/dkim/$domain.$selector.key";
- selector_map = ${selector_map_file};
- allow_username_mismatch = true;
- '';
- /*
- "logging.conf" = ''
- debug_modules = [“dkim_signing”]
- '';
- */
- };
- overrides = {
- "milter_headers.conf".text = ''
- extended_spam_headers = true;
- '';
- "actions.conf".text = ''
- actions {
- reject = 15; # Reject when reaching this score
- add_header = 6; # Add header when reaching this score
- greylist = 4; # Apply greylisting when reaching this score (will emit `soft reject action`)
- }
- '';
- };
- workers = {
- normal = {
- /*
- includes = [ "$CONFDIR/worker-normal.inc" ];
- bindSockets = [{
- socket = "/run/rspamd/rspamd.sock";
- mode = "0660";
- owner = "${cfg.user}";
- group = "${cfg.group}";
- }];
- */
- };
- controller = {
- #includes = [ "$CONFDIR/worker-controller.inc" ];
- bindSockets = [ "*:11334" ]; # FIXME: localhost only
- extraConfig = ''
- #count = 1;
- #static_dir = "''${WWWDIR}";
- # USE: rspamadm pw
- password = "$2$fy8padyutwigfchjbye88h7i4exwx9gw$m3ohkqu9fartjkjz5oeok5xwxamwime63998awryxdt8dt431eoy";
- '';
- };
- };
- };
- /*
- services.rspamd-upstream = {
- enable = true;
- # FIXME: the order of sockets is messed up
- socketActivation = false;
- extraConfig = ''
- .include(priority=1,duplicate=merge) "${localConfig}"
- '';
-
- workers.controller = {
- extraConfig = ''
- count = 1;
- static_dir = "''${WWWDIR}";
- password = "$2$cifyu958qabanmtjyofmf5981posxie7$dz3taiiumir9ew5ordg8n1ia3eb73y1t55kzc9qsjdq1n8esmqqb";
- enable_password = "$2$cifyu958qabanmtjyofmf5981posxie7$dz3taiiumir9ew5ordg8n1ia3eb73y1t55kzc9qsjdq1n8esmqqb";
- '';
- };
-
- workers.rspamd_proxy = {
- type = "proxy";
- extraConfig = ''
- milter = yes; # Enable milter mode
- timeout = 120s; # Needed for Milter usually
- upstream "local" {
- default = yes;
- self_scan = yes;
- }
- count = 1; # Do not spawn too many processes of this type
- '';
- bindSockets = [{
- socket = "/run/rspamd.sock";
- mode = "0666";
- owner = "rspamd";
- group = "rspamd";
- }];
- };
- };
- */
-
- /*
- services.postfix.extraConfig = ''
- smtpd_milters = unix:/run/rspamd.sock
- milter_default_action = accept
- '';
- # Allow users to run 'rspamc' and 'rspamadm'.
- environment.systemPackages = [ pkgs.rspamd ];
- */
-
- /*
- services.redis = {
- enable = true;
- };
- */
- };
-}
+++ /dev/null
-{pkgs, lib, config, ...}:
-let inherit (builtins) hasAttr readFile;
- inherit (pkgs.lib) unlinesAttrs;
- inherit (config.services) shorewall shorewall6;
- zones4 = config.networking.zones;
- zones6 = config.networking.zones;
- "macro.Git" = ''
- ?FORMAT 2
- #ACTION SOURCE DEST PROTO DEST SOURCE RATE USER/
- # PORT(S) PORT(S) LIMIT GROUP
- PARAM - - tcp 9418
- '';
-in
-{
-config = {
- services.shorewall = {
- enable = true;
- configs = {
- "shorewall.conf" = ''
- ${readFile "${shorewall.package}/etc-example/shorewall/shorewall.conf"}
- #
- ## Custom config
- ###
- STARTUP_ENABLED=Yes
- ZONE2ZONE=2
- '';
- zones = ''
- # DOC: shorewall-zones(5)
- fw firewall
- '' + unlinesAttrs (zone: _: "${zone} ipv4") zones4;
- interfaces = ''
- # DOC: shorewall-interfaces(5)
- ?FORMAT 2
- '' + unlinesAttrs (zone: {iface, ...}:
- "${zone} ${iface} arp_filter,nosmurfs,routefilter,tcpflags") zones4;
- policy = ''
- # DOC: shorewall-policy(5)
- $FW all DROP
- '' + unlinesAttrs (zone: _: "${zone} all DROP none") zones4
- + ''
- # XXX: the following policy must be last
- all all REJECT none
- '';
- rules = ''
- # DOC: shorewall-rules(5)
- #SECTION ALL
- #SECTION ESTABLISHED
- #SECTION RELATED
- ?SECTION NEW
- ''
- + lib.optionalString (hasAttr "lan" zones4) ''
- # ----------
- # $FW -> lan
- # ----------
- ACCEPT $FW lan:${zones4.lan.ipv4}/24
-
- # ----------
- # lan -> $FW
- # ----------
- ACCEPT lan:${zones4.lan.ipv4}/24 $FW
- ''
- + lib.optionalString (hasAttr "net" zones4) ''
- # ----------
- # $FW -> net
- # ----------
-
- # By protocol
- Ping(ACCEPT) $FW net
-
- # By port
- DNS(ACCEPT) $FW net
- Git(ACCEPT) $FW net
- HTTP(ACCEPT) $FW net
- HTTPS(ACCEPT) $FW net
- SMTP(ACCEPT) $FW net
- SMTPS(ACCEPT) $FW net
- SSH(ACCEPT) $FW net
-
- # ----------
- # net -> $FW
- # ----------
-
- # By protocol
- Ping(ACCEPT) net $FW
-
- # By port
- #HTTPS(ACCEPT) net $FW
- DNS(ACCEPT) net $FW
- IMAPS(ACCEPT) net $FW
- POP3S(ACCEPT) net $FW
- SMTP(ACCEPT) net $FW
- SMTPS(ACCEPT) net $FW
- '';
- inherit "macro.Git";
- };
- };
- services.shorewall6 = {
- enable = true;
- configs = {
- "shorewall6.conf" = ''
- ${readFile "${shorewall6.package}/etc-example/shorewall6/shorewall6.conf"}
- #
- ## Custom config
- ###
- STARTUP_ENABLED=Yes
- ZONE2ZONE=2
- '';
- zones = ''
- # DOC: shorewall-zones(5)
- fw firewall
- '' + unlinesAttrs (zone: _: "${zone} ipv6") zones6;
- interfaces = ''
- # DOC: shorewall-interfaces(5)
- ?FORMAT 2
- '' + unlinesAttrs (zone: {iface, ...}: "${zone} ${iface} nosmurfs,tcpflags") zones6;
- policy = ''
- # DOC: shorewall-policy(5)
- $FW all DROP
- '' + unlinesAttrs (zone: _: "${zone} all DROP none") zones6
- + ''
- # XXX: the following policy must be last
- all all REJECT none
- '';
- rules = ''
- # DOC: shorewall-rules(5)
- #SECTION ALL
- #SECTION ESTABLISHED
- #SECTION RELATED
- ?SECTION NEW
- ''
- + lib.optionalString (hasAttr "lan" zones6) ''
- # ----------
- # $FW -> lan
- # ----------
- Ping(ACCEPT) $FW lan:fe80::/10
-
- # ----------
- # lan -> $FW
- # ----------
- Ping(ACCEPT) lan:fe80::/10 $FW
- SSH(ACCEPT) lan:fe80::/10 $FW
- Git(ACCEPT) lan:fe80::/10 $FW
- '';
- inherit "macro.Git";
- };
- };
-};
-}
+++ /dev/null
-barrau (Aurélien Barrau)
-calimaq (Lionel Maurel)
-casilli (Antonio Casilli)
-colasse (Bernard Colasse)
-friot (Bernard Friot)
-greenwald (Glenn Greenwald)
-herrou (Cédric Herrou)
-jancovici (Jean-Marc Jancovici)
-lacroix-riz (Annie Lacroix-Riz)
-lordon (Frédéric Lordon)
-losurdo (Domenico Losurdo)
-pauwels (Jacques Pauwels)
-pincon-charlot (Michel Pinçon & Monique Pinçon-Charlot)
-roberts (Denis Roberts)
-scahill (Jeremy Scahill)
-dupuis-deri (Françis Dupuis-Déri)
-deneault (Alain Deneault)
-mermet (Laurent Mermet)
+++ /dev/null
-{pkgs, lib, config, system, ...}:
-let inherit (builtins.extraBuiltins) pass;
- inherit (lib) types;
- inherit (config) networking;
- userPass = name: pass "${networking.domainBase}/${networking.hostName}/login/${name}";
-in {
-imports = [
- <nixpkgs-sourcephile/install/modules.nix>
- ../overlays/tools/networking/shorewall/service.nix
- ../overlays/tools/networking/shorewall6/service.nix
- ../overlays/servers/mail/rspamd/service.nix
- ../overlays.nix
- ../options.nix
-];
-}
+++ /dev/null
-{ pkgs, lib, config, ... }:
-{
- imports =
- [ ./physical.nix
- ./hosting.nix
- ./logical.nix
- ];
-}
+++ /dev/null
-{ config, lib, pkgs, ... }:
-{
- imports =
- [ ./logical/boot.nix
- ./logical/zfs.nix
- ./logical/system.nix
- ];
-}
+++ /dev/null
-{ pkgs, lib, config, ... }:
-{
- # Clean /tmp automatically on boot.
- boot.cleanTmpDir = true;
-}
+++ /dev/null
-{ config, lib, pkgs, ... }:
-{
- networking = {
- hostName = "mermet";
- };
-}
+++ /dev/null
-with builtins;
-import (toPath ./. + "/physical/" + getEnv "MERMET_PHYSICAL" + ".nix")
+++ /dev/null
-map import
-[ overlays/servers/mail/postfix.nix
- overlays/servers/mail/dovecot.nix
- # TODO: remove when using a nixpkgs including the fix
- # https://github.com/NixOS/nixpkgs/pull/46859
- overlays/lib/strings.nix
- overlays/users-init.nix
- overlays/tools/networking/shorewall.nix
- overlays/tools/networking/shorewall6.nix
- #overlays/applications/version-management/redmine/redmine_git_hosting.nix
-]
+++ /dev/null
-self: super:
-{}
-/*
-let lib = super.lib;
- version = "1.2.3";
- rubyEnv = self.bundlerEnv {
- name = "redmine_git_hosting-env-${version}";
- ruby = self.ruby;
- gemdir = ./.;
- };
-in
-{
- redmine_git_hosting =
- super.stdenv.mkDerivation rec {
- name = "redmine_git_hosting-${version}";
- inherit version;
-
- src = super.fetchurl {
- url = "https://www.redmine.org/attachments/download/18780/${name}.tar.gz";
- sha256 = "1kkd5cqxa7q9jckrwrjr4gfwf5ncakrjqyypiqz9agrmipdqmxh2";
- };
- buildInputs = [ rubyEnv rubyEnv.wrappedRuby rubyEnv.bundler ];
- installPhase = ''
- cp -a . $out
- '';
-
- meta = with super.stdenv.lib; {
- homepage = https://www.redmine.org/plugins/redmine_git_hosting;
- platforms = super.lib.platforms.linux;
- license = super.lib.licenses.mit;
- };
- };
-}
-*/
+++ /dev/null
-{ config, options, pkgs, lib, ... }:
-
-with lib;
-
-let
-
- cfg = config.services.rspamd-upstream;
- opts = options.services.rspamd-upstream;
- postfixCfg = config.services.postfix;
-
- bindSocketOpts = {options, config, ... }: {
- options = {
- socket = mkOption {
- type = types.str;
- example = "localhost:11333";
- description = ''
- Socket for this worker to listen on in a format acceptable by rspamd.
- '';
- };
- mode = mkOption {
- type = types.str;
- default = "0644";
- description = "Mode to set on unix socket";
- };
- owner = mkOption {
- type = types.str;
- default = "${cfg.user}";
- description = "Owner to set on unix socket";
- };
- group = mkOption {
- type = types.str;
- default = "${cfg.group}";
- description = "Group to set on unix socket";
- };
- rawEntry = mkOption {
- type = types.str;
- internal = true;
- };
- };
- config.rawEntry = let
- maybeOption = option:
- optionalString options.${option}.isDefined " ${option}=${config.${option}}";
- in
- if (!(hasPrefix "/" config.socket)) then "${config.socket}"
- else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
- };
-
- traceWarning = w: x: builtins.trace "\e[1;31mwarning: ${w}\e[0m" x;
-
- workerOpts = { name, options, ... }: {
- options = {
- enable = mkOption {
- type = types.nullOr types.bool;
- default = null;
- description = "Whether to run the rspamd worker.";
- };
- name = mkOption {
- type = types.nullOr types.str;
- default = name;
- description = "Name of the worker";
- };
- type = mkOption {
- type = types.nullOr (types.enum [
- "normal" "controller" "fuzzy_storage" "rspamd_proxy" "lua" "proxy"
- ]);
- description = ''
- The type of this worker. The type <literal>proxy</literal> is
- deprecated and only kept for backwards compatibility and should be
- replaced with <literal>rspamd_proxy</literal>.
- '';
- apply = let
- from = "services.rspamd-upstream.workers.\”${name}\".type";
- files = options.type.files;
- warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
- in x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
- };
- bindSockets = mkOption {
- type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
- default = [];
- description = ''
- List of sockets to listen, in format acceptable by rspamd
- '';
- example = [{
- socket = "/run/rspamd.sock";
- mode = "0666";
- owner = "rspamd";
- } "*:11333"];
- apply = value: map (each: if (isString each)
- then if (isUnixSocket each)
- then {socket = each; owner = cfg.user; group = cfg.group; mode = "0644"; rawEntry = "${each}";}
- else {socket = each; rawEntry = "${each}";}
- else each) value;
- };
- count = mkOption {
- type = types.nullOr types.int;
- default = null;
- description = ''
- Number of worker instances to run
- '';
- };
- includes = mkOption {
- type = types.listOf types.str;
- default = [];
- description = ''
- List of files to include in configuration
- '';
- };
- extraConfig = mkOption {
- type = types.lines;
- default = "";
- description = "Additional entries to put verbatim into worker section of rspamd config file.";
- };
- };
- config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") {
- type = mkDefault name;
- includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
- bindSockets =
- let
- unixSocket = name: {
- mode = "0660";
- socket = "/run/rspamd/${name}.sock";
- owner = cfg.user;
- group = cfg.group;
- };
- in mkDefault (if name == "normal" then [(unixSocket "rspamd")]
- else if name == "controller" then [ "localhost:11334" ]
- else if name == "rspamd_proxy" then [ (unixSocket "proxy") ]
- else [] );
- };
- };
-
- isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
-
- mkBindSockets = enabled: socks: concatStringsSep "\n "
- (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
-
- rspamdConfFile = pkgs.writeText "rspamd.conf"
- ''
- .include "$CONFDIR/common.conf"
-
- options {
- pidfile = "$RUNDIR/rspamd.pid";
- .include "$CONFDIR/options.inc"
- .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
- .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
- }
-
- logging {
- type = "syslog";
- .include "$CONFDIR/logging.inc"
- .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
- .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
- }
-
- ${concatStringsSep "\n" (mapAttrsToList (name: value: let
- includeName = if name == "rspamd_proxy" then "proxy" else name;
- tryOverride = if value.extraConfig == "" then "true" else "false";
- in ''
- worker "${value.type}" {
- type = "${value.type}";
- ${optionalString (value.enable != null)
- "enabled = ${if value.enable != false then "yes" else "no"};"}
- ${mkBindSockets value.enable value.bindSockets}
- ${optionalString (value.count != null) "count = ${toString value.count};"}
- ${concatStringsSep "\n " (map (each: ".include \"${each}\"") value.includes)}
- .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
- .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
- }
- '') cfg.workers)}
-
- ${optionalString (cfg.extraConfig != "") ''
- .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
- ''}
- '';
-
- filterFiles = files: filterAttrs (n: v: v.enable) files;
- rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
- (mapAttrsToList (name: file: { name = "local.d/${name}"; path = file.source; }) (filterFiles cfg.locals)) ++
- (mapAttrsToList (name: file: { name = "override.d/${name}"; path = file.source; }) (filterFiles cfg.overrides)) ++
- (optional (cfg.localLuaRules != null) { name = "rspamd.local.lua"; path = cfg.localLuaRules; }) ++
- [ { name = "rspamd.conf"; path = rspamdConfFile; } ]
- );
-
- configFileModule = prefix: { name, config, ... }: {
- options = {
- enable = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether this file ${prefix} should be generated. This
- option allows specific ${prefix} files to be disabled.
- '';
- };
-
- text = mkOption {
- default = null;
- type = types.nullOr types.lines;
- description = "Text of the file.";
- };
-
- source = mkOption {
- type = types.path;
- description = "Path of the source file.";
- };
- };
- config = {
- source = mkIf (config.text != null) (
- let name' = "rspamd-${prefix}-" + baseNameOf name;
- in mkDefault (pkgs.writeText name' config.text));
- };
- };
-
- configOverrides =
- (mapAttrs' (n: v: nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
- text = v.extraConfig;
- })
- (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
- // (if cfg.extraConfig == "" then {} else {
- "extra-config.inc".text = cfg.extraConfig;
- });
-in
-
-{
-
- ###### interface
-
- options = {
-
- services.rspamd-upstream = {
-
- enable = mkEnableOption "rspamd, the Rapid spam filtering system";
-
- debug = mkOption {
- type = types.bool;
- default = false;
- description = "Whether to run the rspamd daemon in debug mode.";
- };
-
- locals = mkOption {
- type = with types; attrsOf (submodule (configFileModule "locals"));
- default = {};
- description = ''
- Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
- '';
- example = literalExample ''
- { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
- "arc.conf".text = "allow_envfrom_empty = true;";
- }
- '';
- };
-
- overrides = mkOption {
- type = with types; attrsOf (submodule (configFileModule "overrides"));
- default = {};
- description = ''
- Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
- '';
- example = literalExample ''
- { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
- "arc.conf".text = "allow_envfrom_empty = true;";
- }
- '';
- };
-
- localLuaRules = mkOption {
- default = null;
- type = types.nullOr types.path;
- description = ''
- Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local
- rules written in Lua
- '';
- };
-
- workers = mkOption {
- type = with types; attrsOf (submodule workerOpts);
- description = ''
- Attribute set of workers to start.
- '';
- default = {
- normal = {};
- controller = {};
- };
- example = literalExample ''
- {
- normal = {
- includes = [ "$CONFDIR/worker-normal.inc" ];
- bindSockets = [{
- socket = "/run/rspamd/rspamd.sock";
- mode = "0660";
- owner = "${cfg.user}";
- group = "${cfg.group}";
- }];
- };
- controller = {
- includes = [ "$CONFDIR/worker-controller.inc" ];
- bindSockets = [ "[::1]:11334" ];
- };
- }
- '';
- };
-
- extraConfig = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Extra configuration to add at the end of the rspamd configuration
- file.
- '';
- };
-
- user = mkOption {
- type = types.string;
- default = "rspamd";
- description = ''
- User to use when no root privileges are required.
- '';
- };
-
- group = mkOption {
- type = types.string;
- default = "rspamd";
- description = ''
- Group to use when no root privileges are required.
- '';
- };
-
- postfix = {
- enable = mkOption {
- type = types.bool;
- default = false;
- description = "Add rspamd milter to postfix main.conf";
- };
-
- config = mkOption {
- type = with types; attrsOf (either bool (either str (listOf str)));
- description = ''
- Addon to postfix configuration
- '';
- default = {
- smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
- non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
- };
- example = {
- smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
- non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
- };
- };
- };
- };
- };
-
-
- ###### implementation
-
- config = mkIf cfg.enable {
- services.rspamd-upstream.overrides = configOverrides;
- services.rspamd-upstream.workers = mkIf cfg.postfix.enable {
- controller = {};
- rspamd_proxy = {
- bindSockets = [ {
- mode = "0660";
- socket = "/run/rspamd/rspamd-milter.sock";
- owner = cfg.user;
- group = postfixCfg.group;
- } ];
- extraConfig = ''
- upstream "local" {
- default = yes; # Self-scan upstreams are always default
- self_scan = yes; # Enable self-scan
- }
- '';
- };
- };
- services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config;
-
- # Allow users to run 'rspamc' and 'rspamadm'.
- environment.systemPackages = [ pkgs.rspamd ];
-
- users.users = singleton {
- name = cfg.user;
- description = "rspamd daemon";
- uid = config.ids.uids.rspamd;
- group = cfg.group;
- };
-
- users.groups = singleton {
- name = cfg.group;
- gid = config.ids.gids.rspamd;
- };
-
- environment.etc."rspamd".source = rspamdDir;
-
- systemd.services.rspamd-upstream = {
- description = "Rspamd Service";
-
- wantedBy = [ "multi-user.target" ];
- after = [ "network.target" ];
- restartTriggers = [ rspamdDir ];
-
- serviceConfig = {
- ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c /etc/rspamd/rspamd.conf -f";
- Restart = "always";
- RuntimeDirectory = "rspamd";
- PrivateTmp = true;
- };
-
- preStart = ''
- ${pkgs.coreutils}/bin/mkdir -p /var/lib/rspamd
- ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd
- '';
- };
- };
- imports = [
- (mkRemovedOptionModule [ "services" "rspamd-upstream" "socketActivation" ]
- "Socket activation never worked correctly and could at this time not be fixed and so was removed")
- (mkRenamedOptionModule [ "services" "rspamd-upstream" "bindSocket" ]
- [ "services" "rspamd-upstream" "workers" "normal" "bindSockets" ])
- (mkRenamedOptionModule [ "services" "rspamd-upstream" "bindUISocket" ]
- [ "services" "rspamd-upstream" "workers" "controller" "bindSockets" ])
- ];
-}
+++ /dev/null
-self: super: {
- shorewall = super.callPackage ./shorewall {};
-}
+++ /dev/null
-self: super: {
- shorewall6 = super.callPackage ./shorewall6 {};
-}
+++ /dev/null
-{ config, lib, pkgs, ... }:
-let
- types = lib.types;
- cfg = config.services.shorewall6;
-in {
- options = {
- services.shorewall6 = {
- enable = lib.mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to enable Shorewall IPv6 Firewall.
- <warning>
- <para>
- Enabling this service WILL disable the existing NixOS
- firewall! Default firewall rules provided by packages are not
- considered at the moment.
- </para>
- </warning>
- '';
- };
- package = lib.mkOption {
- type = types.package;
- default = pkgs.shorewall;
- defaultText = "pkgs.shorewall";
- description = "The shorewall package to use.";
- };
- configs = lib.mkOption {
- type = types.attrsOf types.str;
- default = {};
- description = ''
- This option defines the Shorewall configs.
- The attribute name defines the name of the config,
- and the attribute value defines the content of the config.
- '';
- apply = lib.mapAttrs (name: text: pkgs.writeText "${name}" text);
- };
- };
- };
-
- config = lib.mkIf cfg.enable {
- systemd.services.firewall.enable = false;
- systemd.services.shorewall6 = {
- description = "Shorewall IPv6 Firewall";
- after = [ "ipset.target" ];
- before = [ "network-pre.target" ];
- wants = [ "network-pre.target" ];
- wantedBy = [ "multi-user.target" ];
- reloadIfChanged = true;
- restartTriggers = lib.attrValues cfg.configs;
- serviceConfig = {
- Type = "oneshot";
- RemainAfterExit = "yes";
- ExecStart = "${cfg.package}/bin/shorewall6 start";
- ExecReload = "${cfg.package}/bin/shorewall6 reload";
- ExecStop = "${cfg.package}/bin/shorewall6 stop";
- };
- preStart = ''
- install -D -d -m 750 /var/lib/shorewall6
- install -D -d -m 755 /var/lock/subsys
- touch /var/log/shorewall6.log
- chown 750 /var/log/shorewall6.log
- '';
- };
- environment = {
- etc = lib.mapAttrsToList
- (name: file:
- { source = file;
- target = "shorewall6/${name}";
- })
- cfg.configs;
- systemPackages = [ cfg.package ];
- };
- };
-}
+++ /dev/null
-self: super:
-let lib = super.lib; in
-{
- users-init = super.writeScriptBin "users-init.pl" ''
- #!${self.perl}/bin/perl -e
- print "HELLO";
- '';
-}
+++ /dev/null
-with builtins;
-import (toPath ./. + "/physical/" + getEnv "NIXOPS_DEPLOYMENT" + ".nix")
+++ /dev/null
-# Sourcephile physical network
-{
- /*
- friot = {pkgs, lib, config, ...}: {
- deployment.targetHost = "1.2.3.4";
- deployment.autoLuks = {
- # NOTE: not working on virtualbox deployment
- secretdisk = {
- device = "/dev/sda";
- passphrase = "foobar";
- autoFormat = true;
- cipher = "aes-cbc-essiv:sha256";
- };
- };
- networking.zones = {
- net = {
- iface = null;
- ipv4 = null;
- };
- lan = {
- iface = null;
- ipv4 = null;
- };
- };
- };
- */
- mermet = {pkgs, lib, config, ...}: {
- deployment.targetHost = "mermet.sourcephile.fr";
- };
-}
+++ /dev/null
-{
- network.rollBack = false;
- friot = {pkgs, lib, config, options, ...}:
- let ipv4 = if options.networking.privateIPv4.isDefined
- then config.networking.privateIPv4
- else "X.X.X.X";
- in {
- config = {
- deployment.targetEnv = "virtualbox";
- deployment.virtualbox.headless = true;
- deployment.virtualbox.memorySize = 1024;
- deployment.virtualbox.vcpu = 2;
- deployment.virtualbox.disks.disk1.baseImage = ../../.cache/nixops/virtualbox/nixops.vmdk;
- #deployment.virtualbox.disks.disk1.size = 6024;
- # NOTE: resize not yet supported.
-
- deployment.storeKeysOnMachine = true;
- networking = {
- interfaces."enp0s8" = {
- #macAddress = "00:11:22:33:44:55";
- #ipv4.addresses = [ { address = ipv4; prefixLength = 32; } ];
- ipv6.addresses = [ { address = "fe80::1"; prefixLength = 10; } ];
- };
- zones = {
- net = {
- iface = "enp0s3";
- ipv4 = ipv4;
- };
- lan = {
- iface = "enp0s8";
- ipv4 = ipv4;
- #ipv6 = "fe80::1";
- };
- };
- };
- };
- };
-}
-
-#{
-# vbox =
-# { deployment.targetEnv = "virtualbox"; };
-#
-# machine =
-# { resources, ... }:
-# { deployment.targetEnv = "container";
-# deployment.container.host = resources.machines.vbox;
-# };
-#}
--- /dev/null
+{
+ network = {
+ description = "Sourcephile";
+ #enableRollback = true;
+ };
+
+ defaults = {
+ #imports = [ network/defaults.nix ];
+ };
+
+ #friot = import network/friot.nix;
+ mermet = import network/mermet.nix;
+}
--- /dev/null
+{pkgs, lib, config, ...}:
+{
+ imports = [
+ mermet/configuration.nix
+ (builtins.toPath ./mermet + "/deployment/" + builtins.getEnv "NIXOPS_DEPLOYMENT" + ".nix")
+ ];
+}
-mermet_disk := $(shell sed -ne 's/^device: \(.*\)/\1/p' physical/sfdisk.txt)
+#cwd := $(notdir $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))))
+MERMET_MACHINE ?= apu2e4
+MERMET_HOSTING ?= lab
+mermet_disk := $(shell sed -ne 's/^device: \(.*\)/\1/p' machine/$(MERMET_MACHINE)/sfdisk.txt)
mermet_cipher :=
#mermet_cipher := aes-128-gcm
mermet_autotrim :=
mermet_reservation := 40G
#mermet_channel := $$(nix-env -p /nix/var/nix/profiles/per-user/$$USER/channels -q nixpkgs --no-name --out-path)
-MERMET_PHYSICAL ?= apu2e4
-MERMET_HOSTING ?= lab
+
+echo:
+ echo $(MAKEFILES)
mermet-wipeout: mermet-umount
sudo zpool labelclear -f $(mermet_disk)-part3 || true
mermet-partition:
sudo modprobe zfs
- sudo $$(which sfdisk) $(mermet_disk) <physical/sfdisk.txt
+ sudo $$(which sfdisk) $(mermet_disk) <machine/$(MERMET_MACHINE)/sfdisk.txt
sudo $$(which sgdisk) --randomize-guids $(mermet_disk)
sudo partprobe
LANG="$$LANG" \
LC_CTYPE="$$LC_CTYPE" \
MERMET_HOSTING="$(MERMET_HOSTING)" \
- MERMET_PHYSICAL="$(MERMET_PHYSICAL)" \
+ MERMET_MACHINE="$(MERMET_MACHINE)" \
NIXOS_CONFIG="$$(readlink -e ./configuration.nix)" \
NIX_CONF_DIR="$$NIX_CONF_DIR" \
NIX_PATH="$$NIX_PATH" \
$$(which nixos-install) \
--root /mnt/mermet \
$(if $(mermet_channel),--channel "$(mermet_channel)") \
- --no-root-passwd
+ --no-root-passwd \
+ --show-trace
mermet-umount:
for p in \
--- /dev/null
+# This is the root configuration of the target machine.
+# Usable by nixos-install and used by nixops.
+# It is NOT copied nor usable on the target,
+# only the resulting closure is copied to the target.
+{ pkgs, lib, config, ... }:
+{
+ nixpkgs.overlays = import ../../overlays.nix;
+ imports =
+ [ ../../nixos/defaults.nix
+ ./machine.nix
+ ./hosting.nix
+ ./system.nix
+ ];
+}
--- /dev/null
+{pkgs, lib, config, ...}: {
+ deployment.targetHost = "mermet";
+}
--- /dev/null
+{pkgs, lib, config, ...}: {
+ deployment.targetHost = "1.2.3.4";
+}
--- /dev/null
+with builtins;
+import (toPath ./. + "/machine/" + getEnv "MERMET_MACHINE" + ".nix")
port = 2222;
# dropbear uses key format different from openssh; can be generated by using:
# $ nix-shell -p dropbear --command "dropbearkey -t ecdsa -f /tmp/initrd-ssh-key"
- # WARNINg: this key will be in the NixOS store and the initrd and thus maybe on cleartext storage.
+ # WARNING: this key will be in the NixOS store and the initrd and thus maybe on cleartext storage.
# Unfortunately pass cannot be used here because the key is not a valid Nix string.
- hostECDSAKey = ../../../.sec/dropbear/mermet-ecdsa.key ;
+ hostECDSAKey = ../../../.sec/dropbear/mermet.dropbear-ecdsa.key ;
# public ssh key used for login
authorizedKeys = [
# julm
+ # readFile ../../../.pub/ssh/julm.ssh-rsa.pub
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD5FtR++UPEg/5wFeyb2JSS09idTaDb4tMIRf1yxCsyIJEp5LQMif/fIptDeHoYc55lwy8vnWWN9PJpb6PS8YSaLLFV5tn8esR8Ml+evNCAD52Tdu1kPRXGLCSF5kSVnbAMoxqiNi8vRRKXwAzGgXmIUzDAE4QTsq3EwZM6cBnDx5O79wBIZ9va2TObL52qv+Vpi+QyINuslKKc+Osu92pdwceIGZUcwA6Y8aH6lavaTyDUQdSjMRMTAiXSPRjmHf1q+V7wENXT/TKXuuahN8NnJShX3Qf9hwNEIU46SOENsrRFQ5eYahAmqUIK4GbsERS2KRDxbvSOl7rKh2sauBxyKfkW/gxQ4LAyywxuumpI0pO7XmdINCGWdXS9gD216lcGuH/TC0KboiOVExh65eRIOeEFTec0VJQEqqnFul7u8YNPmbBpLnM+SQ3TAkdQmfasKgPIazFNCAnC8I9hKlGYpLk/Dgi/sVbwFeoOUQcaTBRnWKUCedX4v4kmPIHuHSNPV2C/0770gH2iJ1N1XEO3YDGiixuHHiLlCV8Ko950CoTh1PwDNCd3Qy/jR/QhE2waVPliFwl2+H6IkIxkUO8A9ktLCJUeaZJN3snoV+9hvpT1E2TrEccsTVx5BaGAJCUkvO2XYlEsNceIIitkrbhidjZvfZ4/czGUKoN1wSSpMw== GnuPG pub=F2E027182397AC0775714F2AD15AF7F467E8299B sub=7819E44BAEEDE91683811BB00E1AAADBE227DDAA"
];
};
--- /dev/null
+{ config, lib, pkgs, ... }:
+{
+ imports =
+ [ ./system/zfs.nix
+ ];
+
+ # This value determines the NixOS release with which your system is to be
+ # compatible, in order to avoid breaking some software such as database
+ # servers. You should change this only after NixOS release notes say you should.
+ system.stateVersion = "19.09"; # Did you read the comment?
+
+ # Clean /tmp automatically on boot.
+ boot.cleanTmpDir = true;
+
+ networking = {
+ hostName = "mermet";
+ domain = "sourcephile.fr";
+ };
+
+ users = {
+ mutableUsers = false;
+ users = {
+ root = {
+ initialPassword = "toor";
+ password = "toor";
+ openssh.authorizedKeys.keys = [
+ # julm
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD5FtR++UPEg/5wFeyb2JSS09idTaDb4tMIRf1yxCsyIJEp5LQMif/fIptDeHoYc55lwy8vnWWN9PJpb6PS8YSaLLFV5tn8esR8Ml+evNCAD52Tdu1kPRXGLCSF5kSVnbAMoxqiNi8vRRKXwAzGgXmIUzDAE4QTsq3EwZM6cBnDx5O79wBIZ9va2TObL52qv+Vpi+QyINuslKKc+Osu92pdwceIGZUcwA6Y8aH6lavaTyDUQdSjMRMTAiXSPRjmHf1q+V7wENXT/TKXuuahN8NnJShX3Qf9hwNEIU46SOENsrRFQ5eYahAmqUIK4GbsERS2KRDxbvSOl7rKh2sauBxyKfkW/gxQ4LAyywxuumpI0pO7XmdINCGWdXS9gD216lcGuH/TC0KboiOVExh65eRIOeEFTec0VJQEqqnFul7u8YNPmbBpLnM+SQ3TAkdQmfasKgPIazFNCAnC8I9hKlGYpLk/Dgi/sVbwFeoOUQcaTBRnWKUCedX4v4kmPIHuHSNPV2C/0770gH2iJ1N1XEO3YDGiixuHHiLlCV8Ko950CoTh1PwDNCd3Qy/jR/QhE2waVPliFwl2+H6IkIxkUO8A9ktLCJUeaZJN3snoV+9hvpT1E2TrEccsTVx5BaGAJCUkvO2XYlEsNceIIitkrbhidjZvfZ4/czGUKoN1wSSpMw== GnuPG pub=F2E027182397AC0775714F2AD15AF7F467E8299B sub=7819E44BAEEDE91683811BB00E1AAADBE227DDAA"
+ ];
+ };
+ };
+ groups = {
+ };
+ };
+
+ environment = {
+ systemPackages = with pkgs; [
+ cryptsetup
+ zfs
+ ];
+ };
+}
{
imports = [];
+
boot.supportedFilesystems = [ "zfs" ];
# The 32-bit host id of the machine, formatted as 8 hexadecimal characters.
{ pkgs, lib, config, ... }:
{
+imports = [
+ ./modules.nix
+];
+config = {
nix = {
autoOptimiseStore = true;
extraOptions = ''
};
#overlays = import ../overlays.nix;
};
- users = {
- mutableUsers = false;
- users = {
- root = {
- initialPassword = "toor";
- password = "toor";
- openssh.authorizedKeys.keys = [
- # julm
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD5FtR++UPEg/5wFeyb2JSS09idTaDb4tMIRf1yxCsyIJEp5LQMif/fIptDeHoYc55lwy8vnWWN9PJpb6PS8YSaLLFV5tn8esR8Ml+evNCAD52Tdu1kPRXGLCSF5kSVnbAMoxqiNi8vRRKXwAzGgXmIUzDAE4QTsq3EwZM6cBnDx5O79wBIZ9va2TObL52qv+Vpi+QyINuslKKc+Osu92pdwceIGZUcwA6Y8aH6lavaTyDUQdSjMRMTAiXSPRjmHf1q+V7wENXT/TKXuuahN8NnJShX3Qf9hwNEIU46SOENsrRFQ5eYahAmqUIK4GbsERS2KRDxbvSOl7rKh2sauBxyKfkW/gxQ4LAyywxuumpI0pO7XmdINCGWdXS9gD216lcGuH/TC0KboiOVExh65eRIOeEFTec0VJQEqqnFul7u8YNPmbBpLnM+SQ3TAkdQmfasKgPIazFNCAnC8I9hKlGYpLk/Dgi/sVbwFeoOUQcaTBRnWKUCedX4v4kmPIHuHSNPV2C/0770gH2iJ1N1XEO3YDGiixuHHiLlCV8Ko950CoTh1PwDNCd3Qy/jR/QhE2waVPliFwl2+H6IkIxkUO8A9ktLCJUeaZJN3snoV+9hvpT1E2TrEccsTVx5BaGAJCUkvO2XYlEsNceIIitkrbhidjZvfZ4/czGUKoN1wSSpMw== GnuPG pub=F2E027182397AC0775714F2AD15AF7F467E8299B sub=7819E44BAEEDE91683811BB00E1AAADBE227DDAA"
- ];
- };
- };
- groups = {
- };
- };
documentation.nixos = {
enable = false; # NOTE: useless on this machine, and CPU intensive.
environment = {
systemPackages = with pkgs; [
- htop
- tree
- vim
- tcpdump
- cryptsetup
- multitail
+ binutils
dnsutils
+ htop
inetutils
- binutils
mailutils
+ multitail
ncdu
+ tcpdump
tmux
- socat
+ tree
+ vim
which
- zfs
];
- etc."inputrc".text = lib.readFile etc/inputrc;
+ etc."inputrc".text = lib.readFile defaults/readline/inputrc;
};
programs = {
interactiveShellInit = ''
bind '"\e[A":history-search-backward'
bind '"\e[B":history-search-forward'
-
+
# Ignore duplicate commands, ignore commands starting with a space
export HISTCONTROL=erasedups:ignorespace
export HISTSIZE=42000
+
# Append to the history instead of overwriting (good for multiple connections)
shopt -s histappend
'';
l = "ls -alh";
ll = "ls -l";
ls = "ls --color=tty";
-
+
s="sudo systemctl";
s-u="systemctl --user";
-
+
nixos-clean="sudo nix-collect-garbage -d";
nixos-history="sudo nix-env --list-generations --profile /nix/var/nix/profiles/system";
nixos-rollback="sudo nixos-rebuild switch --rollback";
};
mtr.enable = true;
};
-
- # This value determines the NixOS release with which your system is to be
- # compatible, in order to avoid breaking some software such as database
- # servers. You should change this only after NixOS release notes say you
- # should.
- system.stateVersion = "19.09"; # Did you read the comment?
+};
}
--- /dev/null
+{ pkgs, lib, config, ... }:
+# NOTE: list explicitely the modules loaded by defaults.nix,
+# its clearer, safer and more flexible if not quicker.
+{ imports = [
+ modules/services/mail/dovecot.nix
+ modules/services/networking/domains.nix
+ modules/services/networking/shorewall.nix
+ #modules/services/security/x509.nix
+ ];
+}
+
+/*
+# NOTE: this modules.nix file is put in the imports of the configuration.nix,
+# but using the lib.findFiles defined in the nixpkgs.overlays leads to an infinite recursion,
+# hence it is redefined here:
+let findFiles = pattern:
+ with builtins;
+ let go = curr:
+ let dir = readDir curr; in
+ let files = lib.filterAttrs (name: type:
+ type == "regular" &&
+ match pattern name != null) dir; in
+ let dirs = lib.filterAttrs (name: type: type == "directory") dir; in
+ map (name: "${curr}/${name}") (attrNames files) ++
+ lib.concatMap (name: go "${curr}/${name}") (attrNames dirs)
+ ;
+ in root: go (toPath root);
+in
+{
+ imports = findFiles ".*\\.nix" ./modules;
+}
+*/
--- /dev/null
+{pkgs, lib, config, system, ...}:
+let inherit (builtins) toString toFile attrNames;
+ inherit (lib) types;
+ inherit (config.services) dovecot2 postfix x509;
+ unlines = lib.concatStringsSep "\n";
+ when = x: y: if x == null then "" else y;
+ extSep = postfix.recipientDelimiter;
+ dirSep = extSep;
+ libDir = "/var/lib/dovecot";
+ mailDir = "${libDir}/mail";
+ sieveDir = "${libDir}/sieve";
+ authDir = "${libDir}/auth";
+ authUser = dovecot2.mailUser; # TODO: php_roundcube
+ authGroup = dovecot2.mailGroup; # TODO: php_roundcube
+ escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
+ || "0"<=c && c<="9"
+ || c=="-"
+ then c else "_");
+in
+{
+
+options.services.dovecot2 = {
+ domains = lib.mkOption {
+ default = {};
+ type = types.attrsOf (types.submodule ({domain, ...}: {
+ #config.domain = lib.mkDefault domain;
+ options = {
+ accounts = lib.mkOption {
+ type = types.attrsOf (types.submodule ({account, ...}: {
+ options = {
+ password = lib.mkOption {
+ type = types.str;
+ example = "{SSHA512}uyjL1KYx4z7HpfNvnKzuVxpMLD2KVueGGBvOcj7AF1EZCTVhT++IIKUVOC4xpZtWdqVD0OVmZqgYr2qpn/3t3Aj4oU0=";
+ description = ''Password.
+ Use: `doveadm pw -s SSHA512 -p "$password"`
+ '';
+ };
+ aliases = lib.mkOption {
+ type = with types; listOf types.str;
+ example = [ "abuse@${config.networking.domain}" ];
+ default = [];
+ description = ''Aliases of this account.'';
+ };
+ quota = lib.mkOption {
+ type = with types; nullOr types.str;
+ default = null;
+ example = "2G";
+ description = ''
+ Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
+ obvious meaning. Leave blank for the standard quota `100G`.
+ '';
+ };
+ sieves = lib.mkOption {
+ type = with types; attrsOf str;
+ default = { main = ''
+ require ["include"];
+
+ #include :personal "roundcube";
+ include :global "spam";
+ include :global "list";
+ include :global "extension";
+ '';
+ };
+ };
+ groups = lib.mkOption {
+ type = with types; listOf str;
+ default = [];
+ };
+ };
+ }));
+ };
+ };
+ }));
+ };
+ debug = lib.mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable verbose logging or not in mail related services.
+ '';
+ };
+};
+
+# config = lib.mkIf dovecot2.enable {
+# environment.etc."nginx/site.d/autoconfig.conf".source =
+# let servers = lib.concatMapStringsSep " "
+# (dom: "autoconfig.${dom}")
+# (attrNames dovecot2.domains);
+# autoconfigSite = pkgs.writeTextFile {
+# name = "autoconfig";
+# destination = "/mail/config-v1.1.xml";
+# text = ''
+# <?xml version="1.0"?>
+# <clientConfig version="1.1">
+# <emailProvider id="%EMAILDOMAIN%">
+# <!-- <displayName></displayName> -->
+# <!-- <displayShortName></displayShortName> -->
+# <domain>%EMAILDOMAIN%</domain>
+# <incomingServer type="imap">
+# <hostname>imap.%EMAILDOMAIN%</hostname>
+# <port>993</port>
+# <socketType>SSL</socketType>
+# <username>%EMAILADDRESS%</username>
+# <authentication>password-cleartext</authentication>
+# </incomingServer>
+# <incomingServer type="pop3">
+# <hostname>pop.%EMAILDOMAIN%</hostname>
+# <port>995</port>
+# <socketType>SSL</socketType>
+# <username>%EMAILADDRESS%</username>
+# <authentication>password-cleartext</authentication>
+# <pop3>
+# <leaveMessagesOnServer>false</leaveMessagesOnServer>
+# <downloadOnBiff>true</downloadOnBiff>
+# </pop3>
+# </incomingServer>
+# <outgoingServer type="smtp">
+# <hostname>smtp.%EMAILDOMAIN%</hostname>
+# <port>465</port>
+# <socketType>SSL</socketType> <!-- see above -->
+# <username>%EMAILADDRESS%</username> <!-- if smtp-auth -->
+# <authentication>password-cleartext</authentication>
+# <!-- <restriction>client-IP-address</restriction> -->
+# <addThisServer>true</addThisServer>
+# <useGlobalPreferredServer>false</useGlobalPreferredServer>
+# </outgoingServer>
+# </emailProvider>
+# <!-- <clientConfigUpdate url="https://www.example.com/config/mozilla.xml" /> -->
+# </clientConfig>
+# '';
+# };
+# in
+# pkgs.writeText "autoconfig.conf" ''
+# server {
+# listen 80;
+# server_name ${servers};
+# root ${autoconfigSite};
+# access_log off;
+# log_not_found off;
+# }
+# server {
+# listen 443 ssl http2;
+# ssl on;
+# server_name ${servers};
+# root ${autoconfigSite};
+# access_log off;
+# log_not_found off;
+# }
+# '';
+# #services.postfix.mapFiles."transport-dovecot" =
+# # toFile "transport-dovecot"
+# # (unlines
+# # (lib.mapAttrsToList
+# # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
+# # dovecot2.domains));
+# systemd.services.dovecot2.after = [ "postfix.service" ];
+# #users.extraUsers = [
+# # { name = "dovecot";
+# # uid = config.ids.uids.dovecot2;
+# # description = "Dovecot user";
+# # group = dovecot2.group;
+# # }
+# #];
+# users.extraGroups = lib.mapAttrs
+# (domain: {...}:
+# { name = escapeGroup "${dovecot2.mailGroup}-${domain}";
+# })
+# dovecot2.domains;
+# systemd.services.dovecot2.preStart =
+# let sieveList =
+# pkgs.writeText "list.sieve" ''
+# require
+# [ "date"
+# , "fileinto"
+# , "mailbox"
+# , "variables"
+# ];
+#
+# if currentdate :matches "year" "*" { set "year" "''${1}"; }
+# if currentdate :matches "month" "*" { set "month" "''${1}"; }
+#
+# if exists "List-ID" {
+# if header :matches "List-ID" "*<*.*.*.*>*" {
+# set "list" "''${2}";
+# set "domain" "''${4}";
+# }
+# elsif header :matches "List-ID" "*<*.*.*>*" {
+# set "list" "''${2}";
+# set "domain" "''${3}";
+# }
+# fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
+# stop;
+# }
+# '';
+# sieveSpam =
+# pkgs.writeText "spam.sieve" ''
+# require
+# [ "imap4flags"
+# ];
+#
+# if header :contains "X-Spam-Level" "***" {
+# addflag "Junk";
+# }
+# '';
+# sieveExtension =
+# pkgs.writeText "extension.sieve" ''
+# require
+# [ "envelope"
+# , "fileinto"
+# , "mailbox"
+# , "subaddress"
+# , "variables"
+# ];
+# if envelope :matches :detail "TO" "*" {
+# set "extension" "''${1}";
+# }
+# if not string :is "''${extension}" "" {
+# fileinto :create "Plus+''${extension}";
+# stop;
+# }
+# '';
+# dovecot-virtual =
+# pkgs.writeText "dovecot-virtual" ''
+# all
+# all+*
+# all
+# '';
+# in ''
+# # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
+# # The sticky bit is to allow the acl.db{.lock,} done by dovecot
+# install -D -d -m 2771 \
+# -o ${dovecot2.mailUser} \
+# -g ${dovecot2.mailGroup} \
+# ${mailDir}
+#
+# # Install global sieves
+# install -D -d -m 0755 \
+# -o root \
+# -g root \
+# ${sieveDir} \
+# ${sieveDir}/after.d \
+# ${sieveDir}/before.d \
+# ${sieveDir}/global.d
+# ln -fns ${sieveList} ${sieveDir}/global.d/list.sieve
+# ln -fns ${sieveExtension} ${sieveDir}/global.d/extension.sieve
+# ln -fns ${sieveSpam} ${sieveDir}/global.d/spam.sieve
+# for f in ${sieveDir}/*/*.sieve
+# do ${pkgs.dovecot_pigeonhole}/bin/sievec $f
+# done
+#
+# # Install pop3 Inbox
+# install -D -m 0644 \
+# -o root \
+# -g root \
+# ${dovecot-virtual} \
+# ${libDir}/pop3/INBOX/dovecot-virtual
+# ''
+# + ''
+# # Install domains
+# new_uid=5000
+# ''
+# + unlines (lib.mapAttrsToList (domain: {accounts, ...}:
+# let domainGroup = escapeGroup "${dovecot2.mailGroup}-${domain}"; in
+# ''
+# install -D -d -m 1770 \
+# -o ${dovecot2.mailUser} \
+# -g ${domainGroup} \
+# ${mailDir}/${domain} \
+# ${libDir}/control/${domain} \
+# ${libDir}/index/${domain}
+# install -D -d -m 1770 \
+# -o ${dovecot2.mailUser} \
+# -g ${authGroup} \
+# ${libDir}/auth \
+# ${libDir}/auth/${domain}
+# dir_passwd=${libDir}/auth/${domain}
+# old_passwd=$dir_passwd/passwd
+# new_passwd=$(TMPDIR= mktemp --tmpdir=$dir_passwd -t passwd.XXXXXXXX.tmp)
+#
+# # Install users
+# ''
+# + unlines (lib.mapAttrsToList (user: acct: ''
+# home=${mailDir}/${domain}/${user}
+# gecos=
+# shell=/run/current-system/sw/bin/nologin
+# if test -e $home
+# then
+# uid=$(stat -c %u $home)
+# gid=$(stat -c %g $home)
+# fi
+# [ "''${uid:+set}" ] || {
+# while test exists = "$(find $(dirname $home) -mindepth 1 -maxdepth 1 -uid $new_uid -printf exists -quit)"
+# do new_uid=$((new_uid + 1))
+# done
+# uid=$new_uid
+# gid=$new_uid
+# }
+# install -D -d -o $uid -g $gid -m 2770 $home $home/Maildir
+# install -d -o $uid -g $gid -m 0700 $home/sieve
+# ''
+# + unlines (lib.mapAttrsToList
+# (n: v: ''
+# install -D -m 640 -o $uid -g $gid \
+# ${pkgs.writeText "${n}.sieve" v} \
+# $home/sieve/${n}.sieve
+# ${pkgs.dovecot_pigeonhole}/bin/sievec \
+# $home/sieve/${n}.sieve
+# '')
+# acct.sieves)
+# + ''
+# mail_access_groups=${lib.concatStringsSep "," ([domainGroup] ++ acct.groups)}
+# quota=${if lib.isString acct.quota
+# then ''"userdb_quota_rule=*:storage=${acct.quota}"''
+# else ""}
+# extra_fields="userdb_uid=$uid userdb_gid=$gid userdb_mail_access_groups=$mail_access_groups $quota"
+# #test ! -e $old_passwd || {
+# # # Preserve password changed by another mechanism, eg. roundcube.
+# # # But this also does not overwrite any old password set by this config.
+# # pass="$(sed -ne "s/^${user}:\([^:]*\):.*/\1/p" $old_passwd)"
+# #}
+# [ "''${pass:+set}" ] || {
+# pass=${lib.escapeShellArg acct.password}
+# }
+# printf '%s\n' >>$new_passwd \
+# "${user}:$pass:$uid:$gid:$gecos:$home:$shell:$extra_fields"
+# '') accounts)
+# + ''
+# install -o ${authUser} -g ${authGroup} -m 0640 $new_passwd $old_passwd
+# rm $new_passwd
+# ''
+# ) dovecot2.domains);
+# services.dovecot2 = {
+# enable = true;
+# mailUser = "dovemail";
+# mailGroup = "dovemail";
+# modules = [
+# #pkgs.dovecot_antispam
+# pkgs.dovecot_pigeonhole
+# ];
+# # ${lib.concatMapStringsSep "\n"
+# # (dom: ''
+# # local_name imap.${dom} {
+# # #ssl_ca = <''${caPath}
+# # ssl_cert = <${x509.cert dom}
+# # ssl_key = <${x509.key dom}
+# # }
+# # local_name pop.${dom} {
+# # #ssl_ca = <''${caPath}
+# # ssl_cert = <${x509.cert dom}
+# # ssl_key = <${x509.key dom}
+# # }
+# # '')
+# # dovecot2.domains
+# # }
+#
+# configFile = toString (pkgs.writeText "dovecot.conf" ''
+# passdb {
+# driver = passwd-file
+# args = scheme=crypt username_format=%n ${authDir}/%d/passwd
+# }
+# userdb {
+# driver = prefetch
+# }
+# userdb {
+# # NOTE: this userdb is only used by lda.
+# driver = passwd-file
+# args = username_format=%n ${authDir}/%d/passwd
+# #default_fields = home=${mailDir}/%d/%n
+# }
+# mail_home = ${mailDir}/%d/%n
+# auth_mechanisms = plain login
+# # postfix does not supply a client cert.
+# auth_ssl_require_client_cert = no
+# auth_ssl_username_from_cert = yes
+# auth_verbose = yes
+# ${lib.optionalString dovecot2.debug ''
+# auth_debug = yes
+# mail_debug = yes
+# verbose_ssl = yes
+# ''}
+# default_internal_user = ${dovecot2.user}
+# default_internal_group = ${dovecot2.group}
+# disable_plaintext_auth = yes
+# first_valid_uid = 1000
+# lda_mailbox_autocreate = yes
+# lda_mailbox_autosubscribe = yes
+# listen = *
+# log_timestamp = "%Y-%m-%d %H:%M:%S "
+# # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
+# # SEE: http://wiki2.dovecot.org/Quota/FS
+# mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n
+# namespace inbox {
+# # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
+# inbox = yes
+# location =
+# list = yes
+# prefix =
+# separator = ${dirSep}
+# }
+# namespace {
+# #list = children
+# list = yes
+# location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%%n:CONTROL=${libDir}/control/%d/%n/Shared/%%n
+# prefix = Partages+%%n+
+# separator = ${dirSep}
+# subscriptions = yes
+# type = shared
+# }
+# mail_plugins = $mail_plugins acl quota virtual
+# #mail_uid = ${dovecot2.mailUser}
+# #mail_gid = ${dovecot2.mailGroup}
+# #mail_privileged_group = mail
+# #mail_access_groups =
+# plugin {
+# acl = vfile:/etc/dovecot/acl/global.d
+# acl_anyone = allow
+# acl_shared_dict = file:${mailDir}/%d/acl.db
+# ##antispam_allow_append_to_spam = yes
+# # # NOTE: pour offlineimap
+# #antispam_backend = pipe
+# ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm
+# #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm
+# #antispam_crm_binary = /usr/bin/crm
+# #antispam_debug_target = syslog
+# ##antispam_crm_env = HOME=%h;USER=%u
+# #antispam_ham_keywords = NonJunk
+# #antispam_pipe_program = /usr/bin/crm
+# #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force
+# #antispam_pipe_program_notspam_arg = --learnnonspam
+# #antispam_pipe_program_spam_arg = --learnspam
+# #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam
+# #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam
+# #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp
+# #antispam_signature = X-CRM114-CacheID
+# #antispam_signature_missing = move
+# #antispam_spam = Junk
+# #antispam_spam_keywords = Junk
+# #antispam_trash = Trash
+# #antispam_unsure = Unsure
+# #antispam_verbose_debug = 0
+# quota = maildir:User quota
+# quota_rule = *:storage=256M
+# quota_rule2 = Trash:storage=+64M
+# recipient_delimiter = ${extSep}
+# sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
+# #sieve_default = file:${mailDir}/%u/default.sieve
+# #sieve_default_name = default
+# sieve_after = ${sieveDir}/after.d/
+# sieve_before = ${sieveDir}/before.d/
+# sieve_dir = ${mailDir}/%d/%n/sieve/
+# #sieve_extensions = +spamtest +spamtestplus
+# sieve_global_dir = ${sieveDir}/global.d/
+# sieve_max_script_size = 1M
+# sieve_quota_max_scripts = 0
+# sieve_quota_max_storage = 10M
+# sieve_spamtest_max_value = 10
+# sieve_spamtest_status_header = X-Spam-Score
+# sieve_spamtest_status_type = strlen
+# sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
+# }
+# protocol imap {
+# #mail_max_userip_connections = 10
+# mail_plugins = $mail_plugins imap_acl imap_quota # antispam
+# namespace inbox {
+# inbox = yes
+# location =
+# list = yes
+# mailbox Drafts {
+# special_use = \Drafts
+# }
+# mailbox Junk {
+# special_use = \Junk
+# }
+# mailbox Sent {
+# special_use = \Sent
+# }
+# mailbox "Sent Messages" {
+# special_use = \Sent
+# }
+# mailbox Trash {
+# special_use = \Trash
+# }
+# prefix =
+# separator = ${dirSep}
+# }
+# }
+# protocol lda {
+# auth_socket_path = /var/run/dovecot/auth-userdb
+# hostname = ${machine.fqdn}
+# info_log_path =
+# log_path =
+# mail_plugins = $mail_plugins sieve
+# namespace inbox {
+# inbox = yes
+# location =
+# list = yes
+# prefix =
+# separator = ${dirSep}
+# }
+# postmaster_address = postmaster${extSep}dovecot${extSep}lda@${machine.fqdn}
+# syslog_facility = mail
+# }
+# protocol lmtp {
+# #info_log_path = /tmp/dovecot-lmtp.log
+# mail_plugins = $mail_plugins sieve
+# namespace inbox {
+# inbox = yes
+# location =
+# list = yes
+# prefix =
+# separator = ${dirSep}
+# }
+# postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${machine.fqdn}
+# }
+# protocol pop3 {
+# #mail_max_userip_connections = 10
+# # Used by ${libDir}/pop3/INBOX/dovecot-virtual
+# namespace all {
+# hidden = yes
+# list = no
+# location =
+# prefix = all+
+# separator = ${dirSep}
+# }
+# # Virtual namespace for the virtual INBOX.
+# # Use a global directory for dovecot-virtual files.
+# namespace inbox {
+# inbox = yes
+# hidden = yes
+# list = no
+# location = virtual:${libDir}/pop3:INDEX=${libDir}/index/%d/%n/POP3:LAYOUT=fs
+# prefix = pop3+
+# separator = ${dirSep}
+# }
+# pop3_client_workarounds =
+# pop3_fast_size_lookups = yes
+# pop3_lock_session = yes
+# pop3_no_flag_updates = yes
+# # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
+# pop3_uidl_format = %g
+# }
+# protocol sieve {
+# #mail_max_userip_connections = 10
+# #managesieve_implementation_string = Dovecot Pigeonhole
+# managesieve_max_compile_errors = 5
+# #managesieve_max_line_length = 65536
+# #managesieve_notify_capability = mailto
+# #managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave
+# }
+# protocols = imap lmtp pop3 sieve
+# service lmtp {
+# #executable = lmtp -L
+# process_min_avail = 2
+# unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
+# user = ${postfix.user}
+# group = ${postfix.group}
+# mode = 0600
+# }
+# #user = mail
+# }
+# service auth {
+# user = root
+# unix_listener auth-userdb {
+# user = ${dovecot2.user}
+# group = ${dovecot2.group}
+# mode = 0660
+# }
+# unix_listener /var/lib/postfix/queue/private/auth {
+# user = ${postfix.user}
+# group = ${postfix.group}
+# mode = 0660
+# }
+# }
+# service imap {
+# # Most of the memory goes to mmap()ing files.
+# # You may need to increase this limit if you have huge mailboxes.
+# #vsz_limit =
+# process_limit = 1024
+# }
+# service imap-login {
+# #inet_listener imap {
+# # address = 127.0.0.1
+# # port = 143
+# # ssl = no
+# # }
+# inet_listener imaps {
+# port = 993
+# ssl = yes
+# }
+# }
+# service pop3 {
+# process_limit = 1024
+# }
+# service pop3-login {
+# inet_listener pop3s {
+# port = 995
+# ssl = yes
+# }
+# }
+# ssl = required
+# #ssl_ca = <''${caPath}
+# ssl_cert = <${x509.cert}
+# # Only with dovecot >= 2.3
+# #ssl_dh = <${x509.dir}/dh.pem
+# ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL
+# ssl_key = <${x509.key}
+# #ssl_verify_client_cert = yes
+# '');
+# };
+# };
+
+}
+
+ #loginAccounts = lib.mkOption {
+ # type = types.loaOf (types.submodule ({name, ...}: {
+ # config.name = lib.mkDefault name;
+ # options = {
+ # name = lib.mkOption {
+ # type = types.str;
+ # example = "user1@example.coop";
+ # description = "Username";
+ # };
+ # password = lib.mkOption {
+ # type = types.str;
+ # example = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/";
+ # description = ''
+ # Hashed password. Use `mkpasswd` as follows
+ #
+ # ```
+ # mkpasswd -m sha-512 "super secret password"
+ # ```
+ # '';
+ # };
+ # aliases = lib.mkOption {
+ # type = with types; listOf types.str;
+ # example = ["abuse@example.coop" "postmaster@example.coop"];
+ # default = [];
+ # description = ''
+ # A list of aliases of this login account.
+ # '';
+ # };
+ # catchAll = lib.mkOption {
+ # type = with types; listOf (enum dovecot2.domains);
+ # example = ["example.coop" "example2.coop"];
+ # default = [];
+ # description = ''
+ # For which domains should this account act as a catch all?
+ # '';
+ # };
+ # sieveScript = lib.mkOption {
+ # type = with types; nullOr lines;
+ # default = null;
+ # example = ''
+ # require ["fileinto", "mailbox"];
+ #
+ # if address :is "from" "notifications@github.coop" {
+ # fileinto :create "GitHub";
+ # stop;
+ # }
+ #
+ # # This must be the last rule, it will check if list-id is set, and
+ # # file the message into the Lists folder for further investigation
+ # elsif header :matches "list-id" "<?*>" {
+ # fileinto :create "Lists";
+ # stop;
+ # }
+ # '';
+ # description = ''
+ # Per-user sieve script.
+ # '';
+ # };
+ # };
+ # }));
+ # example = {
+ # user1 = {
+ # password = "$6$vy7SOr8Cg$l1QwFSkK6YR72ASUBmMmAqg51Fqu96mPZrKzADh5aI7bEOtTzDger9JSVnUhQ/DiqhxO1N55BUikE01mWvBee1";
+ # };
+ # user2 = {
+ # password = "$6$gmebVgh5iJ9IyAJ5$i2aEvWZqS3iUq7mxSAhs5F./uUvQ4zmqFAdH3fsGiwabekdP.On8HCzpDCRS2nzzYNQ8ZisqyIwXf9R2rkC531";
+ # };
+ # };
+ # description = ''
+ # The login account of the domain. Every account is mapped to a unix user,
+ # e.g. `user1@example.coop`. To generate the passwords use `mkpasswd` as
+ # follows
+ #
+ # ```
+ # mkpasswd -m sha-512 "super secret password"
+ # ```
+ # '';
+ # default = {};
+ #};
+ #extraVirtualAliases = lib.mkOption {
+ # type = with types; attrsOf (enum (builtins.attrNames machine.mail.loginAccounts));
+ # example = {
+ # "info@example.coop" = "user1@example.coop";
+ # "postmaster@example.coop" = "user1@example.coop";
+ # "abuse@example.coop" = "user1@example.coop";
+ # };
+ # default = {};
+ # description = ''
+ # Virtual Aliases. A virtual alias `"info@example2.coop" = "user1@example.coop"`
+ # means that all mail to `info@example2.coop` is forwarded to `user1@example.coop`.
+ # Note that it is expected that `postmaster@example.coop` and `abuse@example.coop` is
+ # forwarded to some valid email address. (Alternatively you can create login
+ # accounts for `postmaster` and (or) `abuse`). Furthermore, it also allows
+ # the user `user1@example.coop` to send emails as `info@example2.coop`.
+ # '';
+ #};
+
-{pkgs, lib, config, system, ...}: {
-options = {
- enable = lib.mkEnableOption "friot";
- networking.domainBase = lib.mkOption {
+{pkgs, lib, config, system, ...}:
+let inherit (lib) types;
+in {
+options.networking = {
+ domainBase = lib.mkOption {
type = types.str;
description = "Base network name.";
example = "example";
};
- networking.domainAliases = lib.mkOption {
+ domainAliases = lib.mkOption {
type = types.listOf types.str;
description = "Domain aliases.";
example = [ "example.org" "example.net" ];
};
- networking.zones = lib.mkOption {
+ zones = lib.mkOption {
type = types.attrsOf (types.submodule ({name, options, config, ...}: {
options = {
iface = lib.mkOption {
};
config = lib.mkIf cfg.enable {
+ /*
systemd.services.firewall.enable = false;
systemd.services.shorewall = {
description = "Shorewall IPv4 Firewall";
cfg.configs;
systemPackages = [ cfg.package ];
};
+ */
};
}
--- /dev/null
+{pkgs, lib, config, ...}:
+let inherit (builtins) toString;
+ inherit (lib) types;
+ inherit (config.services) x509;
+ when = x: y: if x == null then "" else y;
+in
+{
+
+options.services.x509 = {
+ enable = lib.mkEnableOption "Generation of X.509 material";
+ scheme = lib.mkOption {
+ type = types.enum [ "manual" "self-signed" "letsencrypt" ];
+ default = "self-signed";
+ description = ''
+ Scheme for X.509 material.
+
+ manual:
+ Use certificate and key manually copied on the target at certFile and keyFile.
+ self-signed:
+ Generate a self-signed certificate.
+ letsencrypt:
+ Generate a certificate signed by Let's Encrypt.
+ '';
+ };
+ dir = lib.mkOption {
+ type = types.string;
+ default = "/var/x509";
+ description = ''In "self-signed" scheme: directory of the certificate and key.'';
+ };
+ certFile = lib.mkOption {
+ type = types.path;
+ example = "/var/x509/cert.pem";
+ description = ''In "manual" scheme: location of the certificate'';
+ };
+ keyFile = lib.mkOption {
+ type = types.path;
+ example = "/var/x509/key.pem";
+ description = ''In "manual" scheme: location of the key file.'';
+ };
+ dh = lib.mkOption {
+ type = types.int;
+ default = 4096;
+ description = ''Bit size of Diffie–Hellman parameters.'';
+ };
+ host = lib.mkOption {
+ type = types.nullOr types.string;
+ default = config.networking.domain;
+ };
+ distributionHost = lib.mkOption {
+ type = types.string;
+ default = config.networking.domain;
+ };
+ domains = lib.mkOption {
+ type = with types; listOf string;
+ example = [ "example.coop" ];
+ default = [];
+ };
+ days = lib.mkOption {
+ type = types.int;
+ default = 3650;
+ };
+ keySize = lib.mkOption {
+ type = types.int;
+ default = 4096;
+ };
+ cert = lib.mkOption {
+ type = types.string;
+ default =
+ if x509.scheme == "manual"
+ then x509.certFile
+ else if x509.scheme == "self-signed"
+ then "${x509.dir}/${x509.host}.cert.self-signed.pem"
+ else if x509.scheme == "letsencrypt"
+ then "/var/lib/acme/${x509.host}/fullchain.pem"
+ else throw ''Error: Certificate Scheme must be in [ "manual" "self-signed" "letsencrypt" ]'';
+ };
+ key = lib.mkOption {
+ type = types.string;
+ default =
+ if x509.scheme == "manual"
+ then x509.keyFile
+ else if x509.scheme == "self-signed"
+ then "${x509.dir}/${x509.host}.key.pem"
+ else if x509.scheme == "letsencrypt"
+ then "/var/lib/acme/${x509.host}/key.pem"
+ else throw ''Error: Certificate Scheme must be in [ "manual" "self-signed" "letsencrypt" ]'';
+ };
+ opensslConf = lib.mkOption {
+ type = (with types; attrsOf string);
+ default = x509.opensslConf_common //
+ x509.opensslConf_self-signed //
+ x509.opensslConf_auth-signed //
+ x509.opensslConf_user-cert;
+ apply = cnf:
+ pkgs.writeText "openssl.conf"
+ (lib.concatStrings
+ (lib.mapAttrsToList
+ (title: body:
+ (if title == "" then "" else "[ ${title} ]\n") +
+ body)
+ cnf));
+ };
+ opensslConf_common = lib.mkOption {
+ type = (with types; attrsOf string);
+ default = {
+ "" = ''
+ RANDFILE = /var/x509/openssl.rand
+ oid_section = extra_oids
+ '';
+ extra_oids = ''
+ # NOTE: only useful for Extended Validation (EV)
+ jurisdictionOfIncorporationLocalityName = 1.3.6.1.4.1.311.60.2.1.1
+ jurisdictionOfIncorporationStateOrProvinceName = 1.3.6.1.4.1.311.60.2.1.2
+ jurisdictionOfIncorporationCountryName = 1.3.6.1.4.1.311.60.2.1.3
+ '';
+ req = ''
+ prompt = no
+ distinguished_name = distinguished_name
+ string_mask = pkix
+ #x509_extensions = root_extensions
+ #req_extensions = extension
+ #attributes = req_attributes
+ '';
+ distinguished_name = ''
+ commonName = ${x509.host}
+ #countryName =
+ #stateOrProvinceName =
+ #localityName =
+ #"0.organizationName" =
+ #organizationalUnitName =
+ #businessCategory =
+ #jurisdictionOfIncorporationLocalityName = $stateOrProvinceName
+ #jurisdictionOfIncorporationStateOrProvinceName = $stateOrProvinceName
+ #jurisdictionOfIncorporationCountryName = $countryName
+ '';
+ };
+ };
+ opensslConf_self-signed = lib.mkOption {
+ type = (with types; attrsOf string);
+ default = {
+ self_signed_extensions = ''
+ basicConstraints = critical,CA:TRUE,pathlen:0
+ keyUsage = keyCertSign,cRLSign,digitalSignature,keyEncipherment
+ subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") x509.domains}
+ subjectKeyIdentifier = hash
+ issuerAltName = issuer:copy
+ authorityKeyIdentifier = keyid:always,issuer:always
+ authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
+ crlDistributionPoints = URI:http://${x509.distributionHost}/x509/${x509.host}.crl.self-signed.pem
+ '';
+ self_signed_ca = ''
+ dir = ${x509.dir}/pub
+ private_key = $dir/sec/key.pem
+ crl_dir = $dir
+ crlnumber = $dir/crl.self-signed.num
+ crl = $dir/crl.self-signed.pem
+ database = $dir/idx.self-signed.txt
+ '';
+ };
+ };
+ opensslConf_auth-signed = lib.mkOption {
+ type = (with types; attrsOf string);
+ default = {
+ auth_signed_extensions = ''
+ basicConstraints = critical,CA:FALSE
+ keyUsage = digitalSignature,keyEncipherment
+ subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") x509.domains}
+ subjectKeyIdentifier = hash
+ issuerAltName = issuer:copy
+ authorityKeyIdentifier = keyid:always,issuer:always
+ authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
+ crlDistributionPoints = URI:http://${x509.distributionHost}/x509/${x509.host}.crl.pem
+ certificatePolicies = @certificate_policies
+ '';
+ certificate_policies = ''
+ policyIdentifier = 1.2.250.1.42
+ CPS.1 = https://${x509.host}/x509/cps
+ '';
+ ca = ''
+ dir = ${x509.dir}/pub
+ private_key = $dir/sec/key.pem
+ crl_dir = $dir
+ crlnumber = $dir/crl.num
+ crl = $dir/crl.pem
+ database = $dir/idx.txt
+ '';
+ };
+ };
+ opensslConf_user-cert = lib.mkOption {
+ type = (with types; attrsOf string);
+ default = {
+ user_cert_extensions = ''
+ basicConstraints = critical,CA:FALSE,pathlen:0
+ keyUsage = digitalSignature,keyEncipherment
+ subjectAltName = email:$ENV::user@${x509.host}
+ subjectKeyIdentifier = hash
+ issuerAltName = issuer:copy
+ authorityKeyIdentifier = keyid:always,issuer:always
+ authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
+ '';
+ };
+ };
+};
+
+config = {
+ systemd.services.x509 = {
+ enable = true;
+ wantedBy = [ "multi-user.target" ];
+ before = [ "keys.target" ];
+ serviceConfig = {
+ ExecStart = pkgs.writeShellScriptBin "x509.service" (
+ lib.optionalString (x509.scheme == "self-signed") ''
+ # Generate DH parameters
+ { ${pkgs.openssl}/bin/openssl dhparam -in ${x509.dir}/dh.pem -text |
+ head -n 1 |
+ ${pkgs.gnugrep}/bin/grep >/dev/null "(${toString x509.keySize} bit)"
+ } || {
+ mkdir -p ${x509.dir}
+ ${pkgs.openssl}/bin/openssl dhparam \
+ ${toString x509.dh} \
+ >${x509.dir}/dh.pem
+ }
+ # Make private key
+ [ -s "${x509.key}" ] || {
+ mkdir -p ${x509.dir}
+ (
+ umask 077
+ "${pkgs.openssl}/bin/openssl" genrsa \
+ -out "${x509.key}" \
+ -rand /dev/urandom \
+ ${toString x509.keySize}
+ )
+ }
+ # Make self-signed certificate
+ [ -s "${x509.cert}" ] &&
+ [ "${x509.cert}" -nt "${x509.key}" ] || {
+ user= \
+ ${pkgs.openssl}/bin/openssl req \
+ -batch \
+ -new \
+ -x509 \
+ -utf8 \
+ -rand /dev/urandom \
+ -config ${x509.opensslConf} \
+ -extensions self_signed_extensions \
+ -reqexts self_signed_extensions \
+ -inform PEM \
+ -outform PEM \
+ -keyform PEM \
+ ${when x509.days "-days ${toString x509.days}"} \
+ -set_serial 0x$(sleep 1; date '+%Y%m%d%H%M%S') \
+ -reqopt no_pubkey,no_sigdump \
+ -key ${x509.key} \
+ -out ${x509.cert}
+ }
+ '') + "/bin/x509.service";
+ };
+ };
+};
+
+}
--- /dev/null
+map import
+[ overlays/lib/filesystem.nix
+ overlays/lib/strings.nix
+ #overlays/servers/mail/postfix.nix
+ #overlays/servers/mail/dovecot.nix
+ # TODO: remove when using a nixpkgs including the fix
+ # https://github.com/NixOS/nixpkgs/pull/46859
+ #overlays/users-init.nix
+]/* ++
+[ (self: super: { shorewall = super.callPackage ../pkgs/tools/networking/shorewall {}; })
+]*/
--- /dev/null
+self: super:
+let lib = super.lib; in
+{
+ lib = (super.lib or {}) // {
+ findFiles = pattern:
+ with builtins;
+ let go = curr:
+ let dir = readDir curr; in
+ let files = lib.filterAttrs (name: type:
+ type == "regular" &&
+ match pattern name != null) dir; in
+ let dirs = lib.filterAttrs (name: type: type == "directory") dir; in
+ map (name: "${curr}/${name}") (attrNames files) ++
+ lib.concatMap (name: go "${curr}/${name}") (attrNames dirs)
+ ;
+ in root: go (toPath root);
+ };
+}
nixpkgs = import .lib/nix/nixpkgs.nix;
pkgs = import nixpkgs {
config = {}; # Make the config pure, ignoring user's config.
- overlays = import .lib/nixpkgs-sourcephile/build/overlays.nix;
+ overlays = import ./overlays.nix;
};
- sourcephile-nix-build-modules =
- (import .lib/nixpkgs-sourcephile/build/modules.nix {
+ # Using modules enables to separate specific configurations in shell/configuration.nix
+ # from reusable code in shell/modules.nix
+ # which may find its way in another git repository one day.
+ modules =
+ (import shell/modules.nix {
inherit pkgs;
inherit (pkgs) lib;
- modules = [ ( import build/modules.nix ) ];
+ modules = [ ( import shell/configuration.nix ) ];
}).config;
/*
sourcephile-nix-build =
preferLocalBuild = true;
allowSubstitutes = false;
inherit (pkgs) coreutils;
- builder = pkgs.writeText "builder.sh" sourcephile-nix-build-modules.init.builder;
+ builder = pkgs.writeText "builder.sh" modules.init.builder;
};
*/
sourcephile-nix-build =
pkgs.buildEnv {
name = "sourcephile-nix-build";
pathsToLink = [ "/bin" ];
- paths = with sourcephile-nix-build-modules; [
+ paths = with modules; [
gnupg.init
#gnupg.gpg-fingerprint
#nix-plugins.nix-with-extra-builtins
# nix
export NIX_PATH="nixpkgs=${nixpkgs}:nixpkgs-sourcephile=$PWD/.lib/nixpkgs-sourcephile"
- #NIX_PATH+=":nixpkgs-overlays="$PWD"/install/overlays.nix"
+ NIX_PATH+=":nixpkgs-overlays="$PWD"/overlays"
#NIX_PATH+=""
# executables
install -D /dev/stdin "$PWD"/.config/nix/nix.conf <<-EOF
auto-optimise-store = true
plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so
- extra-builtins-file = ${sourcephile-nix-build-modules.nix-plugins.extra-builtins}
+ extra-builtins-file = ${modules.nix-plugins.extra-builtins}
EOF
# NOTE: sudo needs to be own by root with the setuid bit,
# from the local password-store.
NIXOPS_OPTS+=" --show-trace"
NIXOPS_OPTS+=" --option plugin-files ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so"
- NIXOPS_OPTS+=" --option extra-builtins-file ${sourcephile-nix-build-modules.nix-plugins.extra-builtins}"
+ NIXOPS_OPTS+=" --option extra-builtins-file ${modules.nix-plugins.extra-builtins}"
export NIXOPS_OPTS
# disnix
--- /dev/null
+{config, ...}:
+{
+ imports = [
+ configuration/gnupg.nix
+ ];
+ config = {
+ };
+}
enable = true;
dir.var = toString ../../../sec/gnupg;
keys = {
- "Sourcephile <root@sourcephile.fr>" = {
- uid = "Sourcephile <root@sourcephile.fr>";
+ "Julien Moutinho <julm@sourcephile.fr>" = {
+ uid = "Julien Moutinho <julm@sourcephile.fr>";
algo = "rsa4096";
expire = "3y";
usage = ["cert" "sign"];
- passPath = "sourcephile/gpg/root";
+ passPath = "julm/gpg/julm@sourcephile.fr";
subKeys = [
{ algo = "rsa4096"; expire = "3y"; usage = ["sign"];}
{ algo = "rsa4096"; expire = "3y"; usage = ["encrypt"];}
--- /dev/null
+{ pkgs
+, lib ? pkgs.lib
+, modules ? []
+, extraArgs ? {}
+, specialArgs ? {}
+, check ? true
+, prefix ? []
+}:
+let extraArgs_ = extraArgs;
+ pkgs_ = pkgs;
+ baseModules = map import (lib.findFiles ".*\\.nix" ./modules );
+ pkgsModule = rec {
+ _file = ./modules.nix;
+ key = _file;
+ config = {
+ _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_);
+ };
+ };
+in
+rec {
+ # Merge the option definitions in all modules,
+ # forming the full system configuration.
+ inherit (lib.evalModules {
+ inherit prefix;
+ inherit check;
+ modules = modules ++ baseModules ++ [ pkgsModule ];
+ args = extraArgs;
+ inherit specialArgs;
+ #specialArgs = { modulesPath = config/modules.nix; } // specialArgs;
+ }) config options;
+
+ # These are the extra arguments passed to every module.
+ # In particular, Nixpkgs is passed through the "pkgs" argument.
+ extraArgs = extraArgs_ // {
+ inherit modules;
+ inherit baseModules;
+ };
+
+ inherit (config._module.args) pkgs;
+}
--- /dev/null
+{ config, lib, pkgs, ... }:
+with lib;
+let cfg = config.nix-plugins;
+in
+{
+ options.nix-plugins = {
+ enable = lib.mkEnableOption "nix-plugins";
+ extra-builtins = mkOption {
+ type = types.lines;
+ default = ''
+ pass = path: exec [ "${config.nix-plugins.nix-pass}/bin/nix-pass" path ];
+ pass-to-file = path: file: exec [ "${config.nix-plugins.nix-pass-to-file}/bin/nix-pass-to-file" path file ];
+ git = dir: args: exec ([ "${config.nix-plugins.nix-git}/bin/nix-git" (builtins.toPath dir) ] ++ args);
+ git-time = dir: path: exec [ "${config.nix-plugins.nix-git}/bin/nix-git" (builtins.toPath dir) "log" "-1" "--format=%ct" "--" path ];
+ '';
+ description = ''
+ Content put in extra-builtins.nix for nix-plugins.
+ '';
+ apply = lines: pkgs.writeText "extra-builtins.nix" (''
+ { exec, ... }:
+ {
+ '' + lines + ''
+ }
+ '');
+ };
+
+ nix-with-extra-builtins = mkOption {
+ type = types.str;
+ apply = pkgs.writeShellScriptBin "nix-with-extra-builtins";
+ default = ''
+ ${pkgs.nix}/bin/nix \
+ --option plugin-files ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so \
+ --option extra-builtins-file ${cfg.extra-builtins} \
+ "$@"
+ '';
+ description = ''
+ Wrapper around nix to load extra-builtins.nix with nix-plugins.
+ '';
+ };
+
+ nix-pass = mkOption {
+ type = types.str;
+ apply = pkgs.writeShellScriptBin "nix-pass";
+ default = ''
+ set -e
+ f=$(mktemp)
+ trap "shred -u $f" EXIT
+ ${pkgs.pass}/bin/pass show "$1" >$f
+ nix-instantiate --eval -E "builtins.readFile $f"
+ '';
+ /*
+ nix-store --add $f
+ */
+ /*
+ set -o pipefail
+ ${pkgs.pass}/bin/pass show "$1" |
+ ${pkgs.gnused}/bin/sed \
+ -e 's:\n:\\n:g;s:\r:\\r:g;s:\t:\\t:g;s:":\\":g;1s:^:":;$s:$:":;'
+ */
+ description = ''
+ Wrapper around pass to call it with exec in extra-builtins.nix.
+ Unfortunately it can only load secrets which can be represented as a Nix string,
+ hence without null-byte and such special characters.
+ '';
+ };
+
+ nix-pass-to-file = mkOption {
+ type = types.str;
+ apply = pkgs.writeShellScriptBin "nix-pass-to-file";
+ default = ''
+ set -e
+ set -o pipefail
+ ${pkgs.pass}/bin/pass show "$1" |
+ install -D -m 400 /dev/stdin "$2"
+ printf '%s\n' "$PWD/$2"
+ '';
+ description = ''
+ Wrapper around pass to call it with exec in extra-builtins.nix and put the output in a file.
+ Needed for boot.initrd.network.ssh.host*Key.
+ '';
+ };
+
+ nix-git = mkOption {
+ type = types.str;
+ apply = pkgs.writeShellScriptBin "nix-git";
+ default = ''
+ cd "$1"; shift
+ ${pkgs.git}/bin/git "$@"
+ '';
+ description = ''
+ Wrapper around git to call it with exec in extra-builtins.nix.
+ '';
+ };
+ };
+}
--- /dev/null
+{ pkgs, lib, config, ... }:
+let cfg = config.gnupg;
+ inherit (lib) types;
+ unlines = builtins.concatStringsSep "\n";
+ unwords = builtins.concatStringsSep " ";
+
+ generateKeys = keys: unlines (lib.mapAttrsToList generateKey keys);
+ generateKey =
+ uid:
+ { uid ? uid
+ , algo ? "future-default"
+ , usage ? ["default"]
+ , expire ? "-"
+ , passPath
+ , subKeys ? {}
+ , ...
+ }@primary:
+ ''
+ info " generateKey uid=\"${uid}\""
+ if ! ${cfg.gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch --pinentry-mode loopback --passphrase-fd 0 \
+ --quick-generate-key "${uid}" "${algo}" "${unwords usage}" "${expire}"
+ fi
+ ${head1}
+ fpr=$(${cfg.gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1)
+ caps=$(${cfg.gpg-with-home}/bin/gpg-with-home \
+ --with-colons --fixed-list-mode --with-fingerprint \
+ --list-secret-keys -- "=${uid}" |
+ ${pkgs.gnugrep}/bin/grep '^ssb:' |
+ ${pkgs.coreutils}/bin/cut -d : -f 12 || true)
+ ''
+ + unlines (map (generateSubKey primary) subKeys)
+ + generateBackupKey "$fpr" primary
+ ;
+ generateSubKey =
+ primary:
+ { expire ? primary.expire
+ , algo ? primary.algo
+ , usage
+ , ...
+ }:
+ ''
+ info " generateSubKey usage=[${unwords usage}]"
+ if ! printf '%s\n' "$caps" | ${pkgs.gnugrep}/bin/grep -Fqx "${lettersKeyUsage usage}"
+ then
+ ${pkgs.pass}/bin/pass "${primary.passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch --pinentry-mode loopback --passphrase-fd 0 \
+ --quick-add-key "$fpr" "${algo}" "${unwords usage}" "${expire}"
+ fi
+ '';
+ generateBackupKey =
+ fpr:
+ { passPath
+ , backupRecipients ? []
+ , uid
+ , ...
+ }:
+ lib.optionalString (backupRecipients != [])
+ ''
+ info " generateBackupKey backupRecipients=[${unwords (map (s: "\\\"${s}\\\"") backupRecipients)}]"
+ mkdir -p "${cfg.dir.var}/backup/${uid}/"
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.pubkey.asc"
+ then
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.pubkey.asc" \
+ --export-options export-backup \
+ --export "${fpr}"
+ fi
+ '' + (if backupRecipients == [""] then
+ ''
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc"
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --pinentry-mode loopback --passphrase-fd 0 \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc" \
+ --gen-revoke "${fpr}"
+ fi
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec"
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch --pinentry-mode loopback --passphrase-fd 0 \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec" \
+ --export-options export-backup \
+ --export-secret-key "${fpr}"
+ fi
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec"
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch --pinentry-mode loopback --passphrase-fd 0 \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec" \
+ --export-options export-backup \
+ --export-secret-subkeys "${fpr}"
+ fi
+ '' else ''
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc.gpg"
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --pinentry-mode loopback --passphrase-fd 0 \
+ --armor --gen-revoke "${fpr}" |
+ gpg --encrypt ${recipients backupRecipients} \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc.gpg"
+ fi
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec.gpg"
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch --pinentry-mode loopback --passphrase-fd 0 \
+ --armor --export-options export-backup \
+ --export-secret-key "${fpr}" |
+ gpg --encrypt ${recipients backupRecipients} \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec.gpg"
+ fi
+ if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+ then
+ ${pkgs.pass}/bin/pass "${passPath}" |
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --batch --pinentry-mode loopback --passphrase-fd 0 \
+ --armor --export-options export-backup \
+ --export-secret-subkeys "${fpr}" |
+ gpg --encrypt ${recipients backupRecipients} \
+ --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+ fi
+ '');
+ recipients = rs: unwords (map (r: ''--recipient "${refKey r}"'') rs);
+ refKey = key: if builtins.typeOf key == "string" then key else "=${key.uid}";
+ signer = s: if s == null
+ then ""
+ else ''--sign --default-key "${refKey s}"'';
+ lettersKeyUsage = usage:
+ (if builtins.elem "encrypt" usage then "e" else "") +
+ (if builtins.elem "sign" usage then "s" else "") +
+ (if builtins.elem "cert" usage then "c" else "") +
+ (if builtins.elem "auth" usage then "a" else "");
+
+ passOfFingerprint = key:
+ # Return shell code
+ # which fills a map from the fingerprints of the given key
+ # to its password file.
+ ''
+ # shell.gnupg.pass.passOfFingerprint
+ for fpr in $(${cfg.gpg-fingerprint}/bin/gpg-fingerprint -- "=${key.uid}")
+ do eval "pass_$fpr=\"${key.passPath}\""
+ done
+ '';
+ forgetPass =
+ # Return shell code
+ # which installs an exit and keyboard interruption (^C) trap
+ # removing any pass from gpg-agent
+ # whose keygrip is registered in $keygrips.
+ ''
+ # forgetPass
+ keygrips=
+ forgetPass () {
+ for keygrip in $keygrips
+ do
+ echo >&2 "gpg: forget: keygrip=$keygrip"
+ GNUPGHOME=${cfg.dir.var} \
+ ${pkgs.gnupg}/bin/gpg-connect-agent </dev/null >&2 "CLEAR_PASSPHRASE $keygrip" ||
+ true
+ done
+ keygrips=
+ }
+ trap 'forgetPass' EXIT INT
+ '';
+ presetPass = keys: uid:
+ # Return shell code
+ # which preset the pass of given uid into gpg-agent,
+ # using keys to find where the pass is stored.
+ ''
+ ${unlines (map passOfFingerprint keys)}
+ # presetPass
+ GNUPGHOME=${cfg.dir.var} \
+ ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
+ ${head1}
+ fpr="$(${cfg.gpg-fingerprint}/bin/fingerprint -- "${uid}" | head1)"
+ eval pass="\''${pass_$fpr}"
+ if test -n "$pass"
+ then
+ for keygrip in $(${cfg.gpg-keygrip}/bin/gpg-keygrip -- "$fpr")
+ do
+ keygrips="$keygrips $keygrip"
+ echo >&2 "gpg: preset: keygrip=$keygrip pass=$pass"
+ ${pkgs.pass}/bin/pass "$pass" |
+ GNUPGHOME=${cfg.dir.var} \
+ ${pkgs.gnupg}/libexec/gpg-preset-passphrase --preset ''${XTRACE:+--verbose} $keygrip
+ done
+ fi
+ '';
+
+ head1 = ''
+ head1(){
+ IFS= read -r line
+ cat >/dev/null # NOTE: consuming all the input avoids useless triggering of pipefail
+ printf %s "$line"
+ }
+ '';
+ info = ''
+ info(){
+ echo >&2 "INFO: $*"
+ }
+ '';
+in
+{
+ options.gnupg = {
+ enable = lib.mkEnableOption "GnuPG admin utilities";
+ dir.var = lib.mkOption {
+ type = types.path;
+ default = "sec/gnupg";
+ description = ''
+ '';
+ };
+ gpg-with-home = lib.mkOption {
+ type = types.str;
+ apply = pkgs.writeScriptBin "gpg-with-home";
+ default = ''
+ GNUPGHOME=${cfg.dir.var} \
+ exec ${pkgs.gnupg}/bin/gpg "$@"
+ '';
+ description = ''
+ A wrapper around gpg to set GNUPGHOME.
+ '';
+ };
+ gpg-fingerprint = lib.mkOption {
+ type = types.str;
+ apply = pkgs.writeScriptBin "gpg-fingerprint";
+ default = ''
+ set -eu
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --with-colons --fixed-list-mode --with-fingerprint --with-subkey-fingerprint \
+ --list-public-keys "$@" |
+ while IFS=: read -r t x x x key x x x x uid x
+ do case $t in
+ (pub|sub|sec|ssb)
+ while IFS=: read -r t x x x x x x x x fpr x
+ do case $t in (fpr) printf '%s\n' "$fpr"; break;;
+ esac done
+ ;;
+ esac done
+ '';
+ description = ''
+ A wrapper around gpg to get fingerprints.
+ '';
+ };
+ gpg-keygrip = lib.mkOption {
+ type = types.str;
+ apply = pkgs.writeScriptBin "gpg-keygrip";
+ default = ''
+ set -eu
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --with-colons --fixed-list-mode --with-keygrip \
+ --list-public-keys "$@" |
+ while IFS=: read -r t x x x key x x x x uid x
+ do case $t in
+ (pub|sub|sec|ssb)
+ while IFS=: read -r t x x x x x x x x grp x
+ do case $t in (grp) printf '%s\n' "$grp"; break;;
+ esac done
+ ;;
+ esac done
+ '';
+ description = ''
+ A wrapper around gpg to get keygrips.
+ '';
+ };
+ gpg-uid = lib.mkOption {
+ type = types.str;
+ apply = pkgs.writeScriptBin "gpg-uid";
+ default = ''
+ set -eu
+ ${cfg.gpg-with-home}/bin/gpg-with-home \
+ --with-colons --fixed-list-mode \
+ --list-public-keys "$@" |
+ while IFS=: read -r t st x x x x x id x uid x
+ do case $t in
+ (uid)
+ case $st in
+ (u) printf '%s\n' "$uid";;
+ esac
+ ;;
+ esac done
+ '';
+ description = ''
+ A wrapper around gpg to get uids.
+ '';
+ };
+ init = lib.mkOption {
+ type = types.str;
+ apply = pkgs.writeShellScriptBin "init-gpg";
+ default = ''
+ set -eu
+ set -o pipefail
+ ${info}
+ info "Init GnuPG"
+ ${pkgs.coreutils}/bin/install -dm0700 -D ${cfg.dir.var}
+ ${pkgs.coreutils}/bin/ln -snf ${cfg.gpgConf} ${cfg.dir.var}/gpg.conf
+ ${pkgs.coreutils}/bin/ln -snf ${cfg.gpgAgentConf} ${cfg.dir.var}/gpg-agent.conf
+ ${pkgs.coreutils}/bin/ln -snf ${cfg.dirmngrConf} ${cfg.dir.var}/dirmngr.conf
+ '' +
+ generateKeys cfg.keys;
+ description = ''
+ Setup gpg.
+ '';
+ };
+ keys = lib.mkOption {
+ default = {};
+ example =
+ { "John Doe. <contact@example.coop>" = {
+ algo = "rsa4096";
+ expire = "1y";
+ usage = ["cert" "sign"];
+ passPath = "example.coop/gpg/contact";
+ subKeys = [
+ { algo = "rsa4096"; expire = "1y"; usage = ["sign"];}
+ { algo = "rsa4096"; expire = "1y"; usage = ["encrypt"];}
+ { algo = "rsa4096"; expire = "1y"; usage = ["auth"];}
+ ];
+ backupRecipients = ["@john@doe.pro"];
+ };
+ };
+ type = types.attrsOf (types.submodule ({uid, ...}: {
+ #config.uid = lib.mkDefault uid;
+ options = {
+ uid = lib.mkOption {
+ type = types.str;
+ example = "John Doe <john.doe@example.coop>";
+ default = uid;
+ description = ''
+ User ID.
+ '';
+ };
+ algo = lib.mkOption {
+ type = types.enum [ "rsa4096" ];
+ default = "future-default";
+ example = "rsa4096";
+ description = ''
+ Cryptographic algorithm.
+ '';
+ };
+ expire = lib.mkOption {
+ type = types.str;
+ default = "1y";
+ example = "1y";
+ description = ''
+ Expiration timeout.
+ '';
+ };
+ usage = lib.mkOption {
+ type = with types; listOf (enum [ "cert" "sign" "encrypt" "auth" "default" ]);
+ default = ["default"];
+ example = ["cert" "sign" "encrypt" "auth"];
+ description = ''
+ Cryptographic usage.
+ '';
+ };
+ passPath = lib.mkOption {
+ type = types.str;
+ example = "gnupg/coop/example/contact@";
+ description = ''
+ Password path.
+ '';
+ };
+ subKeys = lib.mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ algo = lib.mkOption {
+ type = types.enum [ "rsa4096" ];
+ default = "default";
+ example = "rsa4096";
+ description = ''
+ Cryptographic algorithm.
+ '';
+ };
+ expire = lib.mkOption {
+ type = types.str;
+ default = "1y";
+ example = "1y";
+ description = ''
+ Expiration timeout.
+ '';
+ };
+ usage = lib.mkOption {
+ type = with types; listOf (enum [ "sign" "encrypt" "auth" "default" ]);
+ default = ["default"];
+ example = ["sign" "encrypt" "auth"];
+ description = ''
+ Cryptographic usage.
+ '';
+ };
+ };
+ });
+ };
+ backupRecipients = lib.mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = ["@john@doe.pro"];
+ description = ''
+ Backup keys used to encrypt the a backup copy of the secret keys.
+ '';
+ };
+ };
+ }));
+ };
+ dirmngrConf = lib.mkOption {
+ type = types.str;
+ apply = s: pkgs.writeText "dirmngr.conf" s;
+ default = ''
+ allow-ocsp
+ hkp-cacert ${cfg.keyserverPEM}
+ keyserver hkps://keys.mayfirst.org
+ use-tor
+ #log-file ${cfg.dir.var}/dirmngr.log
+ #standard-resolver
+ '';
+ description = ''
+ GnuPG's dirmngr.conf content.
+ '';
+ };
+ keyserverPEM = lib.mkOption {
+ type = types.str;
+ apply = s: pkgs.writeText "keyserver.pem" s;
+ default = builtins.readFile gnupg/keyserver.pem;
+ description = ''
+ dirmngr's hkp-cacert content.
+ '';
+ };
+ gpgAgentConf = lib.mkOption {
+ type = types.str;
+ apply = s: pkgs.writeText "gpg-agent.conf" s;
+ default = ''
+ allow-preset-passphrase
+ default-cache-ttl 17200
+ default-cache-ttl-ssh 17200
+ enable-ssh-support
+ max-cache-ttl 17200
+ max-cache-ttl-ssh 17200
+ '';
+ description = ''
+ GnuPG's gpg-agent.conf content.
+ '';
+ };
+ gpgConf = lib.mkOption {
+ type = types.str;
+ apply = s: pkgs.writeText "gpg.conf" s;
+ default = ''
+ auto-key-locate keyserver
+ cert-digest-algo SHA512
+ charset utf-8
+ default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 TWOFISH BZIP2 ZLIB ZIP Uncompressed
+ fixed-list-mode
+ keyid-format 0xlong
+ keyserver-options no-honor-keyserver-url
+ no-auto-key-locate
+ no-default-keyring
+ no-emit-version
+ personal-cipher-preferences AES256 AES CAST5
+ personal-digest-preferences SHA512
+ quiet
+ s2k-cipher-algo AES256
+ s2k-count 65536
+ s2k-digest-algo SHA512
+ s2k-mode 3
+ tofu-default-policy unknown
+ trust-model tofu+pgp
+ use-agent
+ utf8-strings
+ '';
+ description = ''
+ GnuPG's gpg.conf content.
+ '';
+ };
+ };
+}
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIFHjCCBAagAwIBAgISA8apfWx1LXVrNYiw3eMAAAH/MA0GCSqGSIb3DQEBCwUA
+MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
+ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODAyMTAxNjE2MjBaFw0x
+ODA1MTExNjE2MjBaMBwxGjAYBgNVBAMTEWtleXMubWF5Zmlyc3Qub3JnMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwnSrjRgOlR5a6Z4TY8RfpY+FFbNN
+9n2nudtB5o2LfTeb7XrMrmpzqYJ31pQjstiLrIOWZCWluzR588cLf8R32f/Qpjf5
+TMvOFROYhDagSqw3AfL3sEIXOybv1e4UqpJFBNiSatzZqEhneCWPj49BGI3qVhc/
+nL9MJvB1o8cnhJ9VEiWiNUbbNcDpLHAYofbO9UuEHTr3ajOdifkeVvl49qHe3XA7
+CH3ep5q8UKELdX/c19JQHYKJG6jRArGhpdZ77flQikwTSyWoZXn2UxTbKkfLv/zE
+Q6CG8ug5IHqQtm4oJ5UyINSgwCovWR0VdNZx84K9nmtLiba4OwiXtwgrCQIDAQAB
+o4ICKjCCAiYwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
+BgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBT1XkWOTiiiYSB5fGll18yw
+CTn9qTAfBgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcB
+AQRjMGEwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlw
+dC5vcmcwLwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlw
+dC5vcmcvMDUGA1UdEQQuMCyCEWtleXMubWF5Zmlyc3Qub3Jnghd6aW1tZXJtYW5u
+Lm1heWZpcnN0Lm9yZzCB/gYDVR0gBIH2MIHzMAgGBmeBDAECATCB5gYLKwYBBAGC
+3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn
+MIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkgYmUg
+cmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFjY29y
+ZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0dHBz
+Oi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IB
+AQBL4mihf2XgbQIb/3GHyY69WPGKTCIKRogqbvWQTVmgSda0y+H22Y4g10PSBl5n
+a9Z5deYCuFdYg6xXmtb7mMXmh5fNVyA032BFZGh3QqV5EQGGr2aiSg98lEGZHTeg
+uU14TTsZek3BmiIY9uSDDLmGombWfP6AryDZlSUCxzlB9j0nSFIwXSNoC9TXkeEA
+sbX1rC/Cf3SC6ErrJ+348LGf/9+7x0gArr5hofolXdKwX3ezcTgkLiWC1JxFW8uk
+giN9PM09UfbX6ro1TIBWKr9nM8HC5HFMgd9aMSxDqwxR1J2GfX/Q+fhCiK9/XXTn
+RvFK7S8tJgyLACLU1uC7ue43
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
+SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
+GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
+q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
+SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
+Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
+a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
+/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
+AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
+CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
+bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
+c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
+VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
+ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
+MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
+Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
+AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
+uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
+wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
+X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
+PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
+KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
+-----END CERTIFICATE-----