From: muradm <mail@muradm.net>
To: 56608@debbugs.gnu.org
Subject: [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type.
Date: Sun, 17 Jul 2022 05:32:23 +0300 [thread overview]
Message-ID: <20220717023223.440-1-mail@muradm.net> (raw)
* gnu/services/security.scm: New module.
* gnu/local.mk: Add new security module.
* doc/guix.text: Add fail2ban-service-type documentation.
---
doc/guix.texi | 215 +++++++++++++++++++++++++
gnu/local.mk | 2 +
gnu/services/security.scm | 328 ++++++++++++++++++++++++++++++++++++++
3 files changed, 545 insertions(+)
create mode 100644 gnu/services/security.scm
diff --git a/doc/guix.texi b/doc/guix.texi
index 8b09bcd4eb..1fc327c4bc 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -36285,6 +36285,221 @@ Extra command line options for @code{nix-service-type}.
@end table
@end deftp
+@cindex Fail2ban
+@subsubheading Fail2ban service
+
+@uref{http://www.fail2ban.org/, @code{fail2ban}} scans log files
+(e.g. @code{/var/log/apache/error_log}) and bans IPs that show the malicious
+signs -- too many password failures, seeking for exploits, etc.
+
+@code{fail2ban} service is provided in @code{(gnu services security)} module.
+
+This is the type of the service that runs @code{fail2ban} daemon. It can be
+used in various ways, which are:
+
+@itemize
+
+@item Explicit configuration
+users are free to enable @code{fail2ban} configuration without strong
+dependency.
+
+@item On-demand extending configuration
+convenience @code{fail2ban-jail-service} function is provided, in order
+to extend existing services on-demand.
+
+@item Permanent extending configuration
+service developers may not @code{fail2ban-service-type} in service-type's
+extensions.
+
+@end itemize
+
+@defvr {Scheme Variable} fail2ban-service-type
+
+This is the type of the service that runs @code{fail2ban} daemon. It can be
+configured explicitly as following:
+
+@lisp
+(append
+ (list
+ ;; excplicit configuration, this way fail2ban daemon
+ ;; will start and do its thing for sshd jail
+ (service fail2ban-service-type
+ (fail2ban-configuration
+ (extra-jails
+ (list
+ (fail2ban-jail-configuration
+ (name "sshd")
+ (enabled #t))))))
+ ;; there is no direct dependency with actual openssh
+ ;; server configuration, it could even be omited here
+ (service openssh-service-type))
+ %base-services)
+@end lisp
+@end defvr
+
+@deffn {Scheme Procedure} fail2ban-jail-service @var{svc-type} @var{jail}
+Return extended @var{svc-type} of @code{<service-type>} with added
+@var{jail} of type @code{fail2ban-jail-configuration} extension
+for @code{fail2ban-service-type}.
+
+For example:
+
+@lisp
+(append
+ (list
+ (service
+ ;; using convenience function we can extend virutally
+ ;; any service type with any fail2ban jail
+ ;; this way we don't have to explicitly add
+ ;; (service fail2ban-service-type) to our configuration
+ (fail2ban-jail-service
+ openssh-service-type
+ (fail2ban-jail-configuration
+ (name "sshd")
+ (enabled #t)))
+ (openssh-configuration ...))))
+@end lisp
+@end deffn
+
+@deftp {Data Type} fail2ban-configuration
+Configuration record for the @code{fail2ban-service-type}.
+@table @asis
+
+@item @code{fail2ban} (default: @code{fail2ban})
+The @code{fail2ban} package to use. It used for both binaries and
+as base default configuration that will be extended with
+@code{<fail2ban-jail-configuration>}s.
+
+@item @code{run-directory} (default: @file{"/var/run/fail2ban"})
+State directory for @code{fail2ban} daemon.
+
+@item @code{jails} (default: @code{'()})
+Instances of @code{<fail2ban-jail-configuration>} collected from
+extensions.
+
+@item @code{extra-jails} (default: @code{'()})
+Instances of @code{<fail2ban-jail-configuration>} provided by user
+explicitly.
+
+@item @code{extra-content} (default: @code{""})
+Extra raw content to add at the end of @file{jail.local}.
+
+@end table
+@end deftp
+
+@deftp {Data Type} fail2ban-jail-configuration
+@code{fail2ban} jail configuration to be added to @file{jail.local}.
+@table @asis
+
+Fields with default value of @code{*unspecified*} will not be serialized
+to configuration, thus default values of @code{fail2ban} will apply.
+
+For details of field meanings, please refer to @code{fail2ban} documentation.
+
+@item @code{name}
+Required name of this jail configuration.
+
+@item @code{enabled} (default: @code{*unspecified*})
+Either @code{#t} or @code{#f} for @samp{true} and @samp{false} respectively.
+
+@item @code{max-retry} (default: @code{*unspecified*})
+Is the number of failures before a host get banned (e.g. @code{(max-retry 5)}).
+
+@item @code{max-matches} (default: @code{*unspecified*})
+Is the number of matches stored in ticket (resolvable via
+tag @code{<matches>}) in action.
+
+@item @code{find-time} (default: @code{*unspecified*})
+A host is banned if it has geneerated @code{max-retry} during the last
+@code{find-time} seconds (e.g. @code{(find-time "10m")}).
+
+@item @code{ban-time} (default: @code{*unspecified*})
+Is the number of seconds that a host is banned (e.g. @code{(ban-time "10m")}).
+
+@item @code{ban-time-increment} (default: @code{*unspecified*})
+Allows to use database for searching of previously banned ip's to increase a
+default ban time using special formula.
+
+@item @code{ban-time-factor} (default: @code{*unspecified*})
+Is a coefficient to calculate exponent growing of the formula or common multiplier.
+
+@item @code{ban-time-formula} (default: @code{*unspecified*})
+Used by default to calculate next value of ban time.
+
+@item @code{ban-time-multipliers} (default: @code{*unspecified*})
+Used to calculate next value of ban time instead of formula.
+
+@item @code{ban-time-maxtime} (default: @code{*unspecified*})
+Is the max number of seconds using the ban time can reach (doesn't grow further).
+
+@item @code{ban-time-rndtime} (default: @code{*unspecified*})
+Is the max number of seconds using for mixing with random time
+to prevent ``clever'' botnets calculate exact time IP can be unbanned again.
+
+@item @code{ban-time-overalljails} (default: @code{*unspecified*})
+Either @code{#t} or @code{#f} for @samp{true} and @samp{false} respectively.
+@itemize
+@item @code{true} - specifies the search of IP in the database will be executed cross over all jails
+@item @code{false} - only current jail of the ban IP will be searched
+@end itemize
+
+@item @code{ignore-command} (default: @code{*unspecified*})
+External command that will take an tagged arguments to ignore.
+Note: while provided, currently unimplemented in the context of @code{guix}.
+
+@item @code{ignore-self} (default: @code{*unspecified*})
+Specifies whether the local resp. own IP addresses should be ignored.
+
+@item @code{ignore-ip} (default: @code{'()})
+Can be a list of IP addresses, CIDR masks or DNS hosts. @code{fail2ban}
+will not ban a host which matches an address in this list
+
+@item @code{ignore-cache} (default: @code{*unspecified*})
+
+@item @code{filter} (default: @code{*unspecified*})
+Defines the filter to use by the jail, using
+@code{<fail2ban-jail-filter-configuration>}.
+By default jails have names matching their filter name.
+
+@item @code{log-time-zone} (default: @code{*unspecified*})
+
+@item @code{log-encoding} (default: @code{*unspecified*})
+Specifies the encoding of the log files handled by the jail.
+Possible values: @code{'ascii}, @code{'utf-8}, @code{'auto}.
+
+@item @code{log-path} (default: @code{*unspecified*})
+
+@item @code{action} (default: @code{'()})
+List of @code{<fail2ban-jail-action-configuration>}.
+
+@end table
+@end deftp
+
+@deftp {Data Type} fail2ban-jail-filter-configuration
+@code{fail2ban} jail filter configuration.
+@table @asis
+
+@item @code{name}
+Name part required.
+
+@item @code{mode} (default: @code{*unspecified*})
+
+@end table
+@end deftp
+
+@deftp {Data Type} fail2ban-jail-action-configuration
+@code{fail2ban} jail action configuration.
+@table @asis
+
+@item @code{name}
+Name part required.
+
+@item @code{arguments} (default: @code{'()})
+Association list of key value pairs.
+
+@end table
+@end deftp
+
@node Setuid Programs
@section Setuid Programs
diff --git a/gnu/local.mk b/gnu/local.mk
index 07e3497d10..eef7d09fd4 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -50,6 +50,7 @@
# Copyright © 2022 Daniel Meißner <daniel.meissner-i4k@ruhr-uni-bochum.de>
# Copyright © 2022 Remco van 't Veer <remco@remworks.net>
# Copyright © 2022 Artyom V. Poptsov <poptsov.artyom@gmail.com>
+# Copyright © 2022 muradm <mail@muradm.net>
#
# This file is part of GNU Guix.
#
@@ -670,6 +671,7 @@ GNU_SYSTEM_MODULES = \
%D%/services/nfs.scm \
%D%/services/pam-mount.scm \
%D%/services/science.scm \
+ %D%/services/security.scm \
%D%/services/security-token.scm \
%D%/services/shepherd.scm \
%D%/services/sound.scm \
diff --git a/gnu/services/security.scm b/gnu/services/security.scm
new file mode 100644
index 0000000000..db95d68035
--- /dev/null
+++ b/gnu/services/security.scm
@@ -0,0 +1,328 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 muradm <mail@muradm.net>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu services security)
+ #:use-module (guix gexp)
+ #:use-module (guix records)
+ #:use-module (gnu packages admin)
+ #:use-module (ice-9 format)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (gnu services)
+ #:use-module (gnu services shepherd)
+ #:export (fail2ban-ignore-cache-configuration
+ fail2ban-jail-filter-configuration
+ fail2ban-jail-action-configuration
+ fail2ban-jail-configuration
+ fail2ban-configuration
+ fail2ban-service-type
+ fail2ban-jail-service))
+
+(define (fail2ban-section->string name)
+ (format #f "[~a]" name))
+
+(define fail2ban-backend->string
+ (match-lambda
+ ('auto "auto")
+ ('pyinotify "pyinotify")
+ ('gamin "gamin")
+ ('polling "polling")
+ ('systemd "systemd")
+ (unknown (error (format #f "Unknown fail2ban backend: ~a" unknown)))))
+
+(define fail2ban-log-encoding->string
+ (match-lambda
+ ('auto "auto")
+ ('utf-8 "utf-8")
+ ('ascii "ascii")
+ (unknown (error (format #f "Unknown fail2ban log-encoding: ~a" unknown)))))
+
+(define-record-type* <fail2ban-ignore-cache-configuration>
+ fail2ban-ignore-cache-configuration make-fail2ban-ignore-cache-configuration
+ fail2ban-ignore-cache-configuration?
+ (key fail2ban-ignore-cache-configuration-key)
+ (max-count fail2ban-ignore-cache-configuration-max-count)
+ (max-time fail2ban-ignore-cache-configuration-max-time))
+
+(define fail2ban-ignore-cache-configuration->string
+ (match-lambda
+ (($ <fail2ban-ignore-cache-configuration> key max-count max-time)
+ (format #f "key=\"~a\", max-count=~d, max-time=~d" key max-count max-time))))
+
+(define-record-type* <fail2ban-jail-filter-configuration>
+ fail2ban-jail-filter-configuration make-fail2ban-jail-filter-configuration
+ fail2ban-jail-filter-configuration?
+ (name fail2ban-jail-filter-configuration-name)
+ (mode fail2ban-jail-filter-configuration-node
+ (default *unspecified*)))
+
+(define fail2ban-jail-filter-configuration->string
+ (match-lambda
+ (($ <fail2ban-jail-filter-configuration> name mode)
+ (format #f "~a~a"
+ name (if (unspecified? mode) "" (format #f "[mode=~a]" mode))))))
+
+(define-record-type* <fail2ban-jail-action-configuration>
+ fail2ban-jail-action-configuration make-fail2ban-jail-action-configuration
+ fail2ban-jail-action-configuration?
+ (name fail2ban-jail-action-configuration-name)
+ (arguments fail2ban-jail-action-configuration-arguments
+ (default '())))
+
+(define (fail2ban-arguments->string args)
+ (let* ((multi-value
+ (lambda (v)
+ (format #f "\"~a\"" (string-join (map object->string v) ","))))
+ (any-value
+ (lambda (v)
+ (if (list? v) (multi-value v) v)))
+ (key-value
+ (lambda (e)
+ (format #f "~a=~a" (car e) (any-value (cdr e))))))
+ (format #f "~a" (string-join (map key-value args) ","))))
+
+(define fail2ban-jail-action-configuration->string
+ (match-lambda
+ (($ <fail2ban-jail-action-configuration> name arguments)
+ (format #f "~a~a"
+ name (if (null? arguments) ""
+ (format #f "[~a]"
+ (fail2ban-arguments->string arguments)))))))
+
+(define-record-type* <fail2ban-jail-configuration>
+ fail2ban-jail-configuration make-fail2ban-jail-configuration
+ fail2ban-jail-configuration?
+ (name fail2ban-jail-configuration-name)
+ (enabled fail2ban-jail-configuration-enabled
+ (default *unspecified*))
+ (backend fail2ban-jail-configuration-backend
+ (default *unspecified*))
+ (max-retry fail2ban-jail-configuration-max-retry
+ (default *unspecified*))
+ (max-matches fail2ban-jail-configuration-max-matches
+ (default *unspecified*))
+ (find-time fail2ban-jail-configuration-find-time
+ (default *unspecified*))
+ (ban-time fail2ban-jail-configuration-ban-time
+ (default *unspecified*))
+ (ban-time-increment fail2ban-jail-configuration-ban-time-increment
+ (default *unspecified*))
+ (ban-time-factor fail2ban-jail-configuration-ban-time-factor
+ (default *unspecified*))
+ (ban-time-formula fail2ban-jail-configuration-ban-time-formula
+ (default *unspecified*))
+ (ban-time-multipliers fail2ban-jail-configuration-ban-time-multipliers
+ (default *unspecified*))
+ (ban-time-maxtime fail2ban-jail-configuration-ban-time-maxtime
+ (default *unspecified*))
+ (ban-time-rndtime fail2ban-jail-configuration-ban-time-rndtime
+ (default *unspecified*))
+ (ban-time-overalljails fail2ban-jail-configuration-ban-time-overalljails
+ (default *unspecified*))
+ (ignore-command fail2ban-jail-configuration-ignore-command
+ (default *unspecified*))
+ (ignore-self fail2ban-jail-configuration-ignore-self
+ (default *unspecified*))
+ (ignore-ip fail2ban-jail-configuration-ignore-ip
+ (default '()))
+ (ignore-cache fail2ban-jail-configuration-ignore-cache
+ (default *unspecified*))
+ (filter fail2ban-jail-configuration-filter
+ (default *unspecified*))
+ (log-time-zone fail2ban-jail-configuration-log-time-zone
+ (default *unspecified*))
+ (log-encoding fail2ban-jail-configuration-log-encoding
+ (default *unspecified*))
+ (log-path fail2ban-jail-configuration-log-path
+ (default *unspecified*))
+ (action fail2ban-jail-configuration-action
+ (default '())))
+
+(define fail2ban-jail-configuration->string
+ (match-lambda
+ (($ <fail2ban-jail-configuration> name enabled backend
+ max-retry max-matches
+ find-time ban-time
+ ban-time-increment ban-time-factor
+ ban-time-formula ban-time-multipliers
+ ban-time-maxtime ban-time-rndtime
+ ban-time-overalljails
+ ignore-command ignore-self
+ ignore-ip ignore-cache
+ fltr
+ log-time-zone log-encoding log-path
+ action)
+ (string-join
+ (filter
+ (lambda (s) (not (unspecified? s)))
+ (list
+ (fail2ban-section->string name)
+ (unless (unspecified? enabled)
+ (format #f "enabled = ~a"
+ (if enabled "true" "false")))
+ (unless (unspecified? backend)
+ (format #f "backend = ~a"
+ (fail2ban-backend->string backend)))
+ (unless (unspecified? max-retry)
+ (format #f "maxretry = ~d" max-retry))
+ (unless (unspecified? max-matches)
+ (format #f "maxmatches = ~d" max-matches))
+ (unless (unspecified? find-time)
+ (format #f "findtime = ~a" find-time))
+ (unless (unspecified? ban-time)
+ (format #f "bantime = ~a" ban-time))
+ (unless (unspecified? ban-time-increment)
+ (format #f "bantime.increment = ~a"
+ (if ban-time-increment "true" "false")))
+ (unless (unspecified? ban-time-factor)
+ (format #f "bantime.factor = ~a" ban-time-factor))
+ (unless (unspecified? ban-time-formula)
+ (format #f "bantime.formula = ~a" ban-time-formula))
+ (unless (unspecified? ban-time-multipliers)
+ (format #f "bantime.multipliers = ~a" ban-time-multipliers))
+ (unless (unspecified? ban-time-maxtime)
+ (format #f "bantime.maxtime = ~a" ban-time-maxtime))
+ (unless (unspecified? ban-time-rndtime)
+ (format #f "bantime.rndtime = ~a" ban-time-rndtime))
+ (unless (unspecified? ban-time-overalljails)
+ (format #f "bantime.overalljails = ~a"
+ (if ban-time-overalljails "true" "false")))
+ (unless (unspecified? ignore-command)
+ (format #f "ignorecommand = ~a" ignore-command))
+ (unless (unspecified? ignore-self)
+ (format #f "ignoreself = ~a"
+ (if ignore-self "true" "false")))
+ (unless (null? ignore-ip)
+ (format #f "ignoreip = ~a" (string-join ignore-ip " ")))
+ (unless (unspecified? ignore-cache)
+ (format #f "ignorecache = ~a"
+ (fail2ban-ignore-cache-configuration->string
+ ignore-cache)))
+ (unless (unspecified? fltr)
+ (format #f "filter = ~a"
+ (fail2ban-jail-filter-configuration->string fltr)))
+ (unless (unspecified? log-time-zone)
+ (format #f "logtimezone = ~a" log-time-zone))
+ (unless (unspecified? log-encoding)
+ (format #f "logencoding = ~a"
+ (fail2ban-log-encoding->string log-encoding)))
+ (unless (unspecified? log-path)
+ (format #f "logpath = ~a" log-path))
+ (unless (null? action)
+ (format #f "action = ~a"
+ (string-join
+ (map fail2ban-jail-action-configuration->string action)
+ "\n")))))
+ "\n"))))
+
+(define-record-type* <fail2ban-configuration>
+ fail2ban-configuration make-fail2ban-configuration
+ fail2ban-configuration?
+ (fail2ban fail2ban-configuration-fail2ban
+ (default fail2ban))
+ (run-directory fail2ban-configuration-run-directory
+ (default "/var/run/fail2ban"))
+ (jails fail2ban-configuration-jails
+ (default '()))
+ (extra-jails fail2ban-configuration-extra-jails
+ (default '()))
+ (extra-content fail2ban-configuration-extra-content
+ (default "")))
+
+(define (fail2ban-configuration->string config)
+ (let* ((jails (fail2ban-configuration-jails config))
+ (extra-jails (fail2ban-configuration-extra-jails config))
+ (extra-content (fail2ban-configuration-extra-content config)))
+ (string-append
+ (string-join
+ (map fail2ban-jail-configuration->string
+ (append jails extra-jails))
+ "\n")
+ "\n" extra-content "\n")))
+
+(define (make-fail2ban-configuration-package config)
+ (let* ((fail2ban (fail2ban-configuration-fail2ban config))
+ (jail-local
+ (plain-file "jail.local"
+ (fail2ban-configuration->string config))))
+ (computed-file
+ "fail2ban-configuration"
+ (with-imported-modules '((guix build utils))
+ #~(begin
+ (use-modules (guix build utils))
+ (let* ((out (ungexp output)))
+ (mkdir-p (string-append out "/etc/fail2ban"))
+ (copy-recursively
+ (string-append #$fail2ban "/etc/fail2ban")
+ (string-append out "/etc/fail2ban"))
+ (symlink
+ #$jail-local
+ (string-append out "/etc/fail2ban/jail.local"))))))))
+
+(define (fail2ban-shepherd-service config)
+ (match-record config <fail2ban-configuration>
+ (fail2ban run-directory)
+ (let* ((fail2ban-server (file-append fail2ban "/bin/fail2ban-server"))
+ (pid-file (in-vicinity run-directory "fail2ban.pid"))
+ (socket-file (in-vicinity run-directory "fail2ban.sock"))
+ (config-dir (make-fail2ban-configuration-package config))
+ (config-dir (file-append config-dir "/etc/fail2ban"))
+ (fail2ban-action
+ (lambda args
+ #~(lambda _
+ (invoke #$fail2ban-server
+ "-c" #$config-dir
+ "-p" #$pid-file
+ "-s" #$socket-file
+ "-b"
+ #$@args)))))
+
+ ;; TODO: Add 'reload' action.
+ (list (shepherd-service
+ (provision '(fail2ban))
+ (documentation "Run the fail2ban daemon.")
+ (requirement '(user-processes))
+ (modules `((ice-9 match)
+ ,@%default-modules))
+ (start (fail2ban-action "start"))
+ (stop (fail2ban-action "stop")))))))
+
+(define fail2ban-service-type
+ (service-type (name 'fail2ban)
+ (extensions
+ (list (service-extension shepherd-root-service-type
+ fail2ban-shepherd-service)))
+ (compose concatenate)
+ (extend (lambda (config jails)
+ (fail2ban-configuration
+ (inherit config)
+ (jails
+ (append
+ (fail2ban-configuration-jails config)
+ jails)))))
+ (default-value (fail2ban-configuration))
+ (description "Run the fail2ban server.")))
+
+(define (fail2ban-jail-service svc-type jail)
+ (service-type
+ (inherit svc-type)
+ (extensions
+ (append
+ (service-type-extensions svc-type)
+ (list (service-extension fail2ban-service-type
+ (lambda _ (list jail))))))))
--
2.36.1
next reply other threads:[~2022-07-17 2:33 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-07-17 2:32 muradm [this message]
2022-08-03 16:09 ` [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type Maxim Cournoyer
2022-08-22 17:26 ` [bug#56608] [PATCH v2 0/2] " muradm
2022-08-22 17:26 ` [bug#56608] [PATCH v2 1/2] gnu: security: " muradm
2022-08-22 18:53 ` Maxim Cournoyer
2022-08-23 18:22 ` muradm
2022-08-22 17:26 ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
2022-08-22 19:13 ` Maxim Cournoyer
2022-08-23 18:51 ` muradm
2022-08-23 20:13 ` [bug#56608] [PATCH v3] gnu: security: Add fail2ban-service-type muradm
2022-08-29 2:01 ` bug#56608: " Maxim Cournoyer
2022-08-23 20:19 ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220717023223.440-1-mail@muradm.net \
--to=mail@muradm.net \
--cc=56608@debbugs.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/guix.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.