(use-modules ((guix licenses) #:prefix license:) ((srfi srfi-1) #:prefix srfi-1:) ((xyz euandreh heredoc) #:prefix heredoc:) (gnu) (gnu services mail) (gnu services shepherd) (gnu packages admin) (gnu packages cyrus-sasl) (gnu packages dbm) (gnu packages m4) (gnu packages mail) (gnu packages messaging) (gnu packages onc-rpc) (gnu packages perl) (gnu packages tls) (gnu packages version-control) (gnu system setuid) (guix build-system gnu) (guix build-system trivial) (guix download) (guix packages) (guix records) (guix utils) (xyz euandreh queue)) (use-package-modules web) (use-service-modules certbot cgit mcron messaging networking security ssh version-control virtualization vpn web) (heredoc:enable-syntax) (define ssh-pubkey "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDnUv7iWOejQNa3fZ6v4lkHT6qFRp2+NuzIpFJ2Vy7eP58XZoiz6HJPcCU8Hf95JXwaXEwS4S7mXdw1x60hd8JIe058Ek6MZSSVQmlLfocGsAYj1wTrLmnQ8+PV0IeQlNj1aytBI1fL+v3IPt+JdLt6b+g3vwcEUU9efzxx2E0KZ5GIpb2meiCQ6ha+tcd7XqegB53eQj/h/coE2zLJodpaJ3xbj894pE/OJCNC0+4d0Sv7oHhY7QoLYldTQbSgPyhyfl4iZpJf6OEPZxK2cJaB+cbeoBB6aGNyU+CIJToM+uAJJ7H7EpvxfcnfJQ1PuY5szTdvFbW820euiUEKEW69mW4uaFNPSc6D4Z8tZ5hXQIqBD40irULhF0CYNkIILmyNV/KJIZ5HkbQ1q+UrCFHJyvuH/3aCTjj9OSfE7xHPQ3xd3Xw8vvj0Mjie09xFbbcklBTw5WRzH7cw8c+Q0O69kZZ8b+ykcdzWTeZeWNdnzptNqnMjfheig90rUIJ7DN0c+53jCUcGpWJxJhcYF9Uk1RNHmSE5+VzK1y+20t0grVFX90nApm4Tl35QPrX7Qxp9C81cWiUB8xCAE6jYrmd4x+P/3wSQfc1Xg0Eg3QjJB+6JD7cbyDJpzDR3ja+CLZCAr9I0B4rDKD2d6et/z67iXPnZUWMyZ8RVVZPFbBMOTw== openpgp:0xF727046D") (define rc.sh (plain-file "rc.sh" #"- #!/bin/sh # shellcheck source=/dev/null . /etc/profile export XDG_PREFIX=~/.usr export XDG_CACHE_HOME="$XDG_PREFIX"/var/cache export XDG_CONFIG_HOME="$XDG_PREFIX"/etc export XDG_DATA_HOME="$XDG_PREFIX"/share export XDG_STATE_HOME="$XDG_PREFIX"/state export XDG_LOG_HOME="$XDG_PREFIX"/var/log mkdir -p \ "$XDG_CONFIG_HOME" \ "$XDG_CACHE_HOME" \ "$XDG_DATA_HOME" \ "$XDG_STATE_HOME" \ "$XDG_LOG_HOME" GUIX_PROFILE="$XDG_CONFIG_HOME"/guix/current if [ -r "$GUIX_PROFILE"/etc/profile ]; then # shellcheck source=/dev/null . "$GUIX_PROFILE"/etc/profile fi export ENV=~/.profile export HISTSIZE=-1 export HISTCONTROL=ignorespace:ignoredups export EDITOR=vi export VISUAL="$EDITOR" export PAGER='less -R' export EXINIT=' " set number " set autoindent set ruler set showmode set showmatch ' export HISTFILE="$XDG_STATE_HOME"/bash-history export LESSHISTFILE="$XDG_STATE_HOME"/lesshst export RLWRAP_HOME="$XDG_CACHE_HOME"/rlwrap export GUILE_HISTORY="$XDG_STATE_HOME"/guile-history HOSTNAME="$(hostname)" export BORG_REPO="16686@ch-s010.rsync.net:borg/$HOSTNAME" export BORG_REMOTE_PATH='borg1' export BORG_PASSCOMMAND='cat /var/lib/borg-passphrase.txt' export GIT_CONFIG_GLOBAL=/etc/gitconfig unalias -a alias l='ls -lahF --color' alias grep='grep --color' alias less='less -R' alias e='vi' alias tree='tree -aC' error_marker() { STATUS=$? if [ "$STATUS" != 0 ]; then printf ' (!! %s !!) ' "$STATUS" fi } export PS1='`error_marker`\T \w/ \u@\H\$ ' "#)) (define backup.sh #"- #!/bin/sh # shellcheck source=/dev/null . /etc/rc set -eu usage() { cat <<-'EOF' Usage: backup [-q] [-C COMMENT] [ARCHIVE_TAG] backup -h EOF } help() { cat <<-'EOF' Options: -q disable verbose move, useful for for batch sessions -C COMMENT the comment text to be attached to the archive -h, --help show this message ARCHIVE_TAG the tag used to create the new backup (default: "manual") The repository is expected to have been created with: $ borg init -e repokey-blake2 The following environment variables are expected to be exported: $BORG_PASSCOMMAND $BORG_REPO $BORG_REMOTE_PATH Password-less SSH access is required, usually done via adding ~/.ssh/id_rsa.pub to the-ssh-remote:.ssh/authorized_keys. Root permission is also required. Examples: Run backup from cronjob: $ backup -q cronjob Create backup with a comment, a tag, and verbose mode active: $ backup -C 'The backup has a comment' EOF } for flag in "$@"; do case "$flag" in --) break ;; --help) usage help exit ;; *) ;; esac done VERBOSE_FLAGS='--verbose --progress' COMMENT='' while getopts 'qC:h' flag; do case "$flag" in q) VERBOSE_FLAGS='' ;; C) COMMENT="$OPTARG" ;; h) usage help exit ;; *) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) ARCHIVE_TAG="${1:-manual}" run() { set -x # shellcheck disable=2086 sudo -i borg create \ $VERBOSE_FLAGS \ --comment " $COMMENT" \ --stats \ --compression lzma,9 \ "::$(hostname)-{now}-$ARCHIVE_TAG" \ /root/ \ /home/ \ /etc/ \ /var/ \ /srv/ STATUS=$? set +x if [ "$STATUS" = 0 ]; then return 0 elif [ "$STATUS" = 1 ]; then printf 'WARNING, but no ERROR.\n' >&2 return 0 else return "$STATUS" fi } run || exit $? sudo -i borg check --verify-data --verbose "#) (define cronjob.sh #"- #!/bin/sh set -eu usage() { cat <<-'EOF' Usage: cronjob COMMAND... cronjob -h EOF } help() { cat <<-'EOF' Options: -h, --help show this message COMMAND the command to be executed Execute the given command, and send the output to email, with special treatment to the status code. It loads the appropriate files, so that the actual cron declaration is smaller. Examples: Run a backup: $ cronjob backup -q cron EOF } for flag in "$@"; do case "$flag" in --) break ;; --help) usage help exit ;; *) ;; esac done while getopts 'h' flag; do case "$flag" in h) usage help exit ;; *) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) set +eu # shellcheck source=/dev/null . /etc/rc set -eu CMD="$*" with-email -s "$CMD" -- "$@" 1>> /var/log/cronjob.log 2>&1 "#) (define reconfigure.sh #"- #!/bin/sh # shellcheck source=/dev/null . /etc/rc set -eu usage() { cat <<-'EOF' Usage: reconfigure [-U] reconfigure -h EOF } help() { cat <<-'EOF' Options: -U pull the latest channels before reconfiguring -h, --help show this message Run a "guix system reconfigure". If the -U flag is given, perform a "guix pull" prior to the reconfigure. Examples: Just do the deploy: $ reconfigure Update and upgrade: $ reconfigure -U EOF } for flag in "$@"; do case "$flag" in --) break ;; --help) usage help exit ;; *) ;; esac done UPDATE=false while getopts 'Uh' flag; do case "$flag" in U) UPDATE=true ;; h) usage help exit ;; *) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) if [ "$UPDATE" = true ]; then sudo -i guix pull -v3 fi sudo -i guix system -v3 reconfigure /etc/guix/system.scm "#) (define with-email.sh #"- #!/bin/sh set -eu usage() { cat <<-'EOF' Usage: with-email [-s SUBJECT] COMMAND... with-email -h EOF } help() { cat <<-'EOF' Options: -s SUBJECT set the subject of the email -h, --help show this message COMMAND the command to be wrapped Examples: Send email with default subject: $ with-email echo 123 Use custom subject and explicit separation of command: $ with-email -s 'Something' -- do-something.sh EOF } now() { date '+%Y-%m-%dT%H:%M:%S%Z' } uuid() { od -xN20 /dev/urandom | head -n1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' } mkstemp() { name="${TMPDIR:-/tmp}/uuid-tmpname with spaces.$(uuid)" touch "$name" echo "$name" } for flag in "$@"; do case "$flag" in --) break ;; --help) usage help exit ;; *) ;; esac done SUBJECT='NO SUBJECT' while getopts 's:h' flag; do case "$flag" in s) SUBJECT="$OPTARG" ;; h) usage help exit ;; *) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) if [ -z "$1" ]; then printf 'Missing COMMAND.\n\n' >&2 usage >&2 exit 2 fi STATUS=0 OUT="$(mkstemp)" { printf 'Running command: %s\n' "$*" printf 'Starting at: %s\n' "$(now)" printf '\n' "$@" || STATUS=$? printf '\n' printf 'Finished at: %s\n' "$(now)" } 1>"$OUT" 2>&1 HOSTNAME="$(hostname)" mail \ -a 'Content-Type: text/plain; charset=UTF-8' \ -s "(exit status: $STATUS) - $SUBJECT" \ root < "$OUT" || cat "$OUT" "#) (define (script name content) (package (name name) (version "latest") (source #f) (build-system trivial-build-system) (arguments (list #:modules '((guix build utils)) #:builder #~(begin (use-modules (guix build utils)) (let* ((bin (string-append %output "/bin")) (prog (string-append bin "/" #$name))) (mkdir-p bin) (call-with-output-file prog (lambda (port) (format port "~a" #$content))) (chmod prog #o755))))) (home-page #f) (synopsis #f) (description #f) (license #f))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;; /etc/shadow ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-record-type* shadow-group-configuration make-shadow-group-configuration shadow-group-configuration? (group shadow-group-configuration-group (default "shadow"))) (define (shadow-group-activation config) (match-record config (group) #~(begin (use-modules (guix build utils)) (format (current-error-port) "Setting ownership and permission for \"/etc/passwd\".~%") (chown "/etc/shadow" (passwd:uid (getpwnam "root")) (group:gid (getgrnam #$group))) (chmod "/etc/shadow" #o640)))) (define (shadow-group-accounts config) (match-record config (group) (list (user-group (name group) (system? #t))))) (define shadow-group-service-type (service-type (name 'shadow-group) (extensions (list (service-extension activation-service-type shadow-group-activation) (service-extension account-service-type shadow-group-accounts))) (default-value (shadow-group-configuration)) (description #"- FIXME:DOCUMENTATION "#))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Cyrus SASL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-record-type* cyrus-service-configuration make-cyrus-service-configuration cyrus-service-configuration? (name cyrus-service-configuration-name) (authmech cyrus-service-configuration-authmech (default "saslauthd"))) (define-record-type* cyrus-sasl-configuration make-cyrus-sasl-configuration cyrus-sasl-configuration? (cyrus-sasl cyrus-sasl-configuration-cyrus-sasl (default cyrus-sasl)) (authmech cyrus-sasl-configuration-authmech (default "shadow")) (services cyrus-sasl-configuration-services (default '())) (state-dir cyrus-sasl-configuration-state-dir (default "/var/lib/saslauthd"))) (define (cyrus-sasl-etc-files config) (match-record config (services state-dir) `(("sasl2" ,(file-union "cyrus-sasl" (map (lambda (service-config) (match-record service-config (name authmech) `(,name ,(plain-file name (format #f #"- pwcheck_method: ~a saslauthd_path: ~a/mux log_level: 7 "# authmech state-dir))))) services)))))) (define (cyrus-sasl-activation config) (match-record config (state-dir) #~(begin (use-modules (guix build utils)) (format (current-error-port) "Creating Cyrus SASL socket directory: \"~a\".~%" #$state-dir) (mkdir-p #$state-dir)))) (define (cyrus-sasl-shepherd-service config) (match-record config (cyrus-sasl authmech state-dir) (list (shepherd-service (provision '(cyrus-sasl)) (documentation #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the entrypoint for starting the "master" process. Then the "master" process itself takes responsability of starting all the required daemons and commands."#) (start #~(make-forkexec-constructor (list #$(file-append cyrus-sasl "/sbin/saslauthd") "-a" #$authmech "-d" "-m" #$state-dir) #:user "root" #:user "root")) (stop #~(make-kill-destructor)))))) (define cyrus-sasl-service-type (service-type (name 'cyrus-sasl) (extensions (list (service-extension etc-service-type cyrus-sasl-etc-files) (service-extension activation-service-type cyrus-sasl-activation) (service-extension profile-service-type (compose list cyrus-sasl-configuration-cyrus-sasl)) (service-extension shepherd-root-service-type cyrus-sasl-shepherd-service))) (compose srfi-1:concatenate) (extend (lambda (config services) (cyrus-sasl-configuration (inherit config) (services (append (cyrus-sasl-configuration-services config) services))))) (default-value (cyrus-sasl-configuration)) (description #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the top-level system service for Postfix. It includes: - populating /etc/postfix/ with read-only configuration files; - the user and groups used by Postfix when handling email delivery; - the special setgid binaries for daily usage, such as "sendmail"; - the Shepherd service for starting, stopping and *reloading* the service without restarting it; - the activation script for creating the required directories and configuring them with the correct permissions; - the binaries in the system profile so that one doesn't need to explicilty include the package when the service is already enabled. An extension to the log-rotation service isn't included: the default rottlog configuration already includes /var/log/maillog in its routine, so it is kept out. The defaults of provide sane default values for most things, such as group names, data and queue directories, etc. When used as-is, it creates a Postfix server that sends email from local users of the domain provided by "/etc/hostname"."#))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; dkimproxy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-record-type* dkimproxyout-configuration make-dkimproxyout-configuration dkimproxyout-configuration? (dkimproxy dkimproxyout-configuration-dkimproxy (default dkimproxy)) (user dkimproxyout-configuration-user (default "dkimproxyout")) (group dkimproxyout-configuration-group (default "dkimproxyout")) (listen dkimproxyout-configuration-listen (default "127.0.0.1:10027")) (relay dkimproxyout-configuration-relay (default "127.0.0.1:10028")) (domains dkimproxyout-configuration-domains (default (list (gethostname)))) (selector dkimproxyout-configuration-selector (default "dkimproxy")) (key-size dkimproxyout-configuration-key-size (default 2048)) (data-directory dkimproxyout-configuration-data-directory (default "/var/lib/dkimproxyout"))) (define (generate-out.cf config) (match-record config (listen relay domains selector data-directory) (format #f #"- listen ~a relay ~a domain ~a selector ~a signature dkim(c=relaxed/relaxed) # FIXME:DOCUMENTATION add this to the service documentation # the corresponding public key is available at: # ~a/public.key keyfile ~a/private.key "# listen relay (string-join domains ",") selector data-directory data-directory))) (define (dkimproxyout-etc-files config) (match-record config () `(("dkimproxy_out.conf" ,(plain-file "dkimproxy_out.conf" (generate-out.cf config)))))) (define (dkimproxyout-accounts config) (match-record config (user group) (list (user-account (name user) (group group) (comment "DKIMproxy-out signing system user") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin")) (system? #t)) (user-group (name group) (system? #t))))) (define (dkimproxyout-activation config) (match-record config (user group data-directory key-size) #~(begin (use-modules (guix build utils)) (let ((uid (passwd:uid (getpwnam #$user))) (gid (group:gid (getgrnam #$group)))) (format (current-error-port) "Creating DKIMproxy-out data directory: \"~a\".~%" #$data-directory) (mkdir-p #$data-directory) (chown #$data-directory uid gid) (chmod #$data-directory #o755) (let ((private-key (string-append #$data-directory "/private.key")) (public-key (string-append #$data-directory "/public.key"))) (unless (file-exists? private-key) (cond ((zero? (system* #$(file-append openssl "/bin/openssl") "genrsa" "-out" private-key (number->string #$key-size))) (format (current-error-port) "DKIMproxy private key file created: \"~a\".~%" private-key)) (else (format (current-error-port) "Failed to create DKIMproxy private key file: \"~a\".~%" private-key)))) (invoke #$(file-append openssl "/bin/openssl") "rsa" "-in" private-key "-pubout" "-out" public-key) (format (current-error-port) "Setting permissions for the public/private DKIMproxy keypair: \"~a/{public,private}.key\".~%" #$data-directory) (chown private-key uid gid) (chown public-key uid gid) (chmod private-key #o400) (chmod public-key #o644)))))) (define (dkimproxyout-shepherd-service config) (match-record config (dkimproxy user group) (list (shepherd-service (provision '(dkimproxy)) (documentation #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the entrypoint for starting the "master" process. Then the "master" process itself takes responsability of starting all the required daemons and commands."#) (start #~(make-forkexec-constructor (list #$(file-append dkimproxy "/bin/dkimproxy.out") "--conf_file=/etc/dkimproxy_out.conf") #:user #$user #:group #$group)) (stop #~(make-kill-destructor)) (actions (list (shepherd-action (name 'configuration) (documentation #"- FIXME:DOCUMENTATION "#) (procedure #~(lambda _ (format #t "/etc/dkimproxy_out.conf~%")))))))))) (define dkimproxyout-service-type (service-type (name 'dkimproxy) (extensions (list (service-extension etc-service-type dkimproxyout-etc-files) (service-extension account-service-type dkimproxyout-accounts) (service-extension activation-service-type dkimproxyout-activation) (service-extension profile-service-type (compose list dkimproxyout-configuration-dkimproxy)) (service-extension shepherd-root-service-type dkimproxyout-shepherd-service))) (default-value (dkimproxyout-configuration)) (description #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the top-level system service for Postfix. It includes: - populating /etc/postfix/ with read-only configuration files; - the user and groups used by Postfix when handling email delivery; - the special setgid binaries for daily usage, such as "sendmail"; - the Shepherd service for starting, stopping and *reloading* the service without restarting it; - the activation script for creating the required directories and configuring them with the correct permissions; - the binaries in the system profile so that one doesn't need to explicilty include the package when the service is already enabled. An extension to the log-rotation service isn't included: the default rottlog configuration already includes /var/log/maillog in its routine, so it is kept out. The defaults of provide sane default values for most things, such as group names, data and queue directories, etc. When used as-is, it creates a Postfix server that sends email from local users of the domain provided by "/etc/hostname"."#))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Postfix ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-record-type* postfix-configuration make-postfix-configuration postfix-configuration? (postfix postfix-configuration-postfix (default postfix)) (set-sendmail? postfix-configuration-set-sendmail? (default #t)) (master.cf-file postfix-configuration-master.cf-file (default #f)) (main.cf-file postfix-configuration-main.cf-file (default #f)) (master.cf-extra postfix-configuration-master.cf-extra (default "")) (main.cf-extra postfix-configuration-main.cf-extra (default "")) (data-directory postfix-configuration-data-directory (default "/var/lib/postfix")) (queue-directory postfix-configuration-queue-directory (default "/var/spool/postfix")) (user postfix-configuration-user (default "postfix")) (group postfix-configuration-group (default "postfix")) (setgid-group postfix-configuration-setgid-group (default "postdrop")) (root-aliases postfix-configuration-root-aliases (default '("abuse" "admin" "hostmaster" "postmaster"))) (cert-file postfix-configuration-cert-file (default #f)) (key-file postfix-configuration-key-file (default #f)) (hostname postfix-configuration-hostname (default (gethostname)))) ; FIXME: hardcoded value of dkimproxy listen and relay (define (generate-master.cf config) (match-record config (master.cf-extra) (format #f #"- # ============================================================================================================ # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (no) (never) (100) # ============================================================================================================= anvil unix - - n - 1 anvil bounce unix - - n - 0 bounce cleanup unix n - n - 0 cleanup defer unix - - n - 0 bounce discard unix - - n - - discard error unix - - n - - error flush unix n - n 1000? 0 flush lmtp unix - - n - - lmtp local unix - n n - - local pickup unix n - n 60 1 pickup -o content_filter=dksign:[127.0.0.1]:10027 proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap qmgr unix n - n 300 1 qmgr relay unix - - n - - smtp -o syslog_name=postfix/relay retry unix - - n - - error rewrite unix - - n - - trivial-rewrite scache unix - - n - 1 scache showq unix n - n - - showq smtp inet n - n - - smtpd -v -o syslog_name=postfix/smtp smtp unix - - n - - smtp submission inet n - n - - smtpd -v -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o content_filter=dksign:[127.0.0.1]:10027 tlsmgr unix - - n 1000? 1 tlsmgr trace unix - - n - 0 bounce verify unix - - n - 1 verify virtual unix - n n - - virtual postlog unix-dgram n - n - 1 postlogd # FIXME: doesn't work for sendmail -t in localhost dksign unix - - n - - smtp -o syslog_name=postfix/dkimproxyout-listen -o smtp_send_xforward_command=yes -o smtp_discard_ehlo_keywords=8bitmime,starttls 127.0.0.1:10028 inet n - n - - smtpd -v -o syslog_name=postfix/dkimproxyout-relay -o content_filter= -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks -o smtpd_helo_restrictions= -o smtpd_client_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o mynetworks=127.0.0.0/8 -o smtpd_authorized_xforward_hosts=127.0.0.0/8 ~a "# master.cf-extra))) (define (generate-main.cf config) (match-record config (postfix queue-directory data-directory user setgid-group cert-file key-file hostname main.cf-extra) (format #f #"- compatibility_level = 3.6 queue_directory = ~a data_directory = ~a mail_owner = ~a setgid_group = ~a myhostname = ~a mydestination = $myhostname, $mydomain, localhost.$mydomain, localhost alias_maps = hash:/etc/aliases home_mailbox = Mail/Inbox/ header_checks = regexp:{ { /^Received:.*/ IGNORE }, { /^X-Originating-IP:.*/ IGNORE } } smtpd_use_tls = yes smtpd_tls_cert_file = ~a smtpd_tls_key_file = ~a smtp_use_tls = $smtpd_use_tls smtp_tls_cert_file = $smtpd_tls_cert_file smtp_tls_key_file = $smtpd_tls_key_file smtpd_tls_security_level = may smtp_tls_security_level = may smtpd_tls_auth_only = yes smtpd_relay_restrictions = $smtpd_recipient_restrictions smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination smtpd_sasl_auth_enable = yes cyrus_sasl_config_path = /etc/sasl2 debug_peer_list = 127.0.0.1 milter_default_action = accept smtpd_milters = ? ~a "# queue-directory data-directory user setgid-group hostname (or cert-file (format #f "/etc/letsencrypt/live/~a/fullchain.pem" hostname)) (or key-file (format #f "/etc/letsencrypt/live/~a/privkey.pem" hostname)) main.cf-extra))) (define (postfix-etc-files config) (match-record config (master.cf-file main.cf-file) `(("postfix" ,(file-union "postfix" `(("master.cf" ,(plain-file "master.cf" (or master.cf-file (generate-master.cf config)))) ("main.cf" ,(plain-file "main.cf" (or main.cf-file (generate-main.cf config)))))))))) (define (postfix-accounts config) (match-record config (user group setgid-group) (list (user-account (name user) (group group) (comment "Postfix system user") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin")) (system? #t)) (user-group (name group) (system? #t)) (user-group (name setgid-group) (system? #t))))) (define (postfix-setuid-programs config) (match-record config (postfix setgid-group set-sendmail?) (append (list (setuid-program (program (file-append postfix "/sbin/postdrop")) (setuid? #f) (setgid? #t) (group setgid-group)) (setuid-program (program (file-append postfix "/sbin/postqueue")) (setuid? #f) (setgid? #t) (group setgid-group))) (if set-sendmail? (list (setuid-program (program (file-append postfix "/sbin/sendmail")) (setuid? #f) (setgid? #t) (group setgid-group))) '())))) (define (postfix-activation config) (match-record config (queue-directory) #~(begin (use-modules (guix build utils)) (let ((user (getpwnam "root"))) (format (current-error-port) "Creating Postfix queue directory: \"~a\".~%" #$queue-directory) (mkdir-p #$queue-directory) (chown #$queue-directory (passwd:uid user) (passwd:gid user)) (chmod #$queue-directory #o755) (format (current-error-port) "Creating email spool director: \"/var/mail\".~%") (mkdir-p "/var/mail") (format (current-error-port) "Updating /etc/aliases: FIXME.~%") #; (invoke #$(file-append postfix "/bin/newaliases")))))) (define (postfix-shepherd-service config) (match-record config (postfix) (list (shepherd-service (provision '(postfix)) (documentation #"- Run the Postfix MTA. This is the entrypoint for starting the "master" process. Then the "master" process itself takes responsability of starting all the required daemons and commands."#) (start #~(make-forkexec-constructor (list #$(file-append postfix "/sbin/postfix") "start-fg") #:pid-file "/var/lib/postfix/master.lock")) (stop #~(make-kill-destructor)) (actions (list (shepherd-action (name 'configuration) (documentation #"- FIXME:DOCUMENTATION "#) (procedure #~(lambda _ (format #t "/etc/postfix/master.cf~%") (format #t "/etc/postfix/main.cf~%")))) (shepherd-action (name 'reload) (documentation #"- Re-read the "master.cf" and "main.cf" configuration files. Daemon processes terminate when possible, and when restarted use the values of the new configuration files. This live-reload option is usually preferable over a stop/start cycle, as it incurs in no interruption of the running service."#) (procedure #~(lambda _ (invoke #$(file-append postfix "/sbin/postfix") "reload")))))))))) (define (postfix-aliases config) (match-record config (root-aliases) (map (lambda (alias) `(,alias "root")) root-aliases))) (define (postfix-certificates config) (match-record config (hostname) (list (certificate-configuration (domains (list hostname)) (deploy-hook (program-file "postfix-certbot-deploy-hook" #~(with-shepherd-action 'postfix ('reload) result result))))))) (define postfix-service-type (service-type (name 'postfix) (extensions (list (service-extension etc-service-type postfix-etc-files) (service-extension account-service-type postfix-accounts) (service-extension setuid-program-service-type postfix-setuid-programs) (service-extension activation-service-type postfix-activation) (service-extension mail-aliases-service-type postfix-aliases) (service-extension certbot-service-type postfix-certificates) (service-extension profile-service-type (compose list postfix-configuration-postfix)) (service-extension shepherd-root-service-type postfix-shepherd-service))) (default-value (postfix-configuration)) (description #"- Run the Postfix MTA. This is the top-level system service for Postfix. It includes: - populating /etc/postfix/ with read-only configuration files; - the user and groups used by Postfix when handling email delivery; - the special setgid binaries for daily usage, such as "sendmail"; - the Shepherd service for starting, stopping and *reloading* the service without restarting it; - the activation script for creating the required directories and configuring them with the correct permissions; - the binaries in the system profile so that one doesn't need to explicilty include the package when the service is already enabled. An extension to the log-rotation service isn't included: the default rottlog configuration already includes /var/log/maillog in its routine, so it is kept out. The defaults of provide sane default values for most things, such as group names, data and queue directories, etc. When used as-is, it creates a Postfix server that sends email from local users of the domain provided by "/etc/hostname"."#))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Dovecot ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-record-type* dovecot2-configuration make-dovecot2-configuration dovecot2-configuration? (dovecot2 dovecot2-configuration-dovecot2 (default dovecot)) (raw-file dovecot2-configuration-raw-file (default #f)) (extra-content dovecot2-configuration-extra-content (default "")) (config-name dovecot2-configuration-config-name (default "dovecot2.conf")) (user dovecot2-configuration-user (default "dovecot2")) (group dovecot2-configuration-group (default "dovecot2")) (untrusted-user dovecot2-configuration-untrusted-user (default "dovenull2")) (untrusted-group dovecot2-configuration-untrusted-group (default "dovenull2")) (base-dir dovecot2-configuration-base-dir (default "/var/run/dovecot2")) (state-dir dovecot2-configuration-state-dir (default "/var/lib/dovecot2")) (hostname dovecot2-configuration-hostname (default (gethostname)))) (define (generate-dovecot-config config) (match-record config (user group untrusted-user hostname base-dir state-dir extra-content) (format #f #"- protocols = imap default_internal_user = ~a default_internal_group = ~a default_login_user = ~a auth_mechanisms = plain login auth_username_format = %n passdb { driver = shadow } userdb { driver = passwd } service auth-worker { group = shadow } ssl = required ssl_cert = (raw-file config-name) `(("dovecot2" ,(file-union "dovecot2" `((,config-name ,(plain-file config-name (or raw-file (generate-dovecot-config config)))))))))) (define (dovecot2-accounts config) (match-record config (user group untrusted-user untrusted-group) (list (user-account (name user) (group group) (comment "Dovecot system user") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin")) (system? #t)) (user-account (name untrusted-user) (group untrusted-group) (comment "Dovecot user for untrusted logins") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin")) (system? #t)) (user-group (name group) (system? #t)) (user-group (name untrusted-group) (system? #t))))) (define (dovecot2-activation config) (match-record config (base-dir state-dir) #~(begin (use-modules (guix build utils)) (let ((user (getpwnam "root"))) (format (current-error-port) "Creating Dovecot base_dir directory: \"~a\".~%" #$base-dir) (mkdir-p #$base-dir) (let ((dhparam.pem (string-append #$state-dir "/dhparam.pem"))) (unless (file-exists? dhparam.pem) (cond ((zero? (system* (string-append #$openssl "/bin/openssl") "dhparam" "-out" dhparam.pem "2048")) (format (current-error-port) "Dovecot2 dhparam.pem file created: \"~a\".~%" dhparam.pem)) (else (format (current-error-port) "Failed to create dhparam.pem file: \"~a\".~%" dhparam.pem))))))))) (define (dovecot2-shepherd-service config) (match-record config (dovecot2 config-name) (let ((config-file (string-append "/etc/dovecot2/" config-name))) (list (shepherd-service (provision '(dovecot2)) (documentation #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the entrypoint for starting the "master" process. Then the "master" process itself takes responsability of starting all the required daemons and commands."#) (start #~(make-forkexec-constructor (list #$(file-append dovecot2 "/sbin/dovecot") "-F" "-c" #$config-file))) (stop #~(make-kill-destructor)) (actions (list (shepherd-action (name 'configuration) (documentation #"- FIXME:DOCUMENTATION "#) (procedure #~(lambda _ (format #t "~a~%" #$config-file)))) (shepherd-action (name 'reload) (documentation #"- FIXME:DOCUMENTATION Re-read the "master.cf" and "main.cf" configuration files. Daemon processes terminate when possible, and when restarted use the values of the new configuration files. This live-reload option is usually preferable over a stop/start cycle, as it incurs in no interruption of the running service."#) (procedure #~(lambda _ (invoke #$(file-append dovecot "/bin/doveadm") "-c" #$config-file "reload"))))))))))) (define (dovecot2-certificates config) (match-record config (config-name hostname) (list (certificate-configuration (domains (list hostname)) (deploy-hook (program-file "dovecot2-certbot-deploy-hook" #~(invoke #$(file-append dovecot "/bin/doveadm") "-c" (string-append "/etc/dovecot2/" #$config-name) "reload"))))))) (define dovecot2-service-type (service-type (name 'dovecot2) (extensions (list (service-extension etc-service-type dovecot2-etc-files) (service-extension account-service-type dovecot2-accounts) (service-extension activation-service-type dovecot2-activation) (service-extension certbot-service-type dovecot2-certificates) (service-extension profile-service-type (compose list dovecot2-configuration-dovecot2)) (service-extension shepherd-root-service-type dovecot2-shepherd-service))) (default-value (dovecot2-configuration)) (description #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the top-level system service for Postfix. It includes: - populating /etc/postfix/ with read-only configuration files; - the user and groups used by Postfix when handling email delivery; - the special setgid binaries for daily usage, such as "sendmail"; - the Shepherd service for starting, stopping and *reloading* the service without restarting it; - the activation script for creating the required directories and configuring them with the correct permissions; - the binaries in the system profile so that one doesn't need to explicilty include the package when the service is already enabled. An extension to the log-rotation service isn't included: the default rottlog configuration already includes /var/log/maillog in its routine, so it is kept out. The defaults of provide sane default values for most things, such as group names, data and queue directories, etc. When used as-is, it creates a Postfix server that sends email from local users of the domain provided by "/etc/hostname"."#))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;; nanolist ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-record-type* nanolist-list-configuration make-nanolist-list-configuration nanolist-list-configuration? (address nanolist-list-configuration-address) (name nanolist-list-configuration-name) (description nanolist-list-configuration-description) (bcc nanolist-list-configuration-bcc (default '())) (posters nanolist-list-configuration-posters (default '())) (hidden? nanolist-list-configuration-hidden? (default #f)) (subscribers-only? nanolist-list-configuration-subscribers-only? (default #f))) ;; FIXME: where do the logs go to? (define-record-type* nanolist-configuration make-nanolist-configuration nanolist-configuration? (nanolist nanolist-configuration-nanolist (default postfix #;nanolist)) (user nanolist-configuration-user (default "nanolist")) (group nanolist-configuration-group (default "nanolist")) (alias-entry nanolist-configuration-alias-entry (default "nanolist")) (config-name nanolist-configuration-config-name (default "nanolist.conf")) (lists nanolist-configuration-lists (default '())) (state-directory nanolist-configuration-state-directory (default "/var/lib/nanolist")) ) (define (generate-nanolist-conf config) (match-record config (state-directory lists) (format #f #"- { "state-directory": "~a", "lists": [ ~a ] } "# state-directory (map (lambda (list-config) (match-record list-config (name description bcc posters hidden? subscribers-only?) (format #f #"- { "name": ~a, "description": ~a, "bcc": ~a, "posters": ~a, "hidden?": ~a, "subscribers-only?": ~a } "# name description bcc posters (if hidden? "true" "false") (if subscribers-only? "true" "false")))) lists)))) (define (nanolist-etc-files config) (match-record config (config-name) `((,config-name ,(plain-file config-name (generate-nanolist-conf config)))))) (define (nanolist-accounts config) (match-record config (user group) (list (user-account (name user) (group group) (comment "nanolist system user") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin")) (system? #t)) (user-group (name group) (system? #t))))) (define (nanolist-activation config) (match-record config (user group state-directory) #~(begin (use-modules (guix build utils)) (format (current-error-port) "Creating nanolist's subscriptions directory: \"~a\".~%" #$state-directory) (mkdir-p #$state-directory) (chown #$state-directory (passwd:uid (getpwnam #$user)) (group:gid (getgrnam #$group))) (chmod #$state-directory #o750)))) (define (nanolist-aliases config) (match-record config (nanolist alias-entry lists) (append (map (lambda (list) `(,alias ,alias-entry)) lists) `((,alias-entry ,(string-append "|" "/tmp/nanotmp" "/bin/nanolist")))))) (define nanolist-service-type (service-type (name 'nanolist) (extensions (list (service-extension etc-service-type nanolist-etc-files) (service-extension account-service-type nanolist-accounts) (service-extension activation-service-type nanolist-activation) (service-extension mail-aliases-service-type nanolist-aliases) (service-extension profile-service-type (compose list nanolist-configuration-nanolist)))) (default-value (nanolist-configuration)) (description #"- FIXME:DOCUMENTATION Run the Postfix MTA. This is the top-level system service for Postfix. It includes: - populating /etc/postfix/ with read-only configuration files; - the user and groups used by Postfix when handling email delivery; - the special setgid binaries for daily usage, such as "sendmail"; - the Shepherd service for starting, stopping and *reloading* the service without restarting it; - the activation script for creating the required directories and configuring them with the correct permissions; - the binaries in the system profile so that one doesn't need to explicilty include the package when the service is already enabled. An extension to the log-rotation service isn't included: the default rottlog configuration already includes /var/log/maillog in its routine, so it is kept out. The defaults of provide sane default values for most things, such as group names, data and queue directories, etc. When used as-is, it creates a Postfix server that sends email from local users of the domain provided by "/etc/hostname"."#))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;; OS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define host-name "toph") (define tld "arrobaponto.org") (define mail.tld (string-append "mail." tld)) (define whoami "andreh") (define me "eu") (define gitconfig (plain-file "gitconfig" (format #f #"- [init] defaultBranch = main [user] email = ci@~a name = "~a CI" [advice] detachedHead = false "# host-name host-name))) (operating-system (locale "fr_FR.UTF-8") (timezone "America/Sao_Paulo") (host-name host-name) (users (append (list (user-account (name whoami) (comment "EuAndreh") (group "users") (supplementary-groups '("wheel"))) (user-account (name "git") (group "git") (comment "Public Git user") (home-directory "/srv/git") (shell (file-append git "/bin/git-shell")))) %base-user-accounts)) (groups (append (list (user-group (name "git"))) %base-groups)) (sudoers-file (plain-file "sudoers" #"- root ALL=(ALL) ALL %wheel ALL=NOPASSWD: ALL "#)) (packages (append (map (compose list specification->package+output symbol->string) '(nss-certs borg curl entr git-minimal guile-heredoc-latest mailutils-sendmail ranger rlwrap rsync strace trash-cli tree dkimproxy prosody cgit)) (list (script "rc" rc.sh) (script "backup" backup.sh) (script "cronjob" cronjob.sh) (script "reconfigure" reconfigure.sh) (script "with-email" with-email.sh)) %base-packages)) (services (append (list (service dhcp-client-service-type) (service ntp-service-type) (service libvirt-service-type) (service virtlog-service-type) (service shadow-group-service-type) (service nanolist-service-type) (service fail2ban-service-type) (service mcron-service-type (mcron-configuration (jobs (list #~(job "0 1 * * *" "cronjob backup -q cron") #~(job "0 2 * * *" "cronjob reconfigure -U"))))) (service dkimproxyout-service-type (dkimproxyout-configuration (domains (list tld)))) (service git-daemon-service-type (git-daemon-configuration (export-all? #t))) (service cgit-service-type (cgit-configuration (nginx '()) (source-filter (file-append cgit "/lib/cgit/filters/syntax-highlighting.py")) (about-filter (file-append cgit "/lib/cgit/filters/about-formatting.sh")) (virtual-root "/git/") (remove-suffix? #t) (nocache? #t) (enable-commit-graph? #t) (enable-follow-links? #t) (enable-index-owner? #t) (enable-log-filecount? #t) (enable-log-linecount? #t) (enable-remote-branches? #t) (enable-subject-links? #t) (snapshots '("tar.gz" "tar.xz")) (root-desc "Patches welcome!") (root-title "EuAndreh repositories") (logo "/git/static/cgit.png") (favicon "/git/static/favicon.ico") (css "/git/static/cgit.css") (extra-options '(#"- enable-blame=1 readme=:README.md readme=:README "#)))) (service nginx-service-type (nginx-configuration (server-blocks (list (nginx-server-configuration (server-name (list tld)) (listen '("[::]:443 ssl http2" "443 ssl http2")) (root "/srv/www") (ssl-certificate (format #f "/etc/letsencrypt/live/~a/fullchain.pem" tld)) (ssl-certificate-key (format #f "/etc/letsencrypt/live/~a/privkey.pem" tld)) (locations (list (nginx-location-configuration (uri "/git/static/") (body (list (list "alias " cgit "/share/cgit/;")))) (nginx-location-configuration (uri "/git/") (body (list (list "fastcgi_param SCRIPT_FILENAME " cgit "/lib/cgit/cgit.cgi;") #"- fastcgi_param SCRIPT_FILENAME ~a/lib/cgit/cgit.cgi; fastcgi_param PATH_INFO $uri; fastcgi_param QUERY_STRING $args; fastcgi_param HTTP_HOST $server_name; fastcgi_pass 127.0.0.1:9000; rewrite /git(.*) $1 break; "#)))))))))) (simple-service 'create-/srv/www activation-service-type #~(begin (use-modules (guix build utils)) (let ((user (getpwnam #$whoami)) (git (getpwnam "git"))) (format (current-error-port) "Creating \"/srv/www\".~%") (mkdir-p "/srv/www") (chown "/srv/www" (passwd:uid user) (passwd:gid user)) (chmod "/srv/www" #o755) (format (current-error-port) "Setting permissions for \"/srv/git\".~%") (mkdir-p "/srv/git") (chown "/srv/git" (passwd:uid git) (passwd:gid git)) (chmod "/srv/git" #o755)))) (service prosody-service-type (prosody-configuration (modules-enabled (append '("groups" "mam") %default-modules-enabled)) (admins (list (format #f "~a@~a" me whoami))) (c2s-require-encryption? #t) (s2s-require-encryption? #t) (s2s-secure-auth? #t) (authentication "internal_hashed") (virtualhosts (list (virtualhost-configuration (domain tld)))))) (service wireguard-service-type (wireguard-configuration (addresses '("10.0.0.0/32")) (peers (list (wireguard-peer (name "velhinho") (public-key "Mhv8KxB/QXQpNKNtqD57PoFv43TXJ1lg52PJd6TmtwI=") (allowed-ips '("10.0.0.0/24")) (keep-alive 25)))))) (service dovecot2-service-type (dovecot2-configuration (hostname mail.tld))) (service certbot-service-type (certbot-configuration (email "eu@euandre.org") (certificates (list (certificate-configuration (domains (list tld)) (deploy-hook (program-file "prosody-certbot-deploy-hook" #~(begin (format (current-error-port) "Importing new TLS certificates for \"~a\" to Prosody via prosodyctl(8).~%" #$tld) (invoke #$(file-append prosody "/bin/prosodyctl") "--root" "cert" "import" "/etc/letsencrypt/live"))))))))) (service cyrus-sasl-service-type (cyrus-sasl-configuration (services (list (cyrus-service-configuration (name "smtpd.conf")))))) (service postfix-service-type (postfix-configuration (hostname mail.tld) (main.cf-extra (format #f #"- canonical_maps = inline:{ ~a=~a@~a } "# whoami me tld)))) (service mail-aliases-service-type `(("root" ,whoami) (,me ,whoami))) (simple-service 'extra-etc-file etc-service-type `(("rc" ,rc.sh) ("gitconfig" ,gitconfig))) (service openssh-service-type (openssh-configuration (password-authentication? #f) (authorized-keys `((,whoami ,(plain-file "id_rsa.pub" ssh-pubkey)) ("git" ,(plain-file "id_rsa.pub" ssh-pubkey)))) (extra-content #"- ClientAliveInterval 30 ClientAliveCountMax 20 MaxSessions 20 "#)))) %base-services)) (bootloader (bootloader-configuration (bootloader grub-bootloader) (targets '("/dev/vda")) )) (swap-devices (list (swap-space (target (uuid "94b47d91-3542-438a-84a9-859fe347ce09"))))) (file-systems (append (list (file-system (mount-point "/") (device (uuid "4c36d5ad-f996-413e-a55c-c05b7e1876f2" 'btrfs)) (type "btrfs"))) %base-file-systems)))