gnu: tzdata: Update source URI.
[jackhill/guix/guix.git] / gnu / services / dns.scm
index d0913e9..5f37cb0 100644 (file)
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
             zone-entry
 
             dnsmasq-service-type
-            dnsmasq-configuration))
+            dnsmasq-configuration
+
+            ddclient-service-type
+            ddclient-configuration))
 
 ;;;
 ;;; Knot DNS.
 (define-record-type* <knot-zone-configuration>
   knot-zone-configuration make-knot-zone-configuration
   knot-zone-configuration?
-  (domain           knot-zone-configuration-domain
-                    (default ""))
-  (file             knot-zone-configuration-file
-                    (default "")) ; the file where this zone is saved.
-  (zone             knot-zone-configuration-zone
-                    (default (zone-file))) ; initial content of the zone file
-  (master           knot-zone-configuration-master
-                    (default '()))
-  (ddns-master      knot-zone-configuration-ddns-master
-                    (default #f))
-  (notify           knot-zone-configuration-notify
-                    (default '()))
-  (acl              knot-zone-configuration-acl
-                    (default '()))
-  (semantic-checks? knot-zone-configuration-semantic-checks?
-                    (default #f))
-  (disable-any?     knot-zone-configuration-disable-any?
-                    (default #f))
-  (zonefile-sync    knot-zone-configuration-zonefile-sync
-                    (default 0))
-  (dnssec-policy    knot-zone-configuration-dnssec-policy
-                    (default #f))
-  (serial-policy    knot-zone-configuration-serial-policy
-                    (default 'increment)))
+  (domain            knot-zone-configuration-domain
+                     (default ""))
+  (file              knot-zone-configuration-file
+                     (default "")) ; the file where this zone is saved.
+  (zone              knot-zone-configuration-zone
+                     (default (zone-file))) ; initial content of the zone file
+  (master            knot-zone-configuration-master
+                     (default '()))
+  (ddns-master       knot-zone-configuration-ddns-master
+                     (default #f))
+  (notify            knot-zone-configuration-notify
+                     (default '()))
+  (acl               knot-zone-configuration-acl
+                     (default '()))
+  (semantic-checks?  knot-zone-configuration-semantic-checks?
+                     (default #f))
+  (disable-any?      knot-zone-configuration-disable-any?
+                     (default #f))
+  (zonefile-sync     knot-zone-configuration-zonefile-sync
+                     (default 0))
+  (zonefile-load     knot-zone-configuration-zonefile-load
+                     (default #f))
+  (journal-content   knot-zone-configuration-journal-content
+                     (default #f))
+  (max-journal-usage knot-zone-configuration-max-journal-usage
+                     (default #f))
+  (max-journal-depth knot-zone-configuration-max-journal-depth
+                     (default #f))
+  (max-zone-size     knot-zone-configuration-max-zone-size
+                     (default #f))
+  (dnssec-policy     knot-zone-configuration-dnssec-policy
+                     (default #f))
+  (serial-policy     knot-zone-configuration-serial-policy
+                     (default 'increment)))
 
 (define-record-type* <knot-remote-configuration>
   knot-remote-configuration make-knot-remote-configuration
                  (default knot))
   (run-directory knot-configuration-run-directory
                  (default "/var/run/knot"))
+  (includes      knot-configuration-includes
+                 (default '()))
   (listen-v4     knot-configuration-listen-v4
                  (default "0.0.0.0"))
   (listen-v6     knot-configuration-listen-v6
     (error-out "knot configuration field must be a package."))
   (unless (string? (knot-configuration-run-directory config))
     (error-out "run-directory must be a string."))
+  (unless (list? (knot-configuration-includes config))
+    (error-out "includes must be a list of strings or file-like objects."))
   (unless (list? (knot-configuration-keys config))
     (error-out "keys must be a list of knot-key-configuration."))
   (for-each (lambda (key) (verify-knot-key-configuration key))
           (fold (lambda (x1 x2)
                   (string-append (if (symbol? x1) (symbol->string x1) x1) ", "
                                  (if (symbol? x2) (symbol->string x2) x2)))
-                (car l) (cdr l))
+                (if (symbol? (car l)) (symbol->string (car l)) (car l)) (cdr l))
           "]"))))
 
 (define (knot-acl-config acls)
                 (acl (list #$@(knot-zone-configuration-acl zone)))
                 (semantic-checks? #$(knot-zone-configuration-semantic-checks? zone))
                 (disable-any? #$(knot-zone-configuration-disable-any? zone))
+                (zonefile-sync #$(knot-zone-configuration-zonefile-sync zone))
+                (zonefile-load '#$(knot-zone-configuration-zonefile-load zone))
+                (journal-content #$(knot-zone-configuration-journal-content zone))
+                (max-journal-usage #$(knot-zone-configuration-max-journal-usage zone))
+                (max-journal-depth #$(knot-zone-configuration-max-journal-depth zone))
+                (max-zone-size #$(knot-zone-configuration-max-zone-size zone))
                 (dnssec-policy #$(knot-zone-configuration-dnssec-policy zone))
                 (serial-policy '#$(knot-zone-configuration-serial-policy zone)))
             (format #t "    - domain: ~a\n" domain)
                           (knot-zone-configuration-acl zone))))
             (format #t "      semantic-checks: ~a\n" (if semantic-checks? "on" "off"))
             (format #t "      disable-any: ~a\n" (if disable-any? "on" "off"))
+            (if zonefile-sync
+              (format #t "      zonefile-sync: ~a\n" zonefile-sync))
+            (if zonefile-load
+              (format #t "      zonefile-load: ~a\n"
+                      (symbol->string zonefile-load)))
+            (if journal-content
+              (format #t "      journal-content: ~a\n"
+                      (symbol->string journal-content)))
+            (if max-journal-usage
+              (format #t "      max-journal-usage: ~a\n" max-journal-usage))
+            (if max-journal-depth
+              (format #t "      max-journal-depth: ~a\n" max-journal-depth))
+            (if max-zone-size
+              (format #t "      max-zone-size: ~a\n" max-zone-size))
             (if dnssec-policy
                 (begin
                   (format #t "      dnssec-signing: on\n")
     #~(begin
         (call-with-output-file #$output
           (lambda (port)
+            (for-each (lambda (inc)
+                        (format port "include: ~a\n" inc))
+                      '#$(knot-configuration-includes config))
             (format port "server:\n")
             (format port "    rundir: ~a\n" #$(knot-configuration-run-directory config))
             (format port "    user: knot\n")
                     (default '()))      ;list of string
   (cache-size       dnsmasq-configuration-cache-size
                     (default 150))      ;integer
-  (no-negcache?     dnsmasq-configuration-no-negcache?
-                    (default #f)))      ;boolean
+  (negative-cache?  dnsmasq-configuration-negative-cache?
+                    (default #t)))      ;boolean
 
 (define dnsmasq-shepherd-service
   (match-lambda
                                 no-hosts?
                                 port local-service? listen-addresses
                                 resolv-file no-resolv? servers
-                                cache-size no-negcache?)
+                                cache-size negative-cache?)
      (shepherd-service
       (provision '(dnsmasq))
       (requirement '(networking))
                   #$@(map (cut format #f "--server=~a" <>)
                           servers)
                   #$(format #f "--cache-size=~a" cache-size)
-                  #$@(if no-negcache?
-                         '("--no-negcache")
-                         '()))
+                  #$@(if negative-cache?
+                         '()
+                         '("--no-negcache")))
                 #:pid-file "/run/dnsmasq.pid"))
       (stop #~(make-kill-destructor))))))
 
    (name 'dnsmasq)
    (extensions
     (list (service-extension shepherd-root-service-type
-                             (compose list dnsmasq-shepherd-service))))))
+                             (compose list dnsmasq-shepherd-service))))
+   (default-value (dnsmasq-configuration))
+   (description "Run the dnsmasq DNS server.")))
+
+\f
+;;;
+;;; ddclient
+;;;
+
+(define (uglify-field-name field-name)
+  (string-delete #\? (symbol->string field-name)))
+
+(define (serialize-field field-name val)
+  (when (not (member field-name '(group secret-file user)))
+    (format #t "~a=~a\n" (uglify-field-name field-name) val)))
+
+(define (serialize-boolean field-name val)
+  (serialize-field field-name (if val "yes" "no")))
+
+(define (serialize-integer field-name val)
+  (serialize-field field-name (number->string val)))
+
+(define (serialize-string field-name val)
+  (if (and (string? val) (string=? val ""))
+      ""
+      (serialize-field field-name val)))
+
+(define (serialize-list field-name val)
+  (if (null? val) "" (serialize-field field-name (string-join val))))
+
+(define (serialize-extra-options extra-options)
+  (string-join extra-options "\n" 'suffix))
+
+(define-configuration ddclient-configuration
+  (ddclient
+   (package ddclient)
+   "The ddclient package.")
+  (daemon
+   (integer 300)
+   "The period after which ddclient will retry to check IP and domain name.")
+  (syslog
+   (boolean #t)
+   "Use syslog for the output.")
+  (mail
+   (string "root")
+   "Mail to user.")
+  (mail-failure
+   (string "root")
+   "Mail failed update to user.")
+  (pid
+   (string "/var/run/ddclient/ddclient.pid")
+   "The ddclient PID file.")
+  (ssl
+   (boolean #t)
+   "Enable SSL support.")
+  (user
+   (string "ddclient")
+   "Specifies the user name or ID that is used when running ddclient
+program.")
+  (group
+   (string "ddclient")
+   "Group of the user who will run the ddclient program.")
+  (secret-file
+   (string "/etc/ddclient/secrets.conf")
+   "Secret file which will be appended to @file{ddclient.conf} file.  This
+file contains credentials for use by ddclient.  You are expected to create it
+manually.")
+  (extra-options
+   (list '())
+   "Extra options will be appended to @file{ddclient.conf} file."))
+
+(define (ddclient-account config)
+  "Return the user accounts and user groups for CONFIG."
+  (let ((ddclient-user (ddclient-configuration-user config))
+        (ddclient-group (ddclient-configuration-group config)))
+    (list (user-group
+           (name ddclient-group)
+           (system? #t))
+          (user-account
+           (name ddclient-user)
+           (system? #t)
+           (group ddclient-group)
+           (comment "ddclientd privilege separation user")
+           (home-directory (string-append "/var/run/" ddclient-user))))))
+
+(define (ddclient-activation config)
+  "Return the activation GEXP for CONFIG."
+  (with-imported-modules '((guix build utils)
+                           (ice-9 rdelim))
+    #~(begin
+        (use-modules (guix build utils)
+                     (ice-9 rdelim))
+        (let ((ddclient-user
+               (passwd:uid (getpw #$(ddclient-configuration-user config))))
+              (ddclient-group
+               (passwd:gid (getpw #$(ddclient-configuration-group config))))
+              (ddclient-secret-file
+               #$(ddclient-configuration-secret-file config)))
+          ;; 'ddclient' complains about ddclient.conf file permissions, which
+          ;; rules out /gnu/store.  Thus we copy the ddclient.conf to /etc.
+          (for-each (lambda (dir)
+                      (mkdir-p dir)
+                      (chmod dir #o700)
+                      (chown dir ddclient-user ddclient-group))
+                    '("/var/cache/ddclient" "/var/run/ddclient"
+                      "/etc/ddclient"))
+          (with-output-to-file "/etc/ddclient/ddclient.conf"
+            (lambda ()
+              (display
+               (string-append
+                "# Generated by 'ddclient-service'.\n\n"
+                #$(with-output-to-string
+                    (lambda ()
+                      (serialize-configuration config
+                                               ddclient-configuration-fields)))
+                (if (string-null? ddclient-secret-file)
+                    ""
+                    (format #f "\n\n# Appended from '~a'.\n\n~a"
+                            ddclient-secret-file
+                            (with-input-from-file ddclient-secret-file
+                              read-string)))))))
+          (chmod "/etc/ddclient/ddclient.conf" #o600)
+          (chown "/etc/ddclient/ddclient.conf"
+                 ddclient-user ddclient-group)))))
+
+(define (ddclient-shepherd-service config)
+  "Return a <shepherd-service> for ddclient with CONFIG."
+  (let ((ddclient (ddclient-configuration-ddclient config))
+        (ddclient-pid (ddclient-configuration-pid config))
+        (ddclient-user (ddclient-configuration-user config))
+        (ddclient-group (ddclient-configuration-group config)))
+    (list (shepherd-service
+           (provision '(ddclient))
+           (documentation "Run ddclient daemon.")
+           (start #~(make-forkexec-constructor
+                     (list #$(file-append ddclient "/bin/ddclient")
+                           "-foreground"
+                           "-file" "/etc/ddclient/ddclient.conf")
+                     #:pid-file #$ddclient-pid
+                     #:environment-variables
+                     (list "SSL_CERT_DIR=/run/current-system/profile\
+/etc/ssl/certs"
+                           "SSL_CERT_FILE=/run/current-system/profile\
+/etc/ssl/certs/ca-certificates.crt")
+                     #:user #$ddclient-user
+                     #:group #$ddclient-group))
+           (stop #~(make-kill-destructor))))))
+
+(define ddclient-service-type
+  (service-type
+   (name 'ddclient)
+   (extensions
+    (list (service-extension account-service-type
+                             ddclient-account)
+          (service-extension shepherd-root-service-type
+                             ddclient-shepherd-service)
+          (service-extension activation-service-type
+                             ddclient-activation)))
+   (default-value (ddclient-configuration))
+   (description "Configure address updating utility for dynamic DNS services,
+ddclient.")))
+
+(define (generate-ddclient-documentation)
+  (generate-documentation
+   `((ddclient-configuration ,ddclient-configuration-fields))
+   'ddclient-configuration))