Toggle diff (543 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index d6e17c74cd..6cb11200cd 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -123,7 +123,7 @@
Copyright @copyright{} 2023 Thomas Ieong@*
Copyright @copyright{} 2023 Saku Laesvuori@*
Copyright @copyright{} 2023 Graham James Addis@*
-Copyright @copyright{} 2023, 2024 Tomas Volf@*
+Copyright @copyright{} 2023-2025 Tomas Volf@*
Copyright @copyright{} 2024 Herman Rimm@*
Copyright @copyright{} 2024 Matthew Trzcinski@*
Copyright @copyright{} 2024 Richard Sent@*
@@ -420,7 +420,7 @@ Top
* Network File System:: NFS related services.
* Samba Services:: Samba services.
* Continuous Integration:: Cuirass and Laminar services.
-* Power Management Services:: Extending battery life.
+* Power Management Services:: Extending battery life, etc.
* Audio Services:: The MPD.
* Virtualization Services:: Virtualization services.
* Version Control Services:: Providing remote access to Git repositories.
@@ -19255,7 +19255,7 @@ Services
* Network File System:: NFS related services.
* Samba Services:: Samba services.
* Continuous Integration:: Cuirass and Laminar services.
-* Power Management Services:: Extending battery life.
+* Power Management Services:: Extending battery life, etc.
* Audio Services:: The MPD.
* Virtualization Services:: Virtualization services.
* Version Control Services:: Providing remote access to Git repositories.
@@ -36263,6 +36263,374 @@ Power Management Services
@end table
@end deftp
+The @code{(gnu services power)} module provides a service definition for
+@uref{http://www.apcupsd.org/, apcupsd}, a utility to interact with APC
+UPSes. Apcupsd also works with some OEM-branded products manufactured
+by APC.
+
+@defvar apcupsd-service-type
+The service type for apcupsd. For USB UPSes no configuration is
+necessary, however tweaking some fields to better suit your needs might
+be desirable. The defaults are taken from the upstream configuration
+and they are not very conservative (@code{battery-level} of 5% is too
+low in my opinion).
+
+The default event handlers do send emails, read more in
+@ref{apcupsd-event-handlers}.
+
+@lisp
+(service apcupsd-service-type)
+@end lisp
+@end defvar
+
+@deftp {Data Type} apcupsd-configuration
+
+Available @code{apcupsd-configuration} fields are:
+
+@table @asis
+@item @code{package} (default: @code{apcupsd}) (type: package)
+Package to use.
+
+@item @code{shepherd-base-name} (default: @code{apcupsd}) (type: symbol)
+Base name of the shepherd service. You can add the service multiple
+times with different prefix to manage multiple UPSes.
+
+@item @code{auto-start?} (default: @code{#t}) (type: boolean)
+Should the shepherd service auto-start?
+
+@item @code{pid-file} (default: @code{"/var/run/apcupsd.pid"}) (type: string)
+Path to the pid file.
+
+@item @code{debug-level} (default: @code{0}) (type: integer)
+Logging verbosity. Bigger number means more logs. In the source code I
+saw up to @code{300}, so for all logs, @code{999} seems reasonable.
+
+@item @code{run-dir} (default: @code{"/var/run/apcupsd"}) (type: string)
+Directory containing runtime information. You need to change this if
+you desire to run multiple instances of the daemon.
+
+@item @code{name} (type: maybe-string)
+Use this to give your UPS a name in log files and such. This is
+particularly useful if you have multiple UPSes. This does not set the
+EEPROM. It should be 8 characters or less.
+
+@item @code{cable} (default: @code{usb}) (type: enum-cable)
+The type of cable connecting the UPS to your computer. Possible generic
+choices are @code{'simple}, @code{'smart}, @code{'ether} and
+@code{'usb}. Or a specific cable model number may be used:
+@code{'940-0119A}, @code{'940-0127A}, @code{'940-0128A},
+@code{'940-0020B}, @code{'940-0020C}, @code{'940-0023A},
+@code{'940-0024B}, @code{'940-0024C}, @code{'940-1524C},
+@code{'940-0024G}, @code{'940-0095A}, @code{'940-0095B},
+@code{'940-0095C}, @code{'940-0625A}, @code{'M-04-02-2000}.
+
+@item @code{type} (default: @code{usb}) (type: enum-type)
+Type of the UPS you have.
+
+@table @code
+@item apcsmart
+Newer serial character device, appropriate for SmartUPS models using a
+serial cable (not USB).
+
+@item usb
+Most new UPSes are USB.
+
+@item net
+Network link to a master apcupsd through apcupsd's Network Information
+Server. This is used if the UPS powering your computer is connected to
+a different computer for monitoring.
+
+@item snmp
+SNMP network link to an SNMP-enabled UPS device.
+
+@item netsnmp
+Same as SNMP above but requires use of the net-snmp library. Unless you
+have a specific need for this old driver, you should use @code{'snmp}
+instead.
+
+@item dumb
+Old serial character device for use with simple-signaling UPSes.
+
+@item pcnet
+PowerChute Network Shutdown protocol which can be used as an alternative
+to SNMP with the AP9617 family of smart slot cards.
+
+@item modbus
+Serial device for use with newest SmartUPS models supporting the MODBUS
+protocol.
+
+@end table
+
+@item @code{device} (default: @code{""}) (type: string)
+For USB UPSes, usually you want to set this to an empty string (the
+default). For other UPS types, you must specify an appropriate port or
+address.
+
+@table @code
+@item apcsmart
+Set to the appropriate @file{/dev/tty**} device.
+
+@item usb
+A null string setting enables autodetection, which is the best choice
+for most installations.
+
+@item net
+Set to @code{@var{hostname}:@var{port}}.
+
+@item snmp
+Set to @code{@var{hostname}:@var{port}:@var{vendor}:@var{community}}.
+@var{hostname} is the ip address or hostname of the UPS on the network.
+@var{vendor} can be can be "APC" or "APC_NOTRAP". "APC_NOTRAP" will
+disable SNMP trap catching; you usually want "APC". @var{port} is
+usually 161. @var{community} is usually "private".
+
+@item netsnmp
+Same as @code{'snmp}.
+
+@item dumb
+Set to the appropriate @file{/dev/tty**} device.
+
+@item pcnet
+Set to @code{@var{ipaddr}:@var{username}:@var{passphrase}:@var{port}}.
+@var{ipaddr} is the IP address of the UPS management card.
+@var{username} and @var{passphrase} are the credentials for which the
+card has been configured. @var{port} is the port number on which to
+listen for messages from the UPS, normally 3052. If this parameter is
+empty or missing, the default of 3052 will be used.
+
+@item modbus
+Set to the appropriate @file{/dev/tty**} device. You can also leave it
+empty for MODBUS over USB or set to the serial number of the UPS.
+
+@end table
+
+@item @code{poll-time} (default: @code{60}) (type: integer)
+Interval (in seconds) at which apcupsd polls the UPS for status. This
+setting applies both to directly-attached UPSes (apcsmart, usb, dumb)
+and networked UPSes (net, snmp). Lowering this setting will improve
+apcupsd's responsiveness to certain events at the cost of higher CPU
+utilization.
+
+@item @code{on-batery-delay} (default: @code{6}) (type: integer)
+The the time in seconds from when a power failure is detected until we
+react to it with an onbattery event. The @code{'powerout} event will be
+triggered immediately when a power failure is detected. However, the
+@code{'onbattery} event will be trigger only after this delay.
+
+@item @code{battery-level} (default: @code{5}) (type: integer)
+If during a power failure, the remaining battery percentage (as reported
+by the UPS) is below or equal to this value, apcupsd will initiate a
+system shutdown.
+
+@quotation Note
+@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work
+in conjunction, so the first that occurs will cause the initation of a
+shutdown.
+@end quotation
+
+@item @code{remaining-minutes} (default: @code{3}) (type: integer)
+If during a power failure, the remaining runtime in minutes (as
+calculated internally by the UPS) is below or equal to this value,
+apcupsd will initiate a system shutdown.
+
+@quotation Note
+@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work
+in conjunction, so the first that occurs will cause the initation of a
+shutdown.
+@end quotation
+
+@item @code{timeout} (default: @code{0}) (type: integer)
+If during a power failure, the UPS has run on batteries for this many
+seconds or longer, apcupsd will initiate a system shutdown. A value of
+0 disables this timer.
+
+@quotation Note
+@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work
+in conjunction, so the first that occurs will cause the initation of a
+shutdown.
+@end quotation
+
+@item @code{annoy-interval} (default: @code{300}) (type: integer)
+Time in seconds between annoying users (via the @code{'annoyme} event)
+to sign off prior to system shutdown. 0 disables.
+
+@item @code{annoy-delay} (default: @code{60}) (type: integer)
+Initial delay in seconds after power failure before warning users to get
+off the system.
+
+@item @code{no-logon} (default: @code{disable}) (type: enum-no-logon)
+The condition which determines when users are prevented from logging in
+during a power failure.
+
+@item @code{kill-delay} (default: @code{0}) (type: integer)
+If this is non-zero, apcupsd will continue running after a shutdown has
+been requested, and after the specified time in seconds attempt to kill
+the power. This is for use on systems where apcupsd cannot regain
+control after a shutdown.
+
+@item @code{net-server} (default: @code{#f}) (type: boolean)
+If enabled, a network information server process will be started.
+
+@item @code{net-server-ip} (default: @code{"127.0.0.1"}) (type: string)
+IP address on which NIS server will listen for incoming connections.
+
+@item @code{net-server-port} (default: @code{3551}) (type: integer)
+IP port on which NIS server will listen for incoming connections.
+
+@item @code{net-server-events-file} (type: maybe-string)
+If you want the last few EVENTS to be available over the network by the
+network information server, you must set this to a file patch.
+
+@item @code{net-server-events-file-max-size} (default: @code{10}) (type: integer)
+Maximum size of the events file in kilobytes.
+
+@item @code{class} (default: @code{standalone}) (type: enum-class)
+Normally standalone unless you share an UPS using an APC ShareUPS card.
+
+@item @code{mode} (default: @code{disable}) (type: enum-mode)
+Normally disable unless you share an UPS using an APC ShareUPS card.
+
+@item @code{stat-time} (default: @code{0}) (type: integer)
+Time interval in seconds between writing the status file, 0 disables.
+
+@item @code{log-stats} (default: @code{#f}) (type: boolean)
+Also write the stats as a logs. This generates a lot of output.
+
+@item @code{data-time} (default: @code{0}) (type: integer)
+Time interval in seconds between writing the data records to the log
+file, 0 disables.
+
+@item @code{facility} (type: maybe-string)
+The logging facility for the syslog.
+
+@item @code{event-handlers} (type: apcupsd-event-handlers)
+Handlers for events produced by apcupsd.
+
+@end table
+
+@end deftp
+
+@anchor{apcupsd-event-handlers}
+@deftp {Data Type} apcupsd-event-handlers
+
+For description of the events please refer to the @command{apcupsd}'s
+manual, which can be found in the @samp{apcupsd-doc} package.
+
+Each handler shall be a gexp. It is spliced into the control script for
+the daemon. In addition to the standard Guile programming environment,
+these procedures and variables are also available.
+
+@table @code
+@item conf
+Variable containing path to the configuration file.
+
+@item powerfail-file
+Variable containing path to the powerfail file.
+
+@item cmd
+The event currently being handled.
+
+@item name
+The name of the UPS as specified in the configuration file.
+
+@item connected
+Is @code{"1"} if apcupsd is connected to the UPS via a serial port (or a
+USB port). In most configurations, this will be the case. In the case
+of a Slave machine where apcupsd is not directly connected to the UPS,
+this value will be @code{"0"}.
+
+@item powered
+Is @code{"1"} if the computer on which @command{apcupsd} is running is
+powered by the UPS and @code{"0"} if not. At the moment, this value is
+unimplemented and always @code{"0"}.
+
+@item (err @var{fmt} @var{args...})
+Wrapper around @code{format} outputting to @code{(current-error-port)}.
+
+@item (wall @var{fmt} @var{args...})
+Wrapper around @code{format} outputting via @command{wall}.
+
+@item (apcupsd @var{args...})
+Call @command{apcupsd} while passing the correct configuration file and
+all the arguments.
+
+@item (mail-to-root @var{subject} @var{body})
+Send an email to the local administrator. This procedure assumes the
+@command{sendmail} is located at @command{/run/privileged/bin/sendmail}
+(as would be the case with @code{opensmtpd-service-type}).
+
+@end table
+
+Available @code{apcupsd-event-handlers} fields are:
+
+@table @asis
+@item @code{killpower} (type: gexp)
+Handler for killpower event.
+
+@item @code{commfailure} (type: gexp)
+Handler for commfailure event.
+
+@item @code{commok} (type: gexp)
+Handler for commfailure event.
+
+@item @code{powerout} (type: gexp)
+Handler for powerout event.
+
+@item @code{onbattery} (type: gexp)
+Handler for onbattery event.
+
+@item @code{offbattery} (type: gexp)
+Handler for offbattery event.
+
+@item @code{mainsback} (type: gexp)
+Handler for mainsback event.
+
+@item @code{failing} (type: gexp)
+Handler for failing event.
+
+@item @code{timeout} (type: gexp)
+Handler for timeout event.
+
+@item @code{loadlimit} (type: gexp)
+Handler for loadlimit event.
+
+@item @code{runlimit} (type: gexp)
+Handler for runlimit event.
+
+@item @code{doreboot} (type: gexp)
+Handler for doreboot event.
+
+@item @code{doshutdown} (type: gexp)
+Handler for doshutdown event.
+
+@item @code{annoyme} (type: gexp)
+Handler for annoyme event.
+
+@item @code{emergency} (type: gexp)
+Handler for emergency event.
+
+@item @code{changeme} (type: gexp)
+Handler for changeme event.
+
+@item @code{remotedown} (type: gexp)
+Handler for remotedown event.
+
+@item @code{startselftest} (type: gexp)
+Handler for startselftest event.
+
+@item @code{endselftest} (type: gexp)
+Handler for endselftest event.
+
+@item @code{battdetach} (type: gexp)
+Handler for battdetach event.
+
+@item @code{battattach} (type: gexp)
+Handler for battattach event.
+
+@end table
+
+@end deftp
+
@node Audio Services
@subsection Audio Services
diff --git a/gnu/local.mk b/gnu/local.mk
index 6ca7bf68ac..dea0e43fe6 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -758,6 +758,7 @@ GNU_SYSTEM_MODULES = \
%D%/services/nix.scm \
%D%/services/nfs.scm \
%D%/services/pam-mount.scm \
+ %D%/services/power.scm \
%D%/services/science.scm \
%D%/services/security.scm \
%D%/services/security-token.scm \
diff --git a/gnu/services/power.scm b/gnu/services/power.scm
new file mode 100644
index 0000000000..a6f9259eb3
--- /dev/null
+++ b/gnu/services/power.scm
@@ -0,0 +1,690 @@
+;;; Copyright (C) 2025 Tomas Volf <~@wolfsden.cz>
+
+;;;; Commentary:
+
+;;; Power-related services.
+
+;;;; Code:
+
+(define-module (gnu services power)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 match)
+ #:use-module (gnu)
+ #:use-module (gnu packages admin)
+ #:use-module (gnu packages linux)
+ #:use-module (gnu packages power)
+ #:use-module (gnu services)
+ #:use-module (gnu services configuration)
+ #:use-module (gnu services shepherd)
+ #:use-module (guix packages)
+ #:use-module (guix records)
+ #:export (apcupsd-service-type
+
+ apcupsd-configuration
+ apcupsd-configuration-package
+ apcupsd-configuration-shepherd-base-name
+ apcupsd-configuration-auto-start?
+ apcupsd-configuration-pid-file
+ apcupsd-configuration-debug-level
+ apcupsd-configuration-run-dir
+ apcupsd-configuration-name
+ apcupsd-configuration-cable
+ apcupsd-configuration-type
+ apcupsd-configuration-device
+ apcupsd-configuration-poll-time
+ apcupsd-configuration-on-batery-delay
+ apcupsd-configuration-battery-level
+ apcupsd-configuration-remaining-minutes
+ apcupsd-configuration-timeout
+ apcupsd-configuration-annoy-interval
+ apcupsd-configuration-annoy-delay
+ apcupsd-configuration-no-logon
+ apcupsd-configuration-kill-delay
+ apcupsd-configuration-net-server
+ apcupsd-configuration-net-server-ip
+ apcupsd-configuration-net-server-port
+ apcupsd-configuration-net-server-events-file
+ apcupsd-configuration-net-server-events-file-max-size
+ apcupsd-configuration-class
+ apcupsd-configuration-mode
+ apcupsd-configuration-stat-time
+ apcupsd-configuration-log-stats
+ apcupsd-configuration-data-time
+ apcupsd-configuration-facility
+ apcupsd-configuration-event-handlers
+
+ apcupsd-event-handlers-annoyme
+ apcupsd-event-handlers-battattach
+ apcupsd-event-handlers-battdetach
+ apcupsd-event-handlers-changeme
+ apcupsd-event-handlers-commfailure
+ apcupsd-event-handlers-commok
+ apcupsd-event-handlers-doreboot
+ apcupsd-event-handlers-doshutdown
+ apcupsd-event-handlers-emergency
+ apcupsd-event-handlers-endselftest
+ apcupsd-event-handlers-failing
+ apcupsd-event-handlers-killpower
+ apcupsd-event-handlers-loadlimit
+ apcupsd-event-handlers-mainsback
+ apcupsd-event-handlers-offbattery
+ apcupsd-event-handlers-onbattery
+ apcupsd-event-handlers-powerout
+ apcupsd-event-handlers-remotedown
+ apcupsd-event-handlers-runlimit
+ apcupsd-event-handlers-startselftest
+ apcupsd-event-handlers-timeout))
+
+(define-configuration/no-serialization apcupsd-event-handlers
+ (killpower
+ (gexp
+ #~((wall "Apccontrol doing: apcupsd --killpower on UPS ~a" name)
+ (sleep 10)
+ (apcupsd "--killpower")
+ (wall "Apccontrol has done: apcupsd --killpower on UPS ~a" name)))
+ "Handler for killpower event.")
+ (commfailure
+ (gexp
+ #~((let ((msg (format #f "~a Communications with UPS ~a lost."
+ (gethostname) name)))
+ (mail-to-root msg msg))
+ (wall "Warning: communications lost with UPS ~a" name)))
+ "Handler for commfailure event.")
+ (commok
+ (gexp
+ #~((let ((msg (format #f "~a Communications with UPS ~a restored."
+ (gethostname) name)))
+ (mail-to-root msg msg))
+ (wall "Communications restored with UPS ~a" name)))
+ "Handler for commfailure event.")
+ (powerout
+ (gexp
+ #~(#t))
+ "Handler for powerout event.")
+ (onbattery
+ (gexp
+ #~((let ((msg (format #f "~a UPS ~a Power Failure !!!"
+ (gethostname) name)))
+ (mail-to-root msg msg))
+ (wall "Power failure on UPS ~a. Running on batteries." name)))
+ "Handler for onbattery event.")
+ (offbattery
+ (gexp
+ #~((let ((msg (format #f "~a UPS ~a Power has returned."
+ (gethostname) name)))
+ (mail-to-root msg msg))
+ (wall "Power has returned on UPS ~a..." name)))
+ "Handler for offbattery event.")
+ (mainsba