From 1bdeec5d66cfeea3a5fc5d69690dd32cb32ee104 Mon Sep 17 00:00:00 2001 From: muradm Date: Sun, 17 Dec 2023 15:49:21 +0300 Subject: services: connman: Add 'connman-general-configuration'. Currently connman has no main.conf as specified in 'man 5 connman.conf' which would allow setting NetworkInterfaceBalcklist and other useful options. This patch adds connman-general-configuration, serializes it and passes to connmad with --config= flag. All configuration fields are 'maybe-*' deliberately, to not disturb current users and not require supporting configuration changes for connmand. * gnu/services/networking.scm (): New configuration record to represent main.conf for connmand. ()[general-configuration]: New field. (connman-shepherd-service): Honor it. *doc/guix.texi (Networking Services): Add generated configuration. Change-Id: I5d78f49e8b2d5e0b3cbd7b8b604e8a254b6397e8 Signed-off-by: Maxim Cournoyer Modified-by: Maxim Cournoyer --- gnu/services/networking.scm | 255 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 252 insertions(+), 3 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm index 7c114fa53c..495d049728 100644 --- a/gnu/services/networking.scm +++ b/gnu/services/networking.scm @@ -21,6 +21,7 @@ ;;; Copyright © 2022, 2023 Andrew Tropin ;;; Copyright © 2023 Declan Tsien ;;; Copyright © 2023 Bruno Victal +;;; Copyright © 2023 muradm ;;; ;;; This file is part of GNU Guix. ;;; @@ -78,6 +79,7 @@ #:use-module (srfi srfi-26) #:use-module (srfi srfi-43) #:use-module (ice-9 match) + #:use-module (ice-9 string-fun) #:use-module (json) #:re-export (static-networking-service static-networking-service-type) @@ -171,6 +173,8 @@ network-manager-configuration-vpn-plugins network-manager-service-type + connman-general-configuration + connman-general-configuration? connman-configuration connman-configuration? connman-configuration-connman @@ -1326,6 +1330,241 @@ wireless networking.")))) ;;; Connman ;;; +(define (connman-general-configuration-field-name field-name) + (let* ((str->camel (lambda (s) + (string-concatenate + (map string-capitalize (string-split s #\-))))) + (str (if (symbol? field-name) + (str->camel (symbol->string field-name)) + field-name))) + (cond + ((string-suffix? "?" str) (connman-general-configuration-field-name + (string-drop-right str 1))) + ((string-contains str "RegulatoryDomain") (connman-general-configuration-field-name + (string-replace-substring str "RegulatoryDomain" "Regdom"))) + ((string-contains str "Url") (connman-general-configuration-field-name + (string-replace-substring str "Url" "URL"))) + ((string-contains str "Ip") (connman-general-configuration-field-name + (string-replace-substring str "Ip" "IP"))) + ((string-contains str "6To4") (connman-general-configuration-field-name + (string-replace-substring str "6To4" "6to4"))) + (#t str)))) + +(define (connman-general-configuration-serialize-string field-name value) + (let ((param (connman-general-configuration-field-name field-name))) + #~(string-append #$param " = " #$value "\n"))) + +(define (connman-general-configuration-serialize-number field-name value) + (connman-general-configuration-serialize-string + field-name (number->string value))) + +(define (connman-general-configuration-serialize-list field-name value) + (connman-general-configuration-serialize-string + field-name (string-join value ","))) + +(define (connman-general-configuration-serialize-boolean field-name value) + (connman-general-configuration-serialize-string + field-name (if value "true" "false"))) + +(define-maybe boolean (prefix connman-general-configuration-)) +(define-maybe number (prefix connman-general-configuration-)) +(define-maybe string (prefix connman-general-configuration-)) +(define-maybe list (prefix connman-general-configuration-)) + +(define-configuration connman-general-configuration + (input-request-timeout + maybe-number + "Set input request timeout. Default is 120 seconds. The request for inputs +like passphrase will timeout after certain amount of time. Use this setting to +increase the value in case of different user interface designs.") + (browser-launch-timeout + maybe-number + "Set browser launch timeout. Default is 300 seconds. The request for +launching a browser for portal pages will timeout after certain amount of +time. Use this setting to increase the value in case of different user +interface designs.") + (background-scanning? + maybe-boolean + "Enable background scanning. Default is true. If wifi is disconnected, the +background scanning will follow a simple back off mechanism from 3s up to 5 +minutes. Then, it will stay in 5 minutes unless user specifically asks for +scanning through a D-Bus call. If so, the mechanism will start again from +3s. This feature activates also the background scanning while being connected, +which is required for roaming on wifi. When @code{background-scanning?} is false, +ConnMan will not perform any scan regardless of wifi is connected or not, +unless it is requested by the user through a D-Bus call.") + (use-gateways-as-timeservers? + maybe-boolean + "Assume that service gateways also function as timeservers. Default is false.") + (fallback-timeservers + maybe-list + "List of Fallback timeservers. These timeservers are used for NTP sync +when there are no timeservers set by the user or by the service, and when +@code{use-gateways-as-timeservers?} is @code{#f}. These can contain a mixed +combination of fully qualified domain names, IPv4 and IPv6 addresses.") + (fallback-nameservers + maybe-list + "List of fallback nameservers appended to the list of nameservers given +by the service. The nameserver entries must be in numeric format, +host names are ignored.") + (default-auto-connect-technologies + maybe-list + "List of technologies that are marked autoconnectable by default. The +default value for this entry when empty is @code{\"ethernet\"}, @code{\"wifi\"}, +@code{\"cellular\"}. Services that are automatically connected must have been +set up and saved to storage beforehand.") + (default-favourite-technologies + maybe-list + "List of technologies that are marked favorite by default. The default +value for this entry when empty is @code{\"ethernet\"}. Connects to services +from this technology even if not setup and saved to storage.") + (always-connected-technologies + maybe-list + "List of technologies which are always connected regardless of +preferred-technologies setting (@code{auto-connect?} @code{#t}). The default +value is empty and this feature is disabled unless explicitly enabled.") + (preferred-technologies + maybe-list + "List of preferred technologies from the most preferred one to the least +preferred one. Services of the listed technology type will be tried one by +one in the order given, until one of them gets connected or they are all +tried. A service of a preferred technology type in state 'ready' will get +the default route when compared to another preferred type further down the +list with state 'ready' or with a non-preferred type; a service of a +preferred technology type in state 'online' will get the default route when +compared to either a non-preferred type or a preferred type further down +in the list.") + (network-interface-blacklist + maybe-list + "List of blacklisted network interfaces. Found interfaces will be +compared to the list and will not be handled by ConnMan, if their first +characters match any of the list entries. Default value is @code{\"vmnet\"}, +@code{\"vboxnet\"}, @code{\"virbr\"}, @code{\"ifb\"}.") + (allow-hostname-updates? + maybe-boolean + "Allow ConnMan to change the system hostname. This can happen for +example if we receive DHCP hostname option. Default value is @code{#t}.") + (allow-domainname-updates? + maybe-boolean + "Allow connman to change the system domainname. This can happen for +example if we receive DHCP domainname option. Default value is @code{#t}.") + (single-connected-technology? + maybe-boolean + "Keep only a single connected technology at any time. When a new +service is connected by the user or a better one is found according to +preferred-technologies, the new service is kept connected and all the +other previously connected services are disconnected. With this setting +it does not matter whether the previously connected services are +in 'online' or 'ready' states, the newly connected service is the only +one that will be kept connected. A service connected by the user will +be used until going out of network coverage. With this setting enabled +applications will notice more network breaks than normal. Note this +options can't be used with VPNs. Default value is @code{#f}.") + (tethering-technologies + maybe-list + "List of technologies that are allowed to enable tethering. The +default value is @code{\"wifi\"}, @code{\"bluetooth\"}, +@code{\"gadget\"}. Only those technologies listed here are used for +tethering. If one wants to tether ethernet, then add @code{\"ethernet\"} +in the list. Note that if ethernet tethering is enabled, then a DHCP +server is started on all ethernet interfaces. Tethered ethernet should +never be connected to corporate or home network as it will disrupt normal +operation of these networks. Due to this ethernet is not tethered by +default. Do not activate ethernet tethering unless you really know +what you are doing.") + (persistent-tethering-mode? + maybe-boolean + "Restore earlier tethering status when returning from offline mode, +re-enabling a technology, and after restarts and reboots. Default +value is @code{#f}.") + (enable-6to4? + maybe-boolean + "Automatically enable anycast 6to4 if possible. This is not +recommended, as the use of 6to4 will generally lead to a severe +degradation of connection quality. See RFC6343. Default value +is @code{#f} (as recommended by RFC6343 section 4.1).") + (vendor-class-id + maybe-string + "Set DHCP option 60 (Vendor Class ID) to the given string. This +option can be used by DHCP servers to identify specific clients +without having to rely on MAC address ranges, etc.") + (enable-online-check? + maybe-boolean + "Enable or disable use of HTTP GET as an online status check. When +a service is in a READY state, and is selected as default, ConnMan will +issue an HTTP GET request to verify that end-to-end connectivity is +successful. Only then the service will be transitioned to ONLINE +state. If this setting is false, the default service will remain +in READY state. Default value is @code{#t}.") + (online-check-ipv4-url + maybe-string + "IPv4 URL used during the online status check. Please refer to +the README for more detailed information. Default value is +@url{http://ipv4.connman.net/online/status.html}.") + (online-check-ipv6-url + maybe-string + "IPv6 URL used during the online status check. Please refer to +the README for more detailed information. Default value is +@url{http://ipv6.connman.net/online/status.html}.") + (online-check-initial-interval + maybe-number + "Range of intervals between two online check requests. Please +refer to the README for more detailed information. Default value +is @samp{1}.") + (online-check-max-interval + maybe-number + "Range of intervals between two online check requests. Please +refer to the README for more detailed information. Default value +is @samp{1}.") + (enable-online-to-ready-transition? + maybe-boolean + "WARNING: This is an experimental feature. In addition to +@code{enable-online-check} setting, enable or disable use of HTTP GET +to detect the loss of end-to-end connectivity. If this setting is +@code{#f}, when the default service transitions to ONLINE state, the +HTTP GET request is no more called until next cycle, initiated by a +transition of the default service to DISCONNECT state. If this +setting is @code{#t}, the HTTP GET request keeps being called to +guarantee that end-to-end connectivity is still successful. If not, +the default service will transition to READY state, enabling another +service to become the default one, in replacement. Default value +is @code{#f}.") + (auto-connect-roaming-services? + maybe-boolean + "Automatically connect roaming services. This is not recommended +unless you know you won't have any billing problem. Default value +is @code{#f}.") + (address-conflict-detection? + maybe-boolean + "Enable or disable the implementation of IPv4 address conflict +detection according to RFC5227. ConnMan will send probe ARP packets +to see if an IPv4 address is already in use before assigning the +address to an interface. If an address conflict occurs for a +statically configured address, an IPv4LL address will be chosen +instead (according to RFC3927). If an address conflict occurs for +an address offered via DHCP, ConnMan sends a DHCP DECLINE once +and for the second conflict resorts to finding an IPv4LL +address. Default value is @code{#f}.") + (localtime + maybe-string + "Path to localtime file. Defaults to @file{/etc/localtime}.") + (regulatory-domain-follows-timezone? + maybe-boolean + "Enable regulatory domain to be changed along timezone changes. +With this option set to true each time the timezone changes the first +present ISO3166 country code is read from +@file{/usr/share/zoneinfo/zone1970.tab} and set as regulatory domain +value. Default value is @code{#f}.") + (resolv-conf + maybe-string + "Path to resolv.conf file. If the file does not exist, but +intermediate directories exist, it will be created. If this option +is not set, it tries to write into @file{/var/run/connman/resolv.conf} +if it fails (@file{/var/run/connman} does not exist or is not +writeable). If you do not want to update resolv.conf, you can +set @file{/dev/null}.") + (prefix connman-general-configuration-)) + (define-record-type* connman-configuration make-connman-configuration connman-configuration? @@ -1337,7 +1576,9 @@ wireless networking.")))) (default #f)) (iwd? connman-configuration-iwd? (default #f) - (sanitize warn-iwd?-field-deprecation))) + (sanitize warn-iwd?-field-deprecation)) + (general-configuration connman-configuration-general-configuration + (default (connman-general-configuration)))) (define (connman-activation config) (let ((disable-vpn? (connman-configuration-disable-vpn? config))) @@ -1350,10 +1591,17 @@ wireless networking.")))) (define (connman-shepherd-service config) (match-record config (connman shepherd-requirement - disable-vpn? iwd?) + disable-vpn? iwd? + general-configuration) (let ((iwd? (or iwd? ; TODO: deprecated field, remove later. (and shepherd-requirement - (memq 'iwd shepherd-requirement))))) + (memq 'iwd shepherd-requirement)))) + (config (mixed-text-file + "main.conf" + "[General]\n" + (serialize-configuration + general-configuration + connman-general-configuration-fields)))) (list (shepherd-service (documentation "Run Connman") (provision '(connman networking)) @@ -1365,6 +1613,7 @@ wireless networking.")))) (start #~(make-forkexec-constructor (list (string-append #$connman "/sbin/connmand") + (string-append "--config=" #$config) "--nodaemon" "--nodnsproxy" #$@(if disable-vpn? '("--noplugin=vpn") '()) -- cgit v1.2.3 From 94abfccde9c22adf16a265ff98f31cc36bd8a622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= Date: Thu, 18 Jan 2024 15:00:52 +0100 Subject: =?UTF-8?q?services:=20cuirass:=20Add=20=E2=80=98configuration?= =?UTF-8?q?=E2=80=99=20action.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/cuirass.scm (cuirass-shepherd-service): Add ‘actions’ field to ‘cuirass’ Shepherd service. Change-Id: I7feaeebcb89fcd9a1fdbc63f0337e0d63b697793 --- gnu/services/cuirass.scm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm index fcbd5e08a5..bd3a48f629 100644 --- a/gnu/services/cuirass.scm +++ b/gnu/services/cuirass.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2016 Mathieu Lirzin -;;; Copyright © 2016-2023 Ludovic Courtès +;;; Copyright © 2016-2024 Ludovic Courtès ;;; Copyright © 2017, 2020 Mathieu Othacehe ;;; Copyright © 2017 Jan Nieuwenhuizen ;;; Copyright © 2018, 2019 Ricardo Wurmus @@ -136,7 +136,9 @@ (database (cuirass-configuration-database config)) (port (cuirass-configuration-port config)) (host (cuirass-configuration-host config)) - (specs (cuirass-configuration-specifications config)) + (config-file (scheme-file + "cuirass-specs.scm" + (cuirass-configuration-specifications config))) (use-substitutes? (cuirass-configuration-use-substitutes? config)) (one-shot? (cuirass-configuration-one-shot? config)) (fallback? (cuirass-configuration-fallback? config)) @@ -149,8 +151,7 @@ (list (string-append #$cuirass "/bin/cuirass") "register" "--cache-directory" #$cache-directory - "--specifications" - #$(scheme-file "cuirass-specs.scm" specs) + "--specifications" #$config-file "--database" #$database "--interval" #$(number->string interval) #$@(if parameters @@ -172,7 +173,8 @@ #:user #$user #:group #$group #:log-file #$main-log-file)) - (stop #~(make-kill-destructor))) + (stop #~(make-kill-destructor)) + (actions (list (shepherd-configuration-action config-file)))) ,(shepherd-service (documentation "Run Cuirass web interface.") (provision '(cuirass-web)) -- cgit v1.2.3 From 96c2186a084cbcb3d3b839d21027488a023c76f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= Date: Fri, 19 Jan 2024 15:51:05 +0100 Subject: =?UTF-8?q?services:=20cuirass:=20Depend=20on=20=E2=80=98user-proc?= =?UTF-8?q?esses=E2=80=99.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/cuirass.scm (cuirass-shepherd-service) (cuirass-remote-worker-shepherd-service): Add ‘user-processes’ to the ‘requirement’ field. Change-Id: Iba087bfd8aaa9b1ef54bcc77d855adc136e18644 --- gnu/services/cuirass.scm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm index bd3a48f629..bcdbffa2f3 100644 --- a/gnu/services/cuirass.scm +++ b/gnu/services/cuirass.scm @@ -146,7 +146,9 @@ `(,(shepherd-service (documentation "Run Cuirass.") (provision '(cuirass)) - (requirement '(guix-daemon postgres postgres-roles networking)) + (requirement '(user-processes + guix-daemon + postgres postgres-roles networking)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "register" @@ -178,7 +180,7 @@ ,(shepherd-service (documentation "Run Cuirass web interface.") (provision '(cuirass-web)) - (requirement '(cuirass)) + (requirement '(user-processes cuirass)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "web" @@ -204,7 +206,7 @@ (shepherd-service (documentation "Run Cuirass remote build server.") (provision '(cuirass-remote-server)) - (requirement '(avahi-daemon cuirass)) + (requirement '(user-processes avahi-daemon cuirass)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "remote-server" @@ -375,7 +377,7 @@ CONFIG." (list (shepherd-service (documentation "Run Cuirass remote build worker.") (provision '(cuirass-remote-worker)) - (requirement '(avahi-daemon guix-daemon networking)) + (requirement '(user-processes avahi-daemon guix-daemon networking)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "remote-worker" -- cgit v1.2.3 From dde76db33fbdede6bf26821f8c21cca7df45e79e Mon Sep 17 00:00:00 2001 From: Attila Lendvai Date: Thu, 25 Jan 2024 16:05:41 +0100 Subject: services: shepherd: Add respawn-limit and respawn-delay. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/shepherd.scm (): Add respawn-limit and respawn-delay. (shepherd-service-file): Emit the two values into the shepherd service constructor form. Change-Id: I54408e8fb4bcc0956d9610771bf5c566fdc2914c Signed-off-by: Ludovic Courtès --- gnu/services/shepherd.scm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'gnu/services') diff --git a/gnu/services/shepherd.scm b/gnu/services/shepherd.scm index 5ebac129ce..f5bcde721f 100644 --- a/gnu/services/shepherd.scm +++ b/gnu/services/shepherd.scm @@ -55,6 +55,8 @@ shepherd-service-canonical-name shepherd-service-requirement shepherd-service-one-shot? + shepherd-service-respawn-limit + shepherd-service-respawn-delay shepherd-service-respawn? shepherd-service-start shepherd-service-stop @@ -211,6 +213,10 @@ DEFAULT is given, use it as the service's default value." (default #f)) (respawn? shepherd-service-respawn? ;Boolean (default #t)) + (respawn-limit shepherd-service-respawn-limit + (default #f)) + (respawn-delay shepherd-service-respawn-delay + (default #f)) (start shepherd-service-start) ;g-expression (procedure) (stop shepherd-service-stop ;g-expression (procedure) (default #~(const #f))) @@ -309,6 +315,14 @@ stored." #:one-shot? '#$(shepherd-service-one-shot? service) #:respawn? '#$(shepherd-service-respawn? service) + #$@(if (shepherd-service-respawn-limit service) + `(#:respawn-limit + ,(shepherd-service-respawn-limit service)) + '()) + #$@(if (shepherd-service-respawn-delay service) + `(#:respawn-delay + ,(shepherd-service-respawn-delay service)) + '()) #:start #$(shepherd-service-start service) #:stop #$(shepherd-service-stop service) #:actions -- cgit v1.2.3 From a2b1ef903be001d5abfc47fc3e8add04fb748ff3 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 31 Jan 2024 11:46:22 +0000 Subject: services: certbot: Symlink certificates to /etc/certs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/certbot.scm (certbot-deploy-hook): New procedure. (certbot-command): Pass new deploy hook to certbot. * doc/guix.texi: Replace "letsencrypt/live" with "certs" throughout, except in the certbot deploy-hook description. Change-Id: I2ba5e4903d1e293e566b732a84b07d5a134b697d Signed-off-by: Clément Lassieur --- gnu/services/certbot.scm | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/certbot.scm b/gnu/services/certbot.scm index 0c45471659..3926d0551a 100644 --- a/gnu/services/certbot.scm +++ b/gnu/services/certbot.scm @@ -6,6 +6,7 @@ ;;; Copyright © 2020 Jack Hill ;;; Copyright © 2020 Tobias Geerinckx-Rice ;;; Copyright © 2021 Raghav Gururajan +;;; Copyright © 2024 Carlo Zancanaro ;;; ;;; This file is part of GNU Guix. ;;; @@ -87,6 +88,35 @@ (body (list "return 301 https://$host$request_uri;")))))) +(define (certbot-deploy-hook name deploy-hook-script) + "Returns a gexp which creates symlinks for privkey.pem and fullchain.pem +from /etc/certs/NAME to /etc/letsenctypt/live/NAME. If DEPLOY-HOOK-SCRIPT is +not #f then it is run after the symlinks have been created." + (program-file + (string-append name "-deploy-hook") + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (mkdir-p #$(string-append "/etc/certs/" name)) + (chmod #$(string-append "/etc/certs/" name) #o755) + + ;; Create new symlinks + (symlink #$(string-append + "/etc/letsencrypt/live/" name "/privkey.pem") + #$(string-append "/etc/certs/" name "/privkey.pem.new")) + (symlink #$(string-append + "/etc/letsencrypt/live/" name "/fullchain.pem") + #$(string-append "/etc/certs/" name "/fullchain.pem.new")) + + ;; Rename over the top of the old ones, if there are any. + (rename-file #$(string-append "/etc/certs/" name "/privkey.pem.new") + #$(string-append "/etc/certs/" name "/privkey.pem")) + (rename-file #$(string-append "/etc/certs/" name "/fullchain.pem.new") + #$(string-append "/etc/certs/" name "/fullchain.pem")) + #$@(if deploy-hook-script + (list #~(invoke #$deploy-hook-script)) + '()))))) + (define certbot-command (match-lambda (($ package webroot certificates email @@ -118,7 +148,8 @@ `("--manual-auth-hook" ,authentication-hook) '()) (if cleanup-hook `("--manual-cleanup-hook" ,cleanup-hook) '()) - (if deploy-hook `("--deploy-hook" ,deploy-hook) '())) + (list "--deploy-hook" + (certbot-deploy-hook name deploy-hook))) (append (list name certbot "certonly" "-n" "--agree-tos" "--webroot" "-w" webroot @@ -130,7 +161,8 @@ '("--register-unsafely-without-email")) (if server `("--server" ,server) '()) (if rsa-key-size `("--rsa-key-size" ,rsa-key-size) '()) - (if deploy-hook `("--deploy-hook" ,deploy-hook) '())))))) + (list "--deploy-hook" + (certbot-deploy-hook name deploy-hook))))))) certificates))) (program-file "certbot-command" -- cgit v1.2.3 From fc0ec9a3cc2707260b88c79286e91fa1a3a594cb Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 31 Jan 2024 11:46:23 +0000 Subject: services: certbot: Create self-signed certificates before certbot runs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/certbot.scm (): Add start-self-signed? field. (generate-certificate-gexp): New procedure. (certbot-activation): Generate self-signed certificates when start-self-signed? is #t. * doc/guix.texi (Certificate services): Document start-self-signed?. Change-Id: Icfd85ae0c3e29324acbcde6ba283546cf0e27a1d Signed-off-by: Clément Lassieur --- gnu/services/certbot.scm | 62 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/certbot.scm b/gnu/services/certbot.scm index 3926d0551a..10b99f5630 100644 --- a/gnu/services/certbot.scm +++ b/gnu/services/certbot.scm @@ -35,6 +35,7 @@ #:use-module (guix records) #:use-module (guix gexp) #:use-module (srfi srfi-1) + #:use-module (ice-9 format) #:use-module (ice-9 match) #:export (certbot-service-type certbot-configuration @@ -64,7 +65,9 @@ (cleanup-hook certificate-cleanup-hook (default #f)) (deploy-hook certificate-configuration-deploy-hook - (default #f))) + (default #f)) + (start-self-signed? certificate-configuration-start-self-signed? + (default #t))) (define-record-type* certbot-configuration make-certbot-configuration @@ -91,7 +94,10 @@ (define (certbot-deploy-hook name deploy-hook-script) "Returns a gexp which creates symlinks for privkey.pem and fullchain.pem from /etc/certs/NAME to /etc/letsenctypt/live/NAME. If DEPLOY-HOOK-SCRIPT is -not #f then it is run after the symlinks have been created." +not #f then it is run after the symlinks have been created. This wrapping is +necessary for certificates with start-self-signed? set to #t, as it will +overwrite the initial self-signed certificates upon the first successful +deploy." (program-file (string-append name "-deploy-hook") (with-imported-modules '((guix build utils)) @@ -108,7 +114,8 @@ not #f then it is run after the symlinks have been created." "/etc/letsencrypt/live/" name "/fullchain.pem") #$(string-append "/etc/certs/" name "/fullchain.pem.new")) - ;; Rename over the top of the old ones, if there are any. + ;; Rename over the top of the old ones, just in case they were the + ;; original self-signed certificates. (rename-file #$(string-append "/etc/certs/" name "/privkey.pem.new") #$(string-append "/etc/certs/" name "/privkey.pem")) (rename-file #$(string-append "/etc/certs/" name "/fullchain.pem.new") @@ -184,6 +191,47 @@ not #f then it is run after the symlinks have been created." #~(job '(next-minute-from (next-hour '(0 12)) (list (random 60))) #$(certbot-command config)))) +(define (generate-certificate-gexp certbot-cert-directory rsa-key-size) + (match-lambda + (($ name (primary-domain other-domains ...) + challenge + csr authentication-hook + cleanup-hook deploy-hook) + (let (;; Arbitrary default subject, with just the + ;; right domain filled in. These values don't + ;; have any real significance. + (subject (string-append + "/C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=" + primary-domain)) + (alt-names (if (null? other-domains) + #f + (format #f "subjectAltName=~{DNS:~a~^,~}" + other-domains))) + (directory (string-append "/etc/certs/" (or name primary-domain)))) + #~(when (not (file-exists? #$directory)) + ;; We generate self-signed certificates in /etc/certs/{domain}, + ;; because certbot is very sensitive to its directory + ;; structure. It refuses to write over the top of existing files, + ;; so we need to use a directory outside of its control. + ;; + ;; These certificates are overwritten by the certbot deploy hook + ;; the first time it successfully obtains a letsencrypt-signed + ;; certificate. + (mkdir-p #$directory) + (chmod #$directory #o755) + (invoke #$(file-append openssl "/bin/openssl") + "req" "-x509" + "-newkey" #$(string-append "rsa:" (or rsa-key-size "4096")) + "-keyout" #$(string-append directory "/privkey.pem") + "-out" #$(string-append directory "/fullchain.pem") + "-sha256" + "-days" "1" ; Only one day, because we expect certbot to run + "-nodes" + "-subj" #$subject + #$@(if alt-names + (list "-addext" alt-names) + (list)))))))) + (define (certbot-activation config) (let* ((certbot-directory "/var/lib/certbot") (certbot-cert-directory "/etc/letsencrypt/live") @@ -198,6 +246,14 @@ not #f then it is run after the symlinks have been created." (mkdir-p #$webroot) (mkdir-p #$certbot-directory) (mkdir-p #$certbot-cert-directory) + + #$@(let ((rsa-key-size (and rsa-key-size + (number->string rsa-key-size)))) + (map (generate-certificate-gexp certbot-cert-directory + rsa-key-size) + (filter certificate-configuration-start-self-signed? + certificates))) + (copy-file #$(certbot-command config) #$script) (display #$message))))))) -- cgit v1.2.3 From d4a4b12f0ac52563254d34dc1e26030b354d3f73 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 31 Jan 2024 11:46:24 +0000 Subject: services: certbot: Reload nginx in deploy hook. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/certbot.scm (certbot-deploy-hook): Reload nginx. * doc/guix.texi (Certificate services): Remove deploy-hook from example. Change-Id: Ibb10481170a6fda7df72492072b939dd6a6ad176 Signed-off-by: Clément Lassieur --- gnu/services/certbot.scm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/certbot.scm b/gnu/services/certbot.scm index 10b99f5630..cb1be0c0e9 100644 --- a/gnu/services/certbot.scm +++ b/gnu/services/certbot.scm @@ -100,9 +100,11 @@ overwrite the initial self-signed certificates upon the first successful deploy." (program-file (string-append name "-deploy-hook") - (with-imported-modules '((guix build utils)) + (with-imported-modules '((gnu services herd) + (guix build utils)) #~(begin - (use-modules (guix build utils)) + (use-modules (gnu services herd) + (guix build utils)) (mkdir-p #$(string-append "/etc/certs/" name)) (chmod #$(string-append "/etc/certs/" name) #o755) @@ -120,6 +122,10 @@ deploy." #$(string-append "/etc/certs/" name "/privkey.pem")) (rename-file #$(string-append "/etc/certs/" name "/fullchain.pem.new") #$(string-append "/etc/certs/" name "/fullchain.pem")) + + ;; With the new certificates in place, tell nginx to reload them. + (with-shepherd-action 'nginx ('reload) result result) + #$@(if deploy-hook-script (list #~(invoke #$deploy-hook-script)) '()))))) -- cgit v1.2.3 From 023c3e0ac44e7fc35eeebc87535a47df2cd01485 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 31 Jan 2024 11:46:25 +0000 Subject: services: certbot: Add one-shot service to renew certificates. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gnu/services/certbot.scm (certbot-renewal-one-shot): New procedure. (certbot-service-type)[extensions]: Add it to shepherd-root extension. (certbot-command): Make connection errors return a different exit code. (certbot-activation): Remove message with certificate renewal instructions. Change-Id: I614ac6214a753dba0396e2385a75926c8355caa1 Signed-off-by: Clément Lassieur --- gnu/services/certbot.scm | 89 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 14 deletions(-) (limited to 'gnu/services') diff --git a/gnu/services/certbot.scm b/gnu/services/certbot.scm index cb1be0c0e9..f287c8367f 100644 --- a/gnu/services/certbot.scm +++ b/gnu/services/certbot.scm @@ -180,15 +180,45 @@ deploy." (program-file "certbot-command" #~(begin - (use-modules (ice-9 match)) - (let ((code 0)) + (use-modules (ice-9 match) + (ice-9 textual-ports)) + + (define (log format-string . args) + (apply format #t format-string args) + (force-output)) + + (define (file-contains? file string) + (string-contains (call-with-input-file file + get-string-all) + string)) + + (define (connection-error?) + ;; Certbot errors are always exit code 1, so we need to look at + ;; the log file to see if there was a connection error. + (file-contains? "/var/log/letsencrypt/letsencrypt.log" + "Failed to establish a new connection")) + + (let ((script-code 0)) (for-each (match-lambda ((name . command) - (begin - (format #t "Acquiring or renewing certificate: ~a~%" name) - (set! code (or (apply system* command) code))))) - '#$commands) code))))))) + (log "Acquiring or renewing certificate: ~a~%" name) + (cond + ((zero? (status:exit-val (apply system* command))) + (log "Certificate successfully acquired: ~a~%" name)) + ((connection-error?) + ;; If we have a connection error, then bail early with + ;; exit code 2. We don't expect this to resolve within the + ;; timespan of this script. + (log "Connection error - bailing out~%") + (exit 2)) + (else + ;; If we have any other type of error, then continue but + ;; exit with a failing status code in the end. + (log "Error: ~a - continuing with other domains~%" name) + (set! script-code 1))))) + '#$commands) + (exit script-code)))))))) (define (certbot-renewal-jobs config) (list @@ -197,6 +227,40 @@ deploy." #~(job '(next-minute-from (next-hour '(0 12)) (list (random 60))) #$(certbot-command config)))) +(define (certbot-renewal-one-shot config) + (list + ;; Renew certificates when the system first starts. This is a one-shot + ;; service, because the mcron configuration will take care of running this + ;; periodically. This is most useful the very first time the system starts, + ;; to overwrite our self-signed certificates as soon as possible without + ;; user intervention. + (shepherd-service + (provision '(renew-certbot-certificates)) + (requirement '(nginx)) + (one-shot? #t) + (start #~(lambda _ + ;; This needs the network, but there's no reliable way to know + ;; if the network is up other than trying. If we fail due to a + ;; connection error we retry a number of times in the hope that + ;; the network comes up soon. + (let loop ((attempt 0)) + (let ((code (status:exit-val + (system* #$(certbot-command config))))) + (cond + ((and (= code 2) ; Exit code 2 means connection error + (< attempt 12)) ; Arbitrarily chosen max attempts + (sleep 10) ; Arbitrarily chosen retry delay + (loop (1+ attempt))) + ((zero? code) + ;; Success! + #t) + (else + ;; Failure. + #f)))))) + (auto-start? #t) + (documentation "Call certbot to renew certificates.") + (actions (list (shepherd-configuration-action (certbot-command config))))))) + (define (generate-certificate-gexp certbot-cert-directory rsa-key-size) (match-lambda (($ name (primary-domain other-domains ...) @@ -240,9 +304,7 @@ deploy." (define (certbot-activation config) (let* ((certbot-directory "/var/lib/certbot") - (certbot-cert-directory "/etc/letsencrypt/live") - (script (in-vicinity certbot-directory "renew-certificates")) - (message (format #f (G_ "~a may need to be run~%") script))) + (certbot-cert-directory "/etc/letsencrypt/live")) (match config (($ package webroot certificates email server rsa-key-size default-location) @@ -258,10 +320,7 @@ deploy." (map (generate-certificate-gexp certbot-cert-directory rsa-key-size) (filter certificate-configuration-start-self-signed? - certificates))) - - (copy-file #$(certbot-command config) #$script) - (display #$message))))))) + certificates))))))))) (define certbot-nginx-server-configurations (match-lambda @@ -294,7 +353,9 @@ deploy." (service-extension activation-service-type certbot-activation) (service-extension mcron-service-type - certbot-renewal-jobs))) + certbot-renewal-jobs) + (service-extension shepherd-root-service-type + certbot-renewal-one-shot))) (compose concatenate) (extend (lambda (config additional-certificates) (certbot-configuration -- cgit v1.2.3