;;; 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-file
zone-entry
+ knot-resolver-service-type
+ knot-resolver-configuration
+
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")
(const %knot-accounts))))))
\f
+;;;
+;;; Knot Resolver.
+;;;
+
+(define-record-type* <knot-resolver-configuration>
+ knot-resolver-configuration
+ make-knot-resolver-configuration
+ knot-resolver-configuration?
+ (package knot-resolver-configuration-package
+ (default knot-resolver))
+ (kresd-config-file knot-resolver-kresd-config-file
+ (default %kresd.conf))
+ (garbage-collection-interval knot-resolver-garbage-collection-interval
+ (default 1000)))
+
+(define %kresd.conf
+ (plain-file "kresd.conf" "-- -*- mode: lua -*-
+net = { '127.0.0.1', '::1' }
+user('knot-resolver', 'knot-resolver')
+modules = { 'hints > iterate', 'stats', 'predict' }
+cache.size = 100 * MB
+"))
+
+(define %knot-resolver-accounts
+ (list (user-group
+ (name "knot-resolver")
+ (system? #t))
+ (user-account
+ (name "knot-resolver")
+ (group "knot-resolver")
+ (system? #t)
+ (home-directory "/var/cache/knot-resolver")
+ (shell (file-append shadow "/sbin/nologin")))))
+
+(define (knot-resolver-activation config)
+ #~(begin
+ (use-modules (guix build utils))
+ (let ((rundir "/var/cache/knot-resolver")
+ (owner (getpwnam "knot-resolver")))
+ (mkdir-p rundir)
+ (chown rundir (passwd:uid owner) (passwd:gid owner)))))
+
+(define knot-resolver-shepherd-services
+ (match-lambda
+ (($ <knot-resolver-configuration> package
+ kresd-config-file
+ garbage-collection-interval)
+ (list
+ (shepherd-service
+ (provision '(kresd))
+ (requirement '(networking))
+ (documentation "Run the Knot Resolver daemon.")
+ (start #~(make-forkexec-constructor
+ '(#$(file-append package "/sbin/kresd")
+ "-c" #$kresd-config-file "-f" "1"
+ "/var/cache/knot-resolver")))
+ (stop #~(make-kill-destructor)))
+ (shepherd-service
+ (provision '(kres-cache-gc))
+ (requirement '(user-processes))
+ (documentation "Run the Knot Resolver Garbage Collector daemon.")
+ (start #~(make-forkexec-constructor
+ '(#$(file-append package "/sbin/kres-cache-gc")
+ "-d" #$(number->string garbage-collection-interval)
+ "-c" "/var/cache/knot-resolver")
+ #:user "knot-resolver"
+ #:group "knot-resolver"))
+ (stop #~(make-kill-destructor)))))))
+
+(define knot-resolver-service-type
+ (service-type
+ (name 'knot-resolver)
+ (extensions
+ (list (service-extension shepherd-root-service-type
+ knot-resolver-shepherd-services)
+ (service-extension activation-service-type
+ knot-resolver-activation)
+ (service-extension account-service-type
+ (const %knot-resolver-accounts))))
+ (default-value (knot-resolver-configuration))
+ (description "Run the Knot DNS Resolver.")))
+
+\f
;;;
;;; Dnsmasq.
;;;
(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))