(define-module (services wireguard)
  #:use-module (srfi srfi-1)
  #:use-module (guix records)
  #:use-module (guix gexp)
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services virtualization)
  #:use-module (gnu system)
  #:use-module (gnu packages vpn)
  #:export (wireguard-configuration
            wireguard-service-type))


(define-record-type* <wireguard-configuration>
  wireguard-configuration make-wireguard-configuration
  wireguard-configuration?
  ;; <package>
  (wireguard             wireguard-configuration-wireguard
                         (default wireguard-tools))
  ;; integer
  (port-number           wireguard-configuration-port-number
                         (default 51820))
  ;; string
  (interface             wireguard-configuration-interface
                         (default "wg0"))
  ;; string
  (address               wireguard-configuration-address
                         (default "10.0.0.1/24"))
  ;; string
  (private-key           wireguard-configuration-private-key
                         (default ""))
  ;; string
  (peers                 wireguard-configuration-peers
                         (default '())))

(define (wireguard-config-file config)
  "Return the wireguard configuration file corresponding to CONFIG."
  (computed-file
   "wireguard-config"
   #~(begin
       (use-modules (ice-9 match))
       (use-modules (guix build utils))
       (mkdir-p #$output)
       (call-with-output-file (string-append #$output "/" #$(wireguard-configuration-interface config) ".conf")
         (lambda (port)
           (display "# Generated by 'wireguard-service'.\n" port)
           (display "[Interface]\n" port)
           (display "Table = auto\n" port)
           (format port "Address = ~a\n"
                   #$(wireguard-configuration-address config))
           (format port "ListenPort = ~a\n"
                   #$(number->string (wireguard-configuration-port-number config)))
           (format port "PrivateKey = ~a\n"
                   #$(wireguard-configuration-private-key config))

           (for-each
            (match-lambda
             ((public-key allowed-ips endpoint
               (format port "\n[Peer]\nPublicKey = ~a\nAllowedIPs = ~a\nEndpoint = ~a\nPersistentKeepalive = 10\n" public-key allowed-ips endpoint))))
            '#$(wireguard-configuration-peers config))
           #t)))
   #:options
   '(#:local-build? #t
     #:modules ((guix build utils)))))

(define (wireguard-shepherd-service config)
  "Return a <shepherd-service> for wireguard with CONFIG."

  (define wg-quick-up-command
    #~(list (string-append #$(wireguard-configuration-wireguard config) "/bin/wg-quick")
            "up" (string-append #$(wireguard-config-file config) "/" #$(wireguard-configuration-interface config) ".conf")))

  (define wg-quick-down-command
    #~(list (string-append #$(wireguard-configuration-wireguard config) "/bin/wg-quick")
            "down" #$(wireguard-configuration-interface config)))

  (list (shepherd-service
         (documentation "wireguard configuration")
         (requirement '())
         (provision '(wireguard))
         (start #~(lambda _ (apply invoke #$wg-quick-up-command)))
         (stop #~(lambda _ (apply invoke #$wg-quick-down-command)))
         (one-shot? #t)
         (auto-start? #t))))

(define wireguard-service-type
  (service-type (name 'openssh)
                (description
                 "run wireguard")
                (extensions
                 (list (service-extension shepherd-root-service-type
                                          wireguard-shepherd-service)))
                (compose concatenate)
                (default-value (wireguard-configuration))))
