summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gnu/services/certbot.scm89
1 files changed, 75 insertions, 14 deletions
diff --git a/gnu/services/certbot.scm b/gnu/services/certbot.scm
index cb1be0c0e95..f287c8367f4 100644
--- a/gnu/services/certbot.scm
+++ b/gnu/services/certbot.scm
@@ -180,15 +180,45 @@ deploy."
180 (program-file 180 (program-file
181 "certbot-command" 181 "certbot-command"
182 #~(begin 182 #~(begin
183 (use-modules (ice-9 match)) 183 (use-modules (ice-9 match)
184 (let ((code 0)) 184 (ice-9 textual-ports))
185
186 (define (log format-string . args)
187 (apply format #t format-string args)
188 (force-output))
189
190 (define (file-contains? file string)
191 (string-contains (call-with-input-file file
192 get-string-all)
193 string))
194
195 (define (connection-error?)
196 ;; Certbot errors are always exit code 1, so we need to look at
197 ;; the log file to see if there was a connection error.
198 (file-contains? "/var/log/letsencrypt/letsencrypt.log"
199 "Failed to establish a new connection"))
200
201 (let ((script-code 0))
185 (for-each 202 (for-each
186 (match-lambda 203 (match-lambda
187 ((name . command) 204 ((name . command)
188 (begin 205 (log "Acquiring or renewing certificate: ~a~%" name)
189 (format #t "Acquiring or renewing certificate: ~a~%" name) 206 (cond
190 (set! code (or (apply system* command) code))))) 207 ((zero? (status:exit-val (apply system* command)))
191 '#$commands) code))))))) 208 (log "Certificate successfully acquired: ~a~%" name))
209 ((connection-error?)
210 ;; If we have a connection error, then bail early with
211 ;; exit code 2. We don't expect this to resolve within the
212 ;; timespan of this script.
213 (log "Connection error - bailing out~%")
214 (exit 2))
215 (else
216 ;; If we have any other type of error, then continue but
217 ;; exit with a failing status code in the end.
218 (log "Error: ~a - continuing with other domains~%" name)
219 (set! script-code 1)))))
220 '#$commands)
221 (exit script-code))))))))
192 222
193(define (certbot-renewal-jobs config) 223(define (certbot-renewal-jobs config)
194 (list 224 (list
@@ -197,6 +227,40 @@ deploy."
197 #~(job '(next-minute-from (next-hour '(0 12)) (list (random 60))) 227 #~(job '(next-minute-from (next-hour '(0 12)) (list (random 60)))
198 #$(certbot-command config)))) 228 #$(certbot-command config))))
199 229
230(define (certbot-renewal-one-shot config)
231 (list
232 ;; Renew certificates when the system first starts. This is a one-shot
233 ;; service, because the mcron configuration will take care of running this
234 ;; periodically. This is most useful the very first time the system starts,
235 ;; to overwrite our self-signed certificates as soon as possible without
236 ;; user intervention.
237 (shepherd-service
238 (provision '(renew-certbot-certificates))
239 (requirement '(nginx))
240 (one-shot? #t)
241 (start #~(lambda _
242 ;; This needs the network, but there's no reliable way to know
243 ;; if the network is up other than trying. If we fail due to a
244 ;; connection error we retry a number of times in the hope that
245 ;; the network comes up soon.
246 (let loop ((attempt 0))
247 (let ((code (status:exit-val
248 (system* #$(certbot-command config)))))
249 (cond
250 ((and (= code 2) ; Exit code 2 means connection error
251 (< attempt 12)) ; Arbitrarily chosen max attempts
252 (sleep 10) ; Arbitrarily chosen retry delay
253 (loop (1+ attempt)))
254 ((zero? code)
255 ;; Success!
256 #t)
257 (else
258 ;; Failure.
259 #f))))))
260 (auto-start? #t)
261 (documentation "Call certbot to renew certificates.")
262 (actions (list (shepherd-configuration-action (certbot-command config)))))))
263
200(define (generate-certificate-gexp certbot-cert-directory rsa-key-size) 264(define (generate-certificate-gexp certbot-cert-directory rsa-key-size)
201 (match-lambda 265 (match-lambda
202 (($ <certificate-configuration> name (primary-domain other-domains ...) 266 (($ <certificate-configuration> name (primary-domain other-domains ...)
@@ -240,9 +304,7 @@ deploy."
240 304
241(define (certbot-activation config) 305(define (certbot-activation config)
242 (let* ((certbot-directory "/var/lib/certbot") 306 (let* ((certbot-directory "/var/lib/certbot")
243 (certbot-cert-directory "/etc/letsencrypt/live") 307 (certbot-cert-directory "/etc/letsencrypt/live"))
244 (script (in-vicinity certbot-directory "renew-certificates"))
245 (message (format #f (G_ "~a may need to be run~%") script)))
246 (match config 308 (match config
247 (($ <certbot-configuration> package webroot certificates email 309 (($ <certbot-configuration> package webroot certificates email
248 server rsa-key-size default-location) 310 server rsa-key-size default-location)
@@ -258,10 +320,7 @@ deploy."
258 (map (generate-certificate-gexp certbot-cert-directory 320 (map (generate-certificate-gexp certbot-cert-directory
259 rsa-key-size) 321 rsa-key-size)
260 (filter certificate-configuration-start-self-signed? 322 (filter certificate-configuration-start-self-signed?
261 certificates))) 323 certificates)))))))))
262
263 (copy-file #$(certbot-command config) #$script)
264 (display #$message)))))))
265 324
266(define certbot-nginx-server-configurations 325(define certbot-nginx-server-configurations
267 (match-lambda 326 (match-lambda
@@ -294,7 +353,9 @@ deploy."
294 (service-extension activation-service-type 353 (service-extension activation-service-type
295 certbot-activation) 354 certbot-activation)
296 (service-extension mcron-service-type 355 (service-extension mcron-service-type
297 certbot-renewal-jobs))) 356 certbot-renewal-jobs)
357 (service-extension shepherd-root-service-type
358 certbot-renewal-one-shot)))
298 (compose concatenate) 359 (compose concatenate)
299 (extend (lambda (config additional-certificates) 360 (extend (lambda (config additional-certificates)
300 (certbot-configuration 361 (certbot-configuration