diff mbox series

[bug#60788] services: Add vnstat-service-type.

Message ID 95b646eb6b23dec213cba43b6e4e7ddc4a601d0f.1673640404.git.mirai@makinata.eu
State New
Headers show
Series [bug#60788] services: Add vnstat-service-type. | expand

Commit Message

Bruno Victal Jan. 13, 2023, 8:07 p.m. UTC
* gnu/services/monitoring.scm (vnstat-service-type): New variable.
* doc/guix.texi (Monitoring Services): Document it.
---
 doc/guix.texi               | 238 +++++++++++++++++++++
 gnu/services/monitoring.scm | 413 ++++++++++++++++++++++++++++++++++++
 2 files changed, 651 insertions(+)


base-commit: 0f85081ed1d99be57d3544e0307e7fa9ca043be9

Comments

Maxim Cournoyer Jan. 16, 2023, 6:42 p.m. UTC | #1
Hello!

Bruno Victal <mirai@makinata.eu> writes:

[...]

> @@ -45,6 +48,10 @@ (define-module (gnu services monitoring)
>              prometheus-node-exporter-web-listen-address
>              prometheus-node-exporter-service-type
 
> +            vnstat-configuration
> +            vnstat-configuration?
> +            vnstat-service-type
> +

Normally, all the accessors are exported, otherwise there's no means to
introspect a vnstat-configuration, which may be useful at the REPL.

[...]

> +
> +;;;
> +;;; vnstat daemon
> +;;;
> +
> +(define* (camelfy-field-name field-name #:key (dromedary? #f))
> +  (match (string-split (symbol->string field-name) #\-)
> +    ((head tail ...)
> +     (string-join (cons (if dromedary? head (string-upcase head 0 1))
> +                        (map string-capitalize tail)) ""))))

I'd name this pascal-case-field-name, and drop the apparently unused
dromerady? argument (fun, though :-)).  If we need it, we could
introduce a 2nd camel-case-field-name.

> +(define (dock-field-name field-name)
> +  "Drop rightmost '?' character"
> +  (let ((str (symbol->string field-name)))
> +    (if (string-suffix? "?" str)
> +        (string->symbol (string-drop-right str 1))
> +        field-name)))

I'm not sure about the meaning of 'dock' in this procedure name.
Perhaps 'strip-trailing-?-character' would be better?

> +(define (vnstat-serialize-string field-name value)
> +  #~(format #f "~a ~s~%"
> +            #$(camelfy-field-name field-name)
> +            #$value))
> +
> +(define vnstat-serialize-integer vnstat-serialize-string)
> +
> +(define (vnstat-serialize-boolean field-name value)
> +  #~(format #f "~a ~a~%"
> +            #$(camelfy-field-name (dock-field-name field-name))
> +            #$(if value 1 0)))
> +
> +(define (vnstat-serialize-alist field-name value)
> +  (generic-serialize-alist string-append
> +                           (lambda (iface val)
> +                             (vnstat-serialize-integer
> +                              (format #f "MaxBW~a" iface) val))
> +                           value))
> +
> +(define-maybe string  (prefix vnstat-))
> +(define-maybe integer (prefix vnstat-))
> +(define-maybe boolean (prefix vnstat-))
> +(define-maybe alist   (prefix vnstat-))
> +
> +;; Documentation strings from vnstat.conf manpage adapted to texinfo.
> +;; vnstat checkout: v2.10, commit b3408af1c609aa6265d296cab7bfe59a61d7cf70
> +(define-configuration vnstat-configuration
> +  (package
> +    (file-like vnstat)
> +    "The vnstat package."
> +    empty-serializer)
> +
> +  (database-dir
> +   (string "/var/lib/vnstat")
> +   "\
> +Specifies the directory where the database is to be stored.
> +A full path must be given and a leading '/' isn't required.")  
> +
> +  (5-minute-hours
> +   (maybe-integer 48)
> +   "\
> +Data retention duration for the 5 minute resolution entries. The configuration
> +defines for how many past hours entries will be stored. Set to @code{-1} for
> +unlimited entries or to @code{0} to disable the data collection of this
> +resolution.")
> +
> +  (64bit-interface-counters
> +   (maybe-integer -2)
> +   "\
> +Select interface counter handling. Set to @code{1} for defining that all interfaces
> +use 64-bit counters on the kernel side and @code{0} for defining 32-bit counter. Set
> +to @code{-1} for using the old style logic used in earlier versions where counter
> +values within 32-bits are assumed to be 32-bit and anything larger is assumed to
> +be a 64-bit counter. This may produce false results if a 64-bit counter is
> +reset within the 32-bits. Set to @code{-2} for using automatic detection based on
> +available kernel datastructures.")

Please reflow these paragraphs so they fit under the 80 characters width
limit.  Perhaps drop the \ escape, as it doesn't provide much and looks
a bit worst, in my opinion.  Each sentence should be separated by two
spaces (that's a Texinfo convention).  This comment applies to all
similar documentation texts.

> +  (always-add-new-interfaces?
> +   (maybe-boolean #t)
> +   "\
> +Enable or disable automatic creation of new database entries for interfaces not
> +currently in the database even if the database file already exists when the
> +daemon is started. New database entries will also get created for new interfaces
> +seen while the daemon is running. Pseudo interfaces lo, lo0 and sit0 are always
> +excluded from getting added.")

The lo, lo0 and sit0 could use a @samp{} or decorator.

[...]

> +(define (vnstat-serialize-configuration config)
> +  (mixed-text-file
> +   "vnstat.conf"
> +   (serialize-configuration config vnstat-configuration-fields)))
> +
> +(define (vnstat-shepherd-service config)
> +  (let ((config-file (vnstat-serialize-configuration config)))
> +    (match-record config <vnstat-configuration> (package pid-file)
> +      (shepherd-service
> +       (documentation "Run vnstatd.")
> +       (requirement `(networking))
> +       (provision '(vnstatd))
> +       (start #~(make-forkexec-constructor
> +                 (list #$(file-append package "/sbin/vnstatd")
> +                       "--daemon"
> +                       "--config" #$config-file)
> +                 #:pid-file #$pid-file))
> +       (stop #~(make-kill-destructor))
> +       (actions
> +        (list (shepherd-configuration-action config-file)

I don't understand what (shepherd-configuration-action config-file)
corresponds to?

> +              (shepherd-action
> +               (name 'reload)
> +               (documentation "Reload vnstatd.")
> +               (procedure
> +                #~(lambda (pid)
> +                    (if pid
> +                        (begin
> +                          (kill pid SIGHUP)
> +                          (format #t
> +                                  "Issued SIGHUP to vnstatd (PID ~a)."
> +                                  pid))
> +                        (format #t "vnstatd is not running.")))))))))))

> +(define (vnstat-account-service config)
> +  (match-record config <vnstat-configuration> (daemon-group daemon-user)
> +    (if (every maybe-value-set? (list daemon-group daemon-user))
> +        (list
> +         (user-group
> +          (name daemon-group)
> +          (system? #t))
> +         (user-account
> +          (name daemon-user)
> +          (group daemon-group)
> +          (system? #t)
> +          (home-directory "/var/empty")
> +          (shell (file-append shadow "/sbin/nologin"))))
> +        '())))
> +
> +(define vnstat-service-type
> +  (service-type
> +   (name 'vnstat)
> +   (description "vnStat network-traffic monitor service.")
> +   (extensions
> +    (list (service-extension shepherd-root-service-type
> +                             (compose list vnstat-shepherd-service))
> +          (service-extension account-service-type
> +                             vnstat-account-service)))
> +   (default-value (vnstat-configuration))))

The rest LGTM (a system test would be nice, but since the Shepherd
service definition is straightforward, it could come later, when the
need arise).

That appears carefully crafted, thank you!  Could you send a v2 with my
above comments addressed, keeping me in CC?
Bruno Victal Jan. 16, 2023, 7:31 p.m. UTC | #2
On 2023-01-16 18:42, Maxim Cournoyer wrote:

>> Bruno Victal <mirai@makinata.eu> writes:
>> +;;;
>> +;;; vnstat daemon
>> +;;;
>> +
>> +(define* (camelfy-field-name field-name #:key (dromedary? #f))
>> +  (match (string-split (symbol->string field-name) #\-)
>> +    ((head tail ...)
>> +     (string-join (cons (if dromedary? head (string-upcase head 0 1))
>> +                        (map string-capitalize tail)) ""))))
> 
> I'd name this pascal-case-field-name, and drop the apparently unused
> dromerady? argument (fun, though :-)).  If we need it, we could
> introduce a 2nd camel-case-field-name.

I was anticipating that this snippet would get reused (i.e. copy-pasted) by
other services so I wanted to generalize it before it starts spawning
its dromedary cousin.

If this isn't preferred, I can drop it and just leave it as
pascal-case-field-name. (maybe these kinds of procedures could go into
a new "configuration utils" module with useful auxiliary functions?)

> 
>> +(define (dock-field-name field-name)
>> +  "Drop rightmost '?' character"
>> +  (let ((str (symbol->string field-name)))
>> +    (if (string-suffix? "?" str)
>> +        (string->symbol (string-drop-right str 1))
>> +        field-name)))
> 
> I'm not sure about the meaning of 'dock' in this procedure name.
> Perhaps 'strip-trailing-?-character' would be better?

I couldn't think of any other word that would essentially describe
whats amounts to "dropping" the tail of a list/string.
(here, 'dock': "the practice of cutting off or trimming the tail of an animal")

>> +;; Documentation strings from vnstat.conf manpage adapted to texinfo.
>> +;; vnstat checkout: v2.10, commit b3408af1c609aa6265d296cab7bfe59a61d7cf70
>> +(define-configuration vnstat-configuration
>> +  (package
>> +    (file-like vnstat)
>> +    "The vnstat package."
>> +    empty-serializer)
>> +
>> +  (database-dir
>> +   (string "/var/lib/vnstat")
>> +   "\
>> +Specifies the directory where the database is to be stored.
>> +A full path must be given and a leading '/' isn't required.")  
>> +
>> +  (5-minute-hours
>> +   (maybe-integer 48)
>> +   "\
>> +Data retention duration for the 5 minute resolution entries. The configuration
>> +defines for how many past hours entries will be stored. Set to @code{-1} for
>> +unlimited entries or to @code{0} to disable the data collection of this
>> +resolution.")
>> +
>> +  (64bit-interface-counters
>> +   (maybe-integer -2)
>> +   "\
>> +Select interface counter handling. Set to @code{1} for defining that all interfaces
>> +use 64-bit counters on the kernel side and @code{0} for defining 32-bit counter. Set
>> +to @code{-1} for using the old style logic used in earlier versions where counter
>> +values within 32-bits are assumed to be 32-bit and anything larger is assumed to
>> +be a 64-bit counter. This may produce false results if a 64-bit counter is
>> +reset within the 32-bits. Set to @code{-2} for using automatic detection based on
>> +available kernel datastructures.")
> 
> Please reflow these paragraphs so they fit under the 80 characters width
> limit.  Perhaps drop the \ escape, as it doesn't provide much and looks
> a bit worst, in my opinion.  Each sentence should be separated by two
> spaces (that's a Texinfo convention).  This comment applies to all
> similar documentation texts.

They're formatted this way as I find it easier to compare against upstream troff sources.
If they're reflowed or if the \ escape is dropped then the diffs get larger to compare against
(most of the changes here are easier to compare using word-diffs)

>> +  (always-add-new-interfaces?
>> +   (maybe-boolean #t)
>> +   "\
>> +Enable or disable automatic creation of new database entries for interfaces not
>> +currently in the database even if the database file already exists when the
>> +daemon is started. New database entries will also get created for new interfaces
>> +seen while the daemon is running. Pseudo interfaces lo, lo0 and sit0 are always
>> +excluded from getting added.")
> 
> The lo, lo0 and sit0 could use a @samp{} or decorator.

Noted.

> 
> [...]
> 
>> +(define (vnstat-serialize-configuration config)
>> +  (mixed-text-file
>> +   "vnstat.conf"
>> +   (serialize-configuration config vnstat-configuration-fields)))
>> +
>> +(define (vnstat-shepherd-service config)
>> +  (let ((config-file (vnstat-serialize-configuration config)))
>> +    (match-record config <vnstat-configuration> (package pid-file)
>> +      (shepherd-service
>> +       (documentation "Run vnstatd.")
>> +       (requirement `(networking))
>> +       (provision '(vnstatd))
>> +       (start #~(make-forkexec-constructor
>> +                 (list #$(file-append package "/sbin/vnstatd")
>> +                       "--daemon"
>> +                       "--config" #$config-file)
>> +                 #:pid-file #$pid-file))
>> +       (stop #~(make-kill-destructor))
>> +       (actions
>> +        (list (shepherd-configuration-action config-file)
> 
> I don't understand what (shepherd-configuration-action config-file)
> corresponds to?

It shows the path to the config file with `herd configuration vnstatd'.
It was introduced with 'ebc7de6a1efb702fd0364128cbde19697966c4f4'.

>> +              (shepherd-action
>> +               (name 'reload)
>> +               (documentation "Reload vnstatd.")
>> +               (procedure
>> +                #~(lambda (pid)
>> +                    (if pid
>> +                        (begin
>> +                          (kill pid SIGHUP)
>> +                          (format #t
>> +                                  "Issued SIGHUP to vnstatd (PID ~a)."
>> +                                  pid))
>> +                        (format #t "vnstatd is not running.")))))))))))
> 
>> +(define (vnstat-account-service config)
>> +  (match-record config <vnstat-configuration> (daemon-group daemon-user)
>> +    (if (every maybe-value-set? (list daemon-group daemon-user))
>> +        (list
>> +         (user-group
>> +          (name daemon-group)
>> +          (system? #t))
>> +         (user-account
>> +          (name daemon-user)
>> +          (group daemon-group)
>> +          (system? #t)
>> +          (home-directory "/var/empty")
>> +          (shell (file-append shadow "/sbin/nologin"))))
>> +        '())))
>> +
>> +(define vnstat-service-type
>> +  (service-type
>> +   (name 'vnstat)
>> +   (description "vnStat network-traffic monitor service.")
>> +   (extensions
>> +    (list (service-extension shepherd-root-service-type
>> +                             (compose list vnstat-shepherd-service))
>> +          (service-extension account-service-type
>> +                             vnstat-account-service)))
>> +   (default-value (vnstat-configuration))))
> 
> The rest LGTM (a system test would be nice, but since the Shepherd
> service definition is straightforward, it could come later, when the
> need arise).

I've thought a bit about the system test though the way vnstat works is that
it needs to collect some data and it also takes a while for the daemon to flag it
as ready (~5 minutes). This would be a somewhat slow and non-trivial test to write.


WDYT?



Cheers,
Bruno
Maxim Cournoyer Jan. 16, 2023, 7:56 p.m. UTC | #3
Hi Bruno,

Bruno Victal <mirai@makinata.eu> writes:

> On 2023-01-16 18:42, Maxim Cournoyer wrote:
>
>>> Bruno Victal <mirai@makinata.eu> writes:
>>> +;;;
>>> +;;; vnstat daemon
>>> +;;;
>>> +
>>> +(define* (camelfy-field-name field-name #:key (dromedary? #f))
>>> +  (match (string-split (symbol->string field-name) #\-)
>>> +    ((head tail ...)
>>> +     (string-join (cons (if dromedary? head (string-upcase head 0 1))
>>> +                        (map string-capitalize tail)) ""))))
>> 
>> I'd name this pascal-case-field-name, and drop the apparently unused
>> dromerady? argument (fun, though :-)).  If we need it, we could
>> introduce a 2nd camel-case-field-name.
>
> I was anticipating that this snippet would get reused (i.e. copy-pasted) by
> other services so I wanted to generalize it before it starts spawning
> its dromedary cousin.
>
> If this isn't preferred, I can drop it and just leave it as
> pascal-case-field-name. (maybe these kinds of procedures could go into
> a new "configuration utils" module with useful auxiliary functions?)
>
>> 
>>> +(define (dock-field-name field-name)
>>> +  "Drop rightmost '?' character"
>>> +  (let ((str (symbol->string field-name)))
>>> +    (if (string-suffix? "?" str)
>>> +        (string->symbol (string-drop-right str 1))
>>> +        field-name)))
>> 
>> I'm not sure about the meaning of 'dock' in this procedure name.
>> Perhaps 'strip-trailing-?-character' would be better?
>
> I couldn't think of any other word that would essentially describe
> whats amounts to "dropping" the tail of a list/string.
> (here, 'dock': "the practice of cutting off or trimming the tail of an animal")

Ouch.  I'm even more in favor of an alternative name now that I know the
meaning of 'dock' :-).

>>> +;; Documentation strings from vnstat.conf manpage adapted to texinfo.
>>> +;; vnstat checkout: v2.10, commit b3408af1c609aa6265d296cab7bfe59a61d7cf70
>>> +(define-configuration vnstat-configuration
>>> +  (package
>>> +    (file-like vnstat)
>>> +    "The vnstat package."
>>> +    empty-serializer)
>>> +
>>> +  (database-dir
>>> +   (string "/var/lib/vnstat")
>>> +   "\
>>> +Specifies the directory where the database is to be stored.
>>> +A full path must be given and a leading '/' isn't required.")  
>>> +
>>> +  (5-minute-hours
>>> +   (maybe-integer 48)
>>> +   "\
>>> +Data retention duration for the 5 minute resolution entries. The configuration
>>> +defines for how many past hours entries will be stored. Set to @code{-1} for
>>> +unlimited entries or to @code{0} to disable the data collection of this
>>> +resolution.")
>>> +
>>> +  (64bit-interface-counters
>>> +   (maybe-integer -2)
>>> +   "\
>>> +Select interface counter handling. Set to @code{1} for defining that all interfaces
>>> +use 64-bit counters on the kernel side and @code{0} for defining 32-bit counter. Set
>>> +to @code{-1} for using the old style logic used in earlier versions where counter
>>> +values within 32-bits are assumed to be 32-bit and anything larger is assumed to
>>> +be a 64-bit counter. This may produce false results if a 64-bit counter is
>>> +reset within the 32-bits. Set to @code{-2} for using automatic detection based on
>>> +available kernel datastructures.")
>> 
>> Please reflow these paragraphs so they fit under the 80 characters width
>> limit.  Perhaps drop the \ escape, as it doesn't provide much and looks
>> a bit worst, in my opinion.  Each sentence should be separated by two
>> spaces (that's a Texinfo convention).  This comment applies to all
>> similar documentation texts.
>
> They're formatted this way as I find it easier to compare against upstream troff sources.
> If they're reflowed or if the \ escape is dropped then the diffs get larger to compare against
> (most of the changes here are easier to compare using word-diffs)

I think we should take ownership of that text, and update it when it
makes sense without necessarily having to keep every details the same
with upstream (especially for the double space rule and other
conventions); in this view I'd opt to adjust it so the Guix source reads
beautifully rather than for diffing against the orginal source :-).

If this truly provides value to you (to be able to more easily diff
against the troff source), then perhaps how to do so should be detailed
in a comment for the next person to understand how this works and why it
is this way.

>>> +  (always-add-new-interfaces?
>>> +   (maybe-boolean #t)
>>> +   "\
>>> +Enable or disable automatic creation of new database entries for interfaces not
>>> +currently in the database even if the database file already exists when the
>>> +daemon is started. New database entries will also get created for new interfaces
>>> +seen while the daemon is running. Pseudo interfaces lo, lo0 and sit0 are always
>>> +excluded from getting added.")
>> 
>> The lo, lo0 and sit0 could use a @samp{} or decorator.
>
> Noted.
>
>> 
>> [...]
>> 
>>> +(define (vnstat-serialize-configuration config)
>>> +  (mixed-text-file
>>> +   "vnstat.conf"
>>> +   (serialize-configuration config vnstat-configuration-fields)))
>>> +
>>> +(define (vnstat-shepherd-service config)
>>> +  (let ((config-file (vnstat-serialize-configuration config)))
>>> +    (match-record config <vnstat-configuration> (package pid-file)
>>> +      (shepherd-service
>>> +       (documentation "Run vnstatd.")
>>> +       (requirement `(networking))
>>> +       (provision '(vnstatd))
>>> +       (start #~(make-forkexec-constructor
>>> +                 (list #$(file-append package "/sbin/vnstatd")
>>> +                       "--daemon"
>>> +                       "--config" #$config-file)
>>> +                 #:pid-file #$pid-file))
>>> +       (stop #~(make-kill-destructor))
>>> +       (actions
>>> +        (list (shepherd-configuration-action config-file)
>> 
>> I don't understand what (shepherd-configuration-action config-file)
>> corresponds to?
>
> It shows the path to the config file with `herd configuration vnstatd'.
> It was introduced with 'ebc7de6a1efb702fd0364128cbde19697966c4f4'.

Thanks!  That's news to me :-).

>>> +              (shepherd-action
>>> +               (name 'reload)
>>> +               (documentation "Reload vnstatd.")
>>> +               (procedure
>>> +                #~(lambda (pid)
>>> +                    (if pid
>>> +                        (begin
>>> +                          (kill pid SIGHUP)
>>> +                          (format #t
>>> +                                  "Issued SIGHUP to vnstatd (PID ~a)."
>>> +                                  pid))
>>> +                        (format #t "vnstatd is not running.")))))))))))
>> 
>>> +(define (vnstat-account-service config)
>>> +  (match-record config <vnstat-configuration> (daemon-group daemon-user)
>>> +    (if (every maybe-value-set? (list daemon-group daemon-user))
>>> +        (list
>>> +         (user-group
>>> +          (name daemon-group)
>>> +          (system? #t))
>>> +         (user-account
>>> +          (name daemon-user)
>>> +          (group daemon-group)
>>> +          (system? #t)
>>> +          (home-directory "/var/empty")
>>> +          (shell (file-append shadow "/sbin/nologin"))))
>>> +        '())))
>>> +
>>> +(define vnstat-service-type
>>> +  (service-type
>>> +   (name 'vnstat)
>>> +   (description "vnStat network-traffic monitor service.")
>>> +   (extensions
>>> +    (list (service-extension shepherd-root-service-type
>>> +                             (compose list vnstat-shepherd-service))
>>> +          (service-extension account-service-type
>>> +                             vnstat-account-service)))
>>> +   (default-value (vnstat-configuration))))
>> 
>> The rest LGTM (a system test would be nice, but since the Shepherd
>> service definition is straightforward, it could come later, when the
>> need arise).
>
> I've thought a bit about the system test though the way vnstat works is that
> it needs to collect some data and it also takes a while for the daemon to flag it
> as ready (~5 minutes). This would be a somewhat slow and non-trivial test to write.

Hm, not nice indeed.  Then I guess it's fine without a test.

One last thing: in general I think we should try to have our services
run in a Linux namespace (unprivileged); have you tried running vnstat
in a wrapper produced with least-authority-wrapper (from (guix
least-authority)) ?
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 751d0957d8..b51a488a85 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -111,6 +111,7 @@ 
 Copyright @copyright{} 2022 John Kehayias@*
 Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@*
 Copyright @copyright{} 2023 Giacomo Leidi@*
+Copyright @copyright{} 2023 Bruno Victal@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -28219,6 +28220,243 @@  Monitoring Services
 @end table
 @end deftp
 
+@anchor{vnstat}
+@subsubheading vnStat Network Traffic Monitor
+@cindex vnstat
+
+vnStat is a network traffic monitor that uses interface statistics provided
+by the kernel rather than traffic sniffing.  This makes it a light resource
+monitor, regardless of network traffic rate.
+
+@defvar vnstat-service-type
+This is the service type for the @uref{https://humdi.net/vnstat/,vnStat} daemon
+and accepts a @code{vnstat-configuration} value.
+@end defvar
+
+@c %start of fragment
+@deftp {Data Type} vnstat-configuration
+Available @code{vnstat-configuration} fields are:
+
+@table @asis
+@item @code{package} (default: @code{vnstat}) (type: file-like)
+The vnstat package.
+
+@item @code{database-dir} (default: @code{"/var/lib/vnstat"}) (type: string)
+Specifies the directory where the database is to be stored.  A full path
+must be given and a leading '/' isn't required.
+
+@item @code{#@{5-minute-hours@}#} (default: @code{48}) (type: maybe-integer)
+Data retention duration for the 5 minute resolution entries.  The
+configuration defines for how many past hours entries will be stored.
+Set to @code{-1} for unlimited entries or to @code{0} to disable the
+data collection of this resolution.
+
+@item @code{#@{64bit-interface-counters@}#} (default: @code{-2}) (type: maybe-integer)
+Select interface counter handling.  Set to @code{1} for defining that
+all interfaces use 64-bit counters on the kernel side and @code{0} for
+defining 32-bit counter.  Set to @code{-1} for using the old style logic
+used in earlier versions where counter values within 32-bits are assumed
+to be 32-bit and anything larger is assumed to be a 64-bit counter.  This
+may produce false results if a 64-bit counter is reset within the
+32-bits.  Set to @code{-2} for using automatic detection based on
+available kernel datastructures.
+
+@item @code{always-add-new-interfaces?} (default: @code{#t}) (type: maybe-boolean)
+Enable or disable automatic creation of new database entries for
+interfaces not currently in the database even if the database file
+already exists when the daemon is started.  New database entries will
+also get created for new interfaces seen while the daemon is running.
+Pseudo interfaces lo, lo0 and sit0 are always excluded from getting
+added.
+
+@item @code{bandwidth-detection?} (default: @code{#t}) (type: maybe-boolean)
+Try to automatically detect @var{max-bandwidth} value for each monitored
+interface.  Mostly only ethernet interfaces support this feature.
+@var{max-bandwidth} will be used as fallback value if detection fails.
+Any interface specific @var{max-BW} configuration will disable the
+detection for the specified interface.  In Linux, the detection is
+disabled for tun interfaces due to the Linux kernel always reporting 10
+Mbit regardless of the used real interface.
+
+@item @code{bandwidth-detection-interval} (default: @code{5}) (type: maybe-integer)
+How often in minutes interface specific detection of @var{max-bandwidth}
+is done for detecting possible changes when @var{bandwidth-detection} is
+enabled.  Can be disabled by setting to @code{0}.  Value range:
+@samp{0}..@samp{30}
+
+@item @code{boot-variation} (default: @code{15}) (type: maybe-integer)
+Time in seconds how much the boot time reported by system kernel can
+variate between updates.  Value range: @samp{0}..@samp{300}
+
+@item @code{check-disk-space?} (default: @code{#t}) (type: maybe-boolean)
+Enable or disable the availability check of at least some free disk
+space before a database write.
+
+@item @code{create-dirs?} (default: @code{#t}) (type: maybe-boolean)
+Enable or disable the creation of directories when a configured path
+doesn't exist.  This includes @var{database-dir}.
+
+@item @code{daemon-group} (type: maybe-string)
+Specify the group to which the daemon process should switch during
+startup.  The group can either be the name of the group or a numerical
+group id.  Leave empty to disable group switching.  This option can only
+be used when the process is started as root.
+
+@item @code{daemon-user} (type: maybe-string)
+Specify the user to which the daemon process should switch during
+startup.  The user can either be the login of the user or a numerical
+user id.  Leave empty to disable user switching.  This option can only
+be used when the process is started as root.
+
+@item @code{daily-days} (default: @code{62}) (type: maybe-integer)
+Data retention duration for the one day resolution entries.  The
+configuration defines for how many past days entries will be stored.  Set
+to @code{-1} for unlimited entries or to @code{0} to disable the data
+collection of this resolution.
+
+@item @code{database-synchronous} (default: @code{-1}) (type: maybe-integer)
+Change the setting of the SQLite "synchronous" flag which controls how
+much care is taken to ensure disk writes have fully completed when
+writing data to the database before continuing other actions.  Higher
+values take extra steps to ensure data safety at the cost of slower
+performance.  A value of @code{0} will result in all handling being left
+to the filesystem itself.  Set to @code{-1} to select the default value
+according to database mode controlled by
+@var{database-write-ahead-logging} setting.  See SQLite documentation
+for more details regarding values from @code{1} to @code{3}.  Value
+range: @samp{-1}..@samp{3}
+
+@item @code{database-write-ahead-logging?} (default: @code{#f}) (type: maybe-boolean)
+Enable or disable SQLite Write-Ahead Logging mode for the database.  See
+SQLite documentation for more details and note that support for
+read-only operations isn't available in older SQLite versions.
+
+@item @code{hourly-days} (default: @code{4}) (type: maybe-integer)
+Data retention duration for the one hour resolution entries.  The
+configuration defines for how many past days entries will be stored.  Set
+to @code{-1} for unlimited entries or to @code{0} to disable the data
+collection of this resolution.
+
+@item @code{log-file} (type: maybe-string)
+Specify log file path and name to be used if @var{use-logging} is set to
+@code{1}.
+
+@item @code{max-bandwidth} (type: maybe-integer)
+Maximum bandwidth for all interfaces.  If the interface specific traffic
+exceeds the given value then the data is assumed to be invalid and
+rejected.  Set to 0 in order to disable the feature.  Value range:
+@samp{0}..@samp{50000}
+
+@item @code{max-BW} (type: maybe-alist)
+Same as @var{max-bandwidth} but can be used for setting individual
+limits for selected interfaces.  This is an association list of
+interfaces as symbols/strings to integer values.  For example,
+@lisp
+(max-BW `((eth0 .  15000)
+          (ppp0 .  10000)))
+@end lisp
+@var{bandwidth-detection} is disabled on an interface specific level for
+each @var{max-BW} configuration.  Value range: @samp{0}..@samp{50000}
+
+@item @code{monthly-months} (default: @code{25}) (type: maybe-integer)
+Data retention duration for the one month resolution entries.  The
+configuration defines for how many past months entries will be stored.
+Set to @code{-1} for unlimited entries or to @code{0} to disable the
+data collection of this resolution.
+
+@item @code{month-rotate} (default: @code{1}) (type: maybe-integer)
+Day of month that months are expected to change.  Usually set to 1 but
+can be set to alternative values for example for tracking monthly billed
+traffic where the billing period doesn't start on the first day.  For
+example, if set to 7, days of February up to and including the 6th will
+count for January.  Changing this option will not cause existing data to
+be recalculated.  Value range: @samp{1}..@samp{28}
+
+@item @code{month-rotate-affects-years?} (default: @code{#f}) (type: maybe-boolean)
+Enable or disable @var{month-rotate} also affecting yearly data.
+Applicable only when @var{month-rotate} has a value greater than one.
+
+@item @code{offline-save-interval} (default: @code{30}) (type: maybe-integer)
+How often in minutes cached interface data is saved to file when all
+monitored interfaces are offline.  Value range:
+@var{save-interval}..@samp{60}
+
+@item @code{pid-file} (default: @code{"/var/run/vnstatd.pid"}) (type: maybe-string)
+Specify pid file path and name to be used.
+
+@item @code{poll-interval} (default: @code{5}) (type: maybe-integer)
+How often in seconds interfaces are checked for status changes.  Value
+range: @samp{2}..@samp{60}
+
+@item @code{rescan-database-on-save?} (type: maybe-boolean)
+Automatically discover added interfaces from the database and start
+monitoring.  The rescan is done every @var{save-interval} or
+@var{offline-save-interval} minutes depending on the current activity
+state.
+
+@item @code{save-interval} (default: @code{5}) (type: maybe-integer)
+How often in minutes cached interface data is saved to file.  Value
+range: ( @var{update-interval} / 60 )..@samp{60}
+
+@item @code{save-on-status-change?} (default: @code{#t}) (type: maybe-boolean)
+Enable or disable the additional saving to file of cached interface data
+when the availability of an interface changes, i.e., when an interface
+goes offline or comes online.
+
+@item @code{time-sync-wait} (default: @code{5}) (type: maybe-integer)
+How many minutes to wait during daemon startup for system clock to sync
+if most recent database update appears to be in the future.  This may be
+needed in systems without a real-time clock (RTC) which require some
+time after boot to query and set the correct time.  @code{0} = wait
+disabled.  Value range: @samp{0}..@samp{60}
+
+@item @code{top-day-entries} (default: @code{20}) (type: maybe-integer)
+Data retention duration for the top day entries.  The configuration
+defines how many of the past top day entries will be stored.  Set to
+@code{-1} for unlimited entries or to @code{0} to disable the data
+collection of this resolution.
+
+@item @code{trafficless-entries?} (default: @code{#t}) (type: maybe-boolean)
+Create database entries even when there is no traffic during the entry's
+time period.
+
+@item @code{update-file-owner?} (default: @code{#t}) (type: maybe-boolean)
+Enable or disable the update of file ownership during daemon process
+startup.  During daemon startup, only database, log and pid files will
+be modified if the user or group change feature ( @var{daemon-user} or
+@var{daemon-group} ) is enabled and the files don't match the requested
+user or group.  During manual database creation, this option will cause
+file ownership to be inherited from the database directory if the
+directory already exists.  This option only has effect when the process
+is started as root or via sudo.
+
+@item @code{update-interval} (default: @code{20}) (type: maybe-integer)
+How often in seconds the interface data is updated.  Value range:
+@var{poll-interval}..@samp{300}
+
+@item @code{use-logging} (default: @code{2}) (type: maybe-integer)
+Enable or disable logging.  This option is ignored when the daemon is
+started with .B "-n, --nodaemon" which results in all log output being
+shown in terminal the daemon process is using.  @code{0} = disabled,
+@code{1} = logfile and @code{2} = syslog.
+
+@item @code{use-UTC?} (type: maybe-boolean)
+Enable or disable using UTC as timezone in the database for all entries.
+When enabled, all entries added to the database will use UTC regardless
+of the configured system timezone.  When disabled, the configured system
+timezone will be used.  Changing this setting will not result in already
+existing data to be modified.
+
+@item @code{yearly-years} (default: @code{-1}) (type: maybe-integer)
+Data retention duration for the one year resolution entries.  The
+configuration defines for how many past years entries will be stored.
+Set to @code{-1} for unlimited entries or to @code{0} to disable the
+data collection of this resolution.
+
+@end table
+@end deftp
+@c %end of fragment
+
 @subsubheading Zabbix server
 @cindex zabbix zabbix-server
 Zabbix is a high performance monitoring system that can collect data from a
diff --git a/gnu/services/monitoring.scm b/gnu/services/monitoring.scm
index 44e2e8886c..a83dcf315b 100644
--- a/gnu/services/monitoring.scm
+++ b/gnu/services/monitoring.scm
@@ -3,6 +3,7 @@ 
 ;;; Copyright © 2018, 2019 Gábor Boskovits <boskovits@gmail.com>
 ;;; Copyright © 2018, 2019, 2020 Oleg Pykhalov <go.wigust@gmail.com>
 ;;; Copyright © 2022 Marius Bakke <marius@gnu.org>
+;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -26,6 +27,7 @@  (define-module (gnu services monitoring)
   #:use-module (gnu services web)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages monitoring)
+  #:use-module (gnu packages networking)
   #:use-module (gnu system shadow)
   #:use-module (guix gexp)
   #:use-module (guix packages)
@@ -34,6 +36,7 @@  (define-module (gnu services monitoring)
   #:use-module ((guix ui) #:select (display-hint G_))
   #:use-module (ice-9 match)
   #:use-module (ice-9 rdelim)
+  #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-35)
   #:export (darkstat-configuration
@@ -45,6 +48,10 @@  (define-module (gnu services monitoring)
             prometheus-node-exporter-web-listen-address
             prometheus-node-exporter-service-type
 
+            vnstat-configuration
+            vnstat-configuration?
+            vnstat-service-type
+
             zabbix-server-configuration
             zabbix-server-service-type
             zabbix-agent-configuration
@@ -196,6 +203,412 @@  (define prometheus-node-exporter-service-type
                         prometheus-node-exporter-shepherd-service)))
    (default-value (prometheus-node-exporter-configuration))))
 
+
+;;;
+;;; vnstat daemon
+;;;
+
+(define* (camelfy-field-name field-name #:key (dromedary? #f))
+  (match (string-split (symbol->string field-name) #\-)
+    ((head tail ...)
+     (string-join (cons (if dromedary? head (string-upcase head 0 1))
+                        (map string-capitalize tail)) ""))))
+
+(define (dock-field-name field-name)
+  "Drop rightmost '?' character"
+  (let ((str (symbol->string field-name)))
+    (if (string-suffix? "?" str)
+        (string->symbol (string-drop-right str 1))
+        field-name)))
+
+(define (vnstat-serialize-string field-name value)
+  #~(format #f "~a ~s~%"
+            #$(camelfy-field-name field-name)
+            #$value))
+
+(define vnstat-serialize-integer vnstat-serialize-string)
+
+(define (vnstat-serialize-boolean field-name value)
+  #~(format #f "~a ~a~%"
+            #$(camelfy-field-name (dock-field-name field-name))
+            #$(if value 1 0)))
+
+(define (vnstat-serialize-alist field-name value)
+  (generic-serialize-alist string-append
+                           (lambda (iface val)
+                             (vnstat-serialize-integer
+                              (format #f "MaxBW~a" iface) val))
+                           value))
+
+(define-maybe string  (prefix vnstat-))
+(define-maybe integer (prefix vnstat-))
+(define-maybe boolean (prefix vnstat-))
+(define-maybe alist   (prefix vnstat-))
+
+;; Documentation strings from vnstat.conf manpage adapted to texinfo.
+;; vnstat checkout: v2.10, commit b3408af1c609aa6265d296cab7bfe59a61d7cf70
+(define-configuration vnstat-configuration
+  (package
+    (file-like vnstat)
+    "The vnstat package."
+    empty-serializer)
+
+  (database-dir
+   (string "/var/lib/vnstat")
+   "\
+Specifies the directory where the database is to be stored.
+A full path must be given and a leading '/' isn't required.")  
+
+  (5-minute-hours
+   (maybe-integer 48)
+   "\
+Data retention duration for the 5 minute resolution entries. The configuration
+defines for how many past hours entries will be stored. Set to @code{-1} for
+unlimited entries or to @code{0} to disable the data collection of this
+resolution.")
+
+  (64bit-interface-counters
+   (maybe-integer -2)
+   "\
+Select interface counter handling. Set to @code{1} for defining that all interfaces
+use 64-bit counters on the kernel side and @code{0} for defining 32-bit counter. Set
+to @code{-1} for using the old style logic used in earlier versions where counter
+values within 32-bits are assumed to be 32-bit and anything larger is assumed to
+be a 64-bit counter. This may produce false results if a 64-bit counter is
+reset within the 32-bits. Set to @code{-2} for using automatic detection based on
+available kernel datastructures.")
+
+  (always-add-new-interfaces?
+   (maybe-boolean #t)
+   "\
+Enable or disable automatic creation of new database entries for interfaces not
+currently in the database even if the database file already exists when the
+daemon is started. New database entries will also get created for new interfaces
+seen while the daemon is running. Pseudo interfaces lo, lo0 and sit0 are always
+excluded from getting added.")
+
+  (bandwidth-detection?
+   (maybe-boolean #t)
+   "\
+Try to automatically detect
+@var{max-bandwidth}
+value for each monitored interface. Mostly only ethernet interfaces support
+this feature.
+@var{max-bandwidth}
+will be used as fallback value if detection fails. Any interface specific
+@var{max-BW}
+configuration will disable the detection for the specified interface.
+In Linux, the detection is disabled for tun interfaces due to the
+Linux kernel always reporting 10 Mbit regardless of the used real interface.")
+
+  (bandwidth-detection-interval
+   (maybe-integer 5)
+   "\
+How often in minutes interface specific detection of
+@var{max-bandwidth}
+is done for detecting possible changes when
+@var{bandwidth-detection}
+is enabled. Can be disabled by setting to @code{0}. Value range: @samp{0}..@samp{30}")
+
+  (boot-variation
+   (maybe-integer 15)
+   "\
+Time in seconds how much the boot time reported by system kernel can variate
+between updates. Value range: @samp{0}..@samp{300}")
+
+  (check-disk-space?
+   (maybe-boolean #t)
+   "\
+Enable or disable the availability check of at least some free disk space before
+a database write.")
+
+  (create-dirs?
+   (maybe-boolean #t)
+   "\
+Enable or disable the creation of directories when a configured path doesn't
+exist. This includes @var{database-dir}.")
+
+  (daemon-group
+   maybe-string
+   "\
+Specify the group to which the daemon process should switch during startup.
+The group can either be the name of the group or a numerical group id.
+Leave empty to disable group switching. This option can only be used when
+the process is started as root.")
+
+  (daemon-user
+   maybe-string
+   "\
+Specify the user to which the daemon process should switch during startup.
+The user can either be the login of the user or a numerical user id.
+Leave empty to disable user switching. This option can only be used when
+the process is started as root.")
+
+  (daily-days
+   (maybe-integer 62)
+   "\
+Data retention duration for the one day resolution entries. The configuration
+defines for how many past days entries will be stored. Set to @code{-1} for
+unlimited entries or to @code{0} to disable the data collection of this
+resolution.")
+
+  (database-synchronous
+   (maybe-integer -1)
+   "\
+Change the setting of the SQLite \"synchronous\" flag which controls how much
+care is taken to ensure disk writes have fully completed when writing data to
+the database before continuing other actions. Higher values take extra steps
+to ensure data safety at the cost of slower performance. A value of @code{0} will
+result in all handling being left to the filesystem itself. Set to @code{-1} to
+select the default value according to database mode controlled by
+@var{database-write-ahead-logging}
+setting. See SQLite documentation for more details regarding values from @code{1}
+to @code{3}. Value range: @samp{-1}..@samp{3}")
+
+  (database-write-ahead-logging?
+   (maybe-boolean #f)
+   "\
+Enable or disable SQLite Write-Ahead Logging mode for the database. See SQLite
+documentation for more details and note that support for read-only operations
+isn't available in older SQLite versions.")
+
+  (hourly-days
+   (maybe-integer 4)
+   "\
+Data retention duration for the one hour resolution entries. The configuration
+defines for how many past days entries will be stored. Set to @code{-1} for
+unlimited entries or to @code{0} to disable the data collection of this
+resolution.")
+
+  (log-file
+   maybe-string
+   "\
+Specify log file path and name to be used if @var{use-logging} is set to @code{1}.")
+
+  (max-bandwidth
+   maybe-integer
+   "\
+Maximum bandwidth for all interfaces. If the interface specific traffic
+exceeds the given value then the data is assumed to be invalid and rejected.
+Set to 0 in order to disable the feature. Value range: @samp{0}..@samp{50000}")
+
+  ;; documentation adapted for alist type
+  (max-BW
+   maybe-alist
+   "\
+Same as
+@var{max-bandwidth}
+but can be used for setting individual limits
+for selected interfaces. This is an association list of interfaces
+as symbols/strings to integer values. For example,
+@lisp
+(max-BW
+ `((eth0 . 15000)
+   (ppp0 . 10000)))
+@end lisp
+@var{bandwidth-detection}
+is disabled on an interface specific level for each
+@var{max-BW}
+configuration. Value range: @samp{0}..@samp{50000}")
+
+  (monthly-months
+   (maybe-integer 25)
+   "\
+Data retention duration for the one month resolution entries. The configuration
+defines for how many past months entries will be stored. Set to @code{-1} for
+unlimited entries or to @code{0} to disable the data collection of this
+resolution.")
+
+  (month-rotate
+   (maybe-integer 1)
+   "\
+Day of month that months are expected to change. Usually set to
+1 but can be set to alternative values for example for tracking
+monthly billed traffic where the billing period doesn't start on
+the first day. For example, if set to 7, days of February up to and
+including the 6th will count for January. Changing this option will
+not cause existing data to be recalculated. Value range: @samp{1}..@samp{28}")
+
+  (month-rotate-affects-years?
+   (maybe-boolean #f)
+   "\
+Enable or disable
+@var{month-rotate}
+also affecting yearly data. Applicable only when
+@var{month-rotate}
+has a value greater than one.")
+
+  (offline-save-interval
+   (maybe-integer 30)
+   "\
+How often in minutes cached interface data is saved to file when all monitored
+interfaces are offline. Value range:
+@var{save-interval}..@samp{60}")
+
+  (pid-file
+   (maybe-string "/var/run/vnstatd.pid")
+   "\
+Specify pid file path and name to be used.")
+
+  (poll-interval
+   (maybe-integer 5)
+   "\
+How often in seconds interfaces are checked for status changes.
+Value range: @samp{2}..@samp{60}")
+
+  (rescan-database-on-save?
+   maybe-boolean
+   "\
+Automatically discover added interfaces from the database and start monitoring.
+The rescan is done every
+@var{save-interval}
+or
+@var{offline-save-interval}
+minutes depending on the current activity state.")
+
+  (save-interval
+   (maybe-integer 5)
+   "\
+How often in minutes cached interface data is saved to file.
+Value range: (
+@var{update-interval} / 60 )..@samp{60}")
+
+  (save-on-status-change?
+   (maybe-boolean #t)
+   "\
+Enable or disable the additional saving to file of cached interface data
+when the availability of an interface changes, i.e., when an interface goes
+offline or comes online.")
+
+  (time-sync-wait
+   (maybe-integer 5)
+   "\
+How many minutes to wait during daemon startup for system clock to sync if
+most recent database update appears to be in the future. This may be needed
+in systems without a real-time clock (RTC) which require some time after boot
+to query and set the correct time. @code{0} = wait disabled.
+Value range: @samp{0}..@samp{60}")
+
+  (top-day-entries
+   (maybe-integer 20)
+   "\
+Data retention duration for the top day entries. The configuration
+defines how many of the past top day entries will be stored. Set to @code{-1} for
+unlimited entries or to @code{0} to disable the data collection of this
+resolution.")
+
+  (trafficless-entries?
+   (maybe-boolean #t)
+   "\
+Create database entries even when there is no traffic during the entry's time
+period.")
+
+  (update-file-owner?
+   (maybe-boolean #t)
+   "\
+Enable or disable the update of file ownership during daemon process startup.
+During daemon startup, only database, log and pid files will be modified if the
+user or group change feature (
+@var{daemon-user}
+or
+@var{daemon-group}
+) is enabled and the files don't match the requested user or group. During manual
+database creation, this option will cause file ownership to be inherited from the
+database directory if the directory already exists. This option only has effect
+when the process is started as root or via sudo.")
+
+  (update-interval
+   (maybe-integer 20)
+   "\
+How often in seconds the interface data is updated. Value range:
+@var{poll-interval}..@samp{300}")
+
+  (use-logging
+   (maybe-integer 2)
+   "\
+Enable or disable logging. This option is ignored when the daemon is started with
+.B \"-n, --nodaemon\"
+which results in all log output being shown in terminal the daemon process is using.
+@code{0} = disabled, @code{1} = logfile and @code{2} = syslog.")
+
+  (use-UTC?
+   maybe-boolean
+   "\
+Enable or disable using UTC as timezone in the database for all entries. When
+enabled, all entries added to the database will use UTC regardless of the
+configured system timezone. When disabled, the configured system timezone
+will be used. Changing this setting will not result in already existing
+data to be modified.")
+
+  (yearly-years
+   (maybe-integer -1)
+   "\
+Data retention duration for the one year resolution entries. The configuration
+defines for how many past years entries will be stored. Set to @code{-1} for
+unlimited entries or to @code{0} to disable the data collection of this
+resolution.")
+
+  (prefix vnstat-))
+
+(define (vnstat-serialize-configuration config)
+  (mixed-text-file
+   "vnstat.conf"
+   (serialize-configuration config vnstat-configuration-fields)))
+
+(define (vnstat-shepherd-service config)
+  (let ((config-file (vnstat-serialize-configuration config)))
+    (match-record config <vnstat-configuration> (package pid-file)
+      (shepherd-service
+       (documentation "Run vnstatd.")
+       (requirement `(networking))
+       (provision '(vnstatd))
+       (start #~(make-forkexec-constructor
+                 (list #$(file-append package "/sbin/vnstatd")
+                       "--daemon"
+                       "--config" #$config-file)
+                 #:pid-file #$pid-file))
+       (stop #~(make-kill-destructor))
+       (actions
+        (list (shepherd-configuration-action config-file)
+              (shepherd-action
+               (name 'reload)
+               (documentation "Reload vnstatd.")
+               (procedure
+                #~(lambda (pid)
+                    (if pid
+                        (begin
+                          (kill pid SIGHUP)
+                          (format #t
+                                  "Issued SIGHUP to vnstatd (PID ~a)."
+                                  pid))
+                        (format #t "vnstatd is not running.")))))))))))
+
+(define (vnstat-account-service config)
+  (match-record config <vnstat-configuration> (daemon-group daemon-user)
+    (if (every maybe-value-set? (list daemon-group daemon-user))
+        (list
+         (user-group
+          (name daemon-group)
+          (system? #t))
+         (user-account
+          (name daemon-user)
+          (group daemon-group)
+          (system? #t)
+          (home-directory "/var/empty")
+          (shell (file-append shadow "/sbin/nologin"))))
+        '())))
+
+(define vnstat-service-type
+  (service-type
+   (name 'vnstat)
+   (description "vnStat network-traffic monitor service.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             (compose list vnstat-shepherd-service))
+          (service-extension account-service-type
+                             vnstat-account-service)))
+   (default-value (vnstat-configuration))))
+
 
 ;;;
 ;;; Zabbix server