From patchwork Sun Feb 16 15:16:46 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tomas Volf <~@wolfsden.cz> X-Patchwork-Id: 38748 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id DAD7A27BBE9; Sun, 16 Feb 2025 15:18:21 +0000 (GMT) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_ADSP_ALL,DKIM_SIGNED,DKIM_VALID,MAILING_LIST_MULTI, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_VALIDITY_CERTIFIED,RCVD_IN_VALIDITY_RPBL, RCVD_IN_VALIDITY_SAFE,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id 335F527BBE2 for ; Sun, 16 Feb 2025 15:18:19 +0000 (GMT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1tjgPF-0001em-PR; Sun, 16 Feb 2025 10:18:05 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1tjgPD-0001eN-BJ for guix-patches@gnu.org; Sun, 16 Feb 2025 10:18:03 -0500 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1tjgPD-0005h2-1V; Sun, 16 Feb 2025 10:18:03 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debbugs.gnu.org; s=debbugs-gnu-org; h=MIME-Version:References:In-Reply-To:Date:From:To:Subject; bh=M/QmXPI41OZMM7DbPIzWUvc9TmhoHLs7mkdmLQGVxY4=; b=Pum4zNirU3QygXZYv7O7pLN8ohCr0y6PQpcYiQo1YYMIBwTsmgp2Yb228WNTqMg0FKDqa2Pyy5IhqnnzN8OAluAhgEbXcIr9mqD4dhPw3W6aOIzWl3huQ5Wb38XDlIVIhpfeyRx1cF9ZejaccA6rnmwNQv04r/voVD6h9biX9SdWhFXmj3aoncPiyG+tCjA26dH4NGRWJSR55ZpW5GZEantEm+NJb3TzbYUmVOnwizKtZWGpbcUgw/fV7bbYGy+RzV/L+Y4I1wvqHW4/3Mgwhv5bF4qlsAZDb1EQL39hffjmQPkgK0b7wau64a/rJ3b+sTol1R/FGJAJBNsH9TdF+w==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1tjgPC-0004GW-Qx; Sun, 16 Feb 2025 10:18:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#75528] [PATCH v2 2/2] services: Add power. Resent-From: Tomas Volf <~@wolfsden.cz> Original-Sender: "Debbugs-submit" Resent-CC: ludo@gnu.org, maxim.cournoyer@gmail.com, guix-patches@gnu.org Resent-Date: Sun, 16 Feb 2025 15:18:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 75528 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 75528@debbugs.gnu.org Cc: Tomas Volf <~@wolfsden.cz>, Ludovic =?utf-8?q?Court=C3=A8s?= , Maxim Cournoyer X-Debbugs-Original-Xcc: Ludovic =?utf-8?q?Court=C3=A8s?= , Maxim Cournoyer Received: via spool by 75528-submit@debbugs.gnu.org id=B75528.173971903916335 (code B ref 75528); Sun, 16 Feb 2025 15:18:02 +0000 Received: (at 75528) by debbugs.gnu.org; 16 Feb 2025 15:17:19 +0000 Received: from localhost ([127.0.0.1]:36391 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1tjgOS-0004FM-Jd for submit@debbugs.gnu.org; Sun, 16 Feb 2025 10:17:18 -0500 Received: from wolfsden.cz ([37.205.8.62]:57494) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from <~@wolfsden.cz>) id 1tjgOL-0004F0-4g for 75528@debbugs.gnu.org; Sun, 16 Feb 2025 10:17:12 -0500 Received: by wolfsden.cz (Postfix, from userid 104) id 2E5B237DFCE; Sun, 16 Feb 2025 15:17:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=wolfsden.cz; s=mail; t=1739719028; bh=NHOp0KPow807wkGbt3Qi68ZR/IkY93qU6er84OB0gig=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=xYN0GsAz6Jfv+c6s+PSmUGtU6KUII8A02twdlaGUEp1Ms/qHINdLgPO8Q+8LwDWCk KOVwToniF2jbCjM9BPvO8YgAK73F2H/QSQ2IXWyFhFSO1Sxz6XCvppZAMcFImCD4ww KEJhcu/1sULAArj1X2vDnmUpX29FpbOCnrHeyCJb9KLjA7qNrhmV/7qYTf35lUrWxx Riwe1nz6vgJpieVCJDeIZdd2fMHwvgJwDsD9/EdMUyWI3wOuRCtebYaHY5DTirP58B 9X5CYeZQSgRu2N/5g0KxNMZPaq4/RuXKnce4ni+8SBxX2Io03S3ZQAZi6kwTIDPxfb KTLnUNEj7L+a34aQThhI8vRwCc2Pzg80Gbu8bPjHx20XXLjjq83Vnl5XxVlGZDrJO5 PRV57nmCoPffIz1wCcyqery5+nMIeOlZqfSmN8bC9Pi3f+IQbA/SBnfdEjgSpe6xYC nezhXA1FhNBFEgO9Rhfun7U2xVdhjL6NRqBphSz4lWX7mVvkFb5XGljC4VZ2q2fKQS Y0gGd3FGR4iz5MiM+wlYY2xZIWVDzIw55BphRqn3cCMZyOQEOG/gt1wbXC5syJijWt gwgZ9qlFMiEqKD+RBqLK+rdWzgRDD3X3xOwziGxc+AByXQnifIx+2cNUw5nsivYHSE 1Fe4O4Vi8qNJKSU+zbmDeIag= Received: from localhost (unknown [128.0.188.242]) by wolfsden.cz (Postfix) with ESMTPSA id A85A937DC77; Sun, 16 Feb 2025 15:17:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=wolfsden.cz; s=mail; t=1739719023; bh=NHOp0KPow807wkGbt3Qi68ZR/IkY93qU6er84OB0gig=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=ThUUe5cvlph0ZXi+zL+dbDu2cM6+5DuEkXMLZIz3vwUuO+XjsQTqAIPjCjf05jPHW 3m/QhliTeA9yzJ9ywQIloq6HpEA7h/1YZvaCA6CdviVJm9jIhq0lQud7i30mGeiyXc w7obpypeyrXP46sNJELFWnIejekOui6gs1T/daxgyIC8uJwdaAuj3VesE1SQYEcCOM OvD73cK1xOeU3zdqt2WI3zH/a+H/FfAG20LDMhCLWjRiLb14CwLC6MfyzXr1LPh6Dg kGBKbnSioTxcRFt3b+dk8MJGLlGJM3pgS1InFh4jAgwux/nR2QUfQhBwIV6pIA0vTl P/BZMPcrkEKPyb/+nnwyiJrmWgEYBX8vMsc3JsUv8kGtT2SZryo31KTMwORotbuVBr zIj+5rI85Q02vBPG1so+/qsxlJ4l1SI+WFGpYbkzVd/hZiJARF4wQPP7l+Bjk1thvt ySEjG1KBdhgxkzP90JBJ+CW/AnUVa7N3u99NCOec99SpywwWbJVN5tNL1tqoHS42A9 V1aYjAuSQv4AKoD0CPSZxlFqjh0xbg2lr/QTGhgZpmB78WXc/l1erujutimD2djdO9 SdaBAyDMJed02C3gm3aXao4XaPjp9bHgULmDcYsjeyOUGhobh0l7CNG2EWzOG96Qiy he5txMfRrQrNJRzVYln0lhLA= From: Tomas Volf <~@wolfsden.cz> Date: Sun, 16 Feb 2025 16:16:46 +0100 Message-ID: <0b295c3bf879d00157b0476744e218ad0d921656.1739719006.git.~@wolfsden.cz> X-Mailer: git-send-email 2.48.1 In-Reply-To: <9b91fcc95120426959b89c466beadf61505fee59.1739719006.git.~@wolfsden.cz> References: <9b91fcc95120426959b89c466beadf61505fee59.1739719006.git.~@wolfsden.cz> MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches * gnu/services/power.scm: New file. * gnu/local.mk (GNU_SYSTEM_MODULES): Add it. * doc/guix.texi (Power Management Services): Document service and data types. Change-Id: If205d19bea1d20a99309626e28521a2d6fe6702f --- doc/guix.texi | 382 +++++++++++++++++++++- gnu/local.mk | 1 + gnu/services/power.scm | 711 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1091 insertions(+), 3 deletions(-) create mode 100644 gnu/services/power.scm diff --git a/doc/guix.texi b/doc/guix.texi index f8b3022ccf..eee47d0b86 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, 2025 Herman Rimm@* Copyright @copyright{} 2024 Matthew Trzcinski@* Copyright @copyright{} 2024 Richard Sent@* @@ -421,7 +421,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. @@ -19269,7 +19269,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. @@ -36516,6 +36516,382 @@ 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 +@acronym{APC, APC by Schneider Electric or formerly American Power +Conversion Corporation} @acronym{UPS, Uninterruptible Power Supply} +devices. Apcupsd also works with some @acronym{OEM, Original Equipment +Manufacturer}-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 (for example, the default +@code{battery-level} of 5% may be considered too low by some). + +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{apcupsd} (default: @code{apcupsd}) (type: package) +The @code{apcupsd} package to use. + +@item @code{shepherd-service-name} (default: @code{apcupsd}) (type: symbol) +The name of the shepherd service. You can add the service multiple +times with different names 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) +The file name of the pid file. + +@item @code{debug-level} (default: @code{0}) (type: integer) +The logging verbosity. Bigger number means more logs. The source code +uses up to @code{300} as debug level value, so a value of @code{999} +seems reasonable to enable all the logs. + +@item @code{run-dir} (default: @code{"/var/run/apcupsd"}) (type: string) +The 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 a cable connecting the UPS to your computer. Possible +generic choices are @code{'simple}, @code{'smart}, @code{'ether} and +@code{'usb}. + +Alternatively, 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) +The type of the UPS you have. + +@table @code +@item apcsmart +Newer serial character device, appropriate for SmartUPS models using a +serial cable (not an USB). + +@item usb +Most new UPSes are an 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 the SNMP above but requires use of the net-snmp library. Unless +you have a specific need for this old driver, you should use the +@code{'snmp} instead. + +@item dumb +An old serial character device for use with simple-signaling UPSes. + +@item pcnet +A PowerChute Network Shutdown protocol which can be used as an +alternative to an SNMP with the AP9617 family of smart slot cards. + +@item modbus +A 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 auto-detection, 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}}. +The @var{hostname} is the ip address or hostname of the UPS on the +network. The @var{vendor} can be can be "APC" or "APC_NOTRAP". +"APC_NOTRAP" will disable SNMP trap catching; you usually want "APC". +The @var{port} is usually 161. The @var{community} is usually +"private". + +@item netsnmp +Same as the @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}}. +The @var{ipaddr} is the IP address of the UPS management card. The +@var{username} and the @var{passphrase} are the credentials for which +the card has been configured. The @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) +The 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 the apcupsd's responsiveness to certain events at the cost of +higher CPU utilization. + +@item @code{on-batery-delay} (default: @code{6}) (type: integer) +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, the apcupsd will initiate a +system shutdown. + +@quotation Note +@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work +in a 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 a 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. The value +of 0 disables this timer. + +@quotation Note +@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work +in a conjunction, so the first that occurs will cause the initation of a +shutdown. +@end quotation + +@item @code{annoy-interval} (default: @code{300}) (type: integer) +The 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) +The initial delay in seconds after a 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, the 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) +An IP address on which the NIS server will listen for incoming +connections. + +@item @code{net-server-port} (default: @code{3551}) (type: integer) +An IP port on which the 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 name. + +@item @code{net-server-events-file-max-size} (default: @code{10}) (type: integer) +The 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) +The 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) +The 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 a description of the events please refer to @samp{man 8 apccontrol}. + +Each handler shall be a gexp. It is spliced into the control script for +the daemon. In addition to the standard Guile programming environment, +the following procedures and variables are also available: + +@table @code +@item conf +Variable containing the file name of the configuration file. + +@item powerfail-file +Variable containing the file name of 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{#t} if @command{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{#f}. + +@item powered? +Is @code{#t} if the computer on which @command{apcupsd} is running is +powered by the UPS and @code{#f} if not. At the moment, this value is +unimplemented and always @code{#f}. + +@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{modules} (type: gexp) +Additional modules to import into the generated handler script. + +@item @code{killpower} (type: gexp) +The handler for the killpower event. + +@item @code{commfailure} (type: gexp) +The handler for the commfailure event. + +@item @code{commok} (type: gexp) +The handler for the commfailure event. + +@item @code{powerout} (type: gexp) +The handler for the powerout event. + +@item @code{onbattery} (type: gexp) +The handler for the onbattery event. + +@item @code{offbattery} (type: gexp) +The handler for the offbattery event. + +@item @code{mainsback} (type: gexp) +The handler for the mainsback event. + +@item @code{failing} (type: gexp) +The handler for the failing event. + +@item @code{timeout} (type: gexp) +The handler for the timeout event. + +@item @code{loadlimit} (type: gexp) +The handler for the loadlimit event. + +@item @code{runlimit} (type: gexp) +The handler for the runlimit event. + +@item @code{doreboot} (type: gexp) +The handler for the doreboot event. + +@item @code{doshutdown} (type: gexp) +The handler for the doshutdown event. + +@item @code{annoyme} (type: gexp) +The handler for the annoyme event. + +@item @code{emergency} (type: gexp) +The handler for the emergency event. + +@item @code{changeme} (type: gexp) +The handler for the changeme event. + +@item @code{remotedown} (type: gexp) +The handler for the remotedown event. + +@item @code{startselftest} (type: gexp) +The handler for the startselftest event. + +@item @code{endselftest} (type: gexp) +The handler for the endselftest event. + +@item @code{battdetach} (type: gexp) +The handler for the battdetach event. + +@item @code{battattach} (type: gexp) +The handler for the battattach event. + +@end table +@end deftp + @node Audio Services @subsection Audio Services diff --git a/gnu/local.mk b/gnu/local.mk index 3e9740779e..61f652876e 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -761,6 +761,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..72b2a40fef --- /dev/null +++ b/gnu/services/power.scm @@ -0,0 +1,711 @@ +;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz> + +;;;; Commentary: + +;;; Power-related services. + +;;;; Code: + +(define-module (gnu services power) + #:use-module (gnu) + #:use-module (gnu packages admin) + #:use-module (gnu packages linux) + #:use-module (gnu packages power) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (gnu services) + #:use-module (guix packages) + #:use-module (guix records) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:export (apcupsd-service-type + + apcupsd-configuration + apcupsd-configuration-apcupsd + apcupsd-configuration-shepherd-service-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 + apcupsd-event-handlers-modules + 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 + (modules + (gexp #~()) + "Additional modules to import into the generated handler script.") + (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))) + "The handler for the 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))) + "The handler for the 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))) + "The handler for the commfailure event.") + (powerout + (gexp + #~(#t)) + "The handler for the 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))) + "The handler for the 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))) + "The handler for the offbattery event.") + (mainsback + (gexp + #~((when (file-exists? powerfail-file) + (wall "Continuing with shutdown.")))) + "The handler for the mainsback event.") + (failing + (gexp + #~((wall "Battery power exhausted on UPS ~a. Doing shutdown." name))) + "The handler for the failing event.") + (timeout + (gexp + #~((wall "Battery time limit exceeded on UPS ~a. Doing shutdown." name))) + "The handler for the timeout event.") + (loadlimit + (gexp + #~((wall "Remaining battery charge below limit on UPS ~a. Doing shutdown." name))) + "The handler for the loadlimit event.") + (runlimit + (gexp + #~((wall "Remaining battery runtime below limit on UPS ~a. Doing shutdown." name))) + "The handler for the runlimit event.") + (doreboot + (gexp + #~((wall "UPS ~a initiating Reboot Sequence" name) + (system* #$(file-append shepherd "/sbin/reboot")))) + "The handler for the doreboot event.") + (doshutdown + (gexp + #~((wall "UPS ~a initiated Shutdown Sequence" name) + (system* #$(file-append shepherd "/sbin/halt")))) + "The handler for the doshutdown event.") + (annoyme + (gexp + #~((wall "Power problems with UPS ~a. Please logoff." name))) + "The handler for the annoyme event.") + (emergency + (gexp + #~((wall "Emergency Shutdown. Possible battery failure on UPS ~a." name))) + "The handler for the emergency event.") + (changeme + (gexp + #~((let ((msg (format #f "~a UPS ~a battery needs changing NOW." + (gethostname) name))) + (mail-to-root msg msg)) + (wall "Emergency! Batteries have failed on UPS ~a. Change them NOW." name))) + "The handler for the changeme event.") + (remotedown + (gexp + #~((wall "Remote Shutdown. Beginning Shutdown Sequence."))) + "The handler for the remotedown event.") + (startselftest + (gexp + #~(#t)) + "The handler for the startselftest event.") + (endselftest + (gexp + #~(#t)) + "The handler for the endselftest event.") + (battdetach + (gexp + #~(#t)) + "The handler for the battdetach event.") + (battattach + (gexp + #~(#t)) + "The handler for the battattach event.")) + +(define-syntax define-enum + (lambda (x) + (syntax-case x () + ((_ name values) + (let* ((datum/name (syntax->datum #'name)) + (datum/predicate (string->symbol + (format #f "enum-~a?" datum/name))) + (datum/serialize (string->symbol + (format #f "serialize-enum-~a" datum/name)))) + (with-syntax + ((predicate (datum->syntax x datum/predicate)) + (serialize (datum->syntax x datum/serialize))) + #'(begin + (define (predicate value) + (memq value values)) + (define serialize serialize-symbol)))))))) + +(define mangle-field-name + (match-lambda + ('name "UPSNAME") + ('cable "UPSCABLE") + ('type "UPSTYPE") + ('device "DEVICE") + ('poll-time "POLLTIME") + ('lock-dir "LOCKFILE") + ('power-fail-dir "PWRFAILDIR") + ('no-login-dir "NOLOGINDIR") + ('on-batery-delay "ONBATTERYDELAY") + ('battery-level "BATTERYLEVEL") + ('remaining-minutes "MINUTES") + ('timeout "TIMEOUT") + ('annoy-interval "ANNOY") + ('annoy-delay "ANNOYDELAY") + ('no-logon "NOLOGON") + ('kill-delay "KILLDELAY") + ('net-server "NETSERVER") + ('net-server-ip "NISIP") + ('net-server-port "NISPORT") + ('net-server-events-file "EVENTSFILE") + ('net-server-events-file-max-size "EVENTSFILEMAX") + ('class "UPSCLASS") + ('mode "UPSMODE") + ('stat-time "STATTIME") + ('stat-file "STATFILE") + ('log-stats "LOGSTATS") + ('data-time "DATATIME") + ('facility "FACILITY"))) + +(define (serialize-string field-name value) + #~(format #f "~a ~a\n" #$(mangle-field-name field-name) '#$value)) +(define serialize-symbol serialize-string) +(define serialize-integer serialize-string) +(define (serialize-boolean field-name value) + #~(format #f "~a ~a\n" + #$(mangle-field-name field-name) + #$(if value "on" "off"))) + +(define-maybe string) + +(define-enum cable '( simple smart ether usb + 940-0119A 940-0127A 940-0128A 940-0020B 940-0020C + 940-0023A 940-0024B 940-0024C 940-1524C 940-0024G + 940-0095A 940-0095B 940-0095C 940-0625A MAM-04-02-2000)) +(define-enum type '(apcsmart usb net snmp netsnmp dumb pcnet modbus test)) +(define-enum no-logon '(disable timeout percent minutes always)) +(define-enum class '(standalone shareslave sharemaster)) +(define-enum mode '(disable share)) + +(define-configuration apcupsd-configuration + (apcupsd (package apcupsd) "The @code{apcupsd} package to use.") + + (shepherd-service-name + (symbol 'apcupsd) + "The name of the shepherd service. You can add the service multiple times +with different names to manage multiple UPSes." + empty-serializer) + (auto-start? + (boolean #t) + "Should the shepherd service auto-start?" + empty-serializer) + (pid-file + (string "/run/apcupsd.pid") + "The file name of the PID file." + empty-serializer) + (debug-level + (integer 0) + "The logging verbosity. Bigger number means more logs. The source code +uses up to @code{300} as debug level value, so a value of @code{999} seems +reasonable to enable all the logs." + empty-serializer) + + (run-dir + (string "/run/apcupsd") + "The directory containing runtime information. You need to change this if +you desire to run multiple instances of the daemon." + empty-serializer) + + ;; General configuration parameters + (name + 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.") + (cable + (enum-cable 'usb) + "The type of a cable connecting the UPS to your computer. Possible generic +choices are @code{'simple}, @code{'smart}, @code{'ether} and +@code{'usb}. + +Alternatively, 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}.") + (type + (enum-type 'usb) + "The type of the UPS you have. + +@table @code +@item apcsmart +Newer serial character device, appropriate for SmartUPS models using a serial +cable (not an USB). + +@item usb +Most new UPSes are an 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 the SNMP above but requires use of the net-snmp library. Unless you +have a specific need for this old driver, you should use the @code{'snmp} +instead. + +@item dumb +An old serial character device for use with simple-signaling UPSes. + +@item pcnet +A PowerChute Network Shutdown protocol which can be used as an alternative to +an SNMP with the AP9617 family of smart slot cards. + +@item modbus +A serial device for use with newest SmartUPS models supporting the MODBUS +protocol. + +@end table") + (device + (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 auto-detection, 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}}. The +@var{hostname} is the ip address or hostname of the UPS on the network. The +@var{vendor} can be can be \"APC\" or \"APC_NOTRAP\". \"APC_NOTRAP\" will +disable SNMP trap catching; you usually want \"APC\". The @var{port} is +usually 161. The @var{community} is usually \"private\". + +@item netsnmp +Same as the @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}}. The +@var{ipaddr} is the IP address of the UPS management card. The @var{username} +and the @var{passphrase} are the credentials for which the card has been +configured. The @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") + (poll-time + (integer 60) + "The 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 the apcupsd's +responsiveness to certain events at the cost of higher CPU utilization.") + + ;; Configuration parameters used during power failures + (on-batery-delay + (integer 6) + "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.") + (battery-level + (integer 5) + "If during a power failure, the remaining battery percentage (as reported +by the UPS) is below or equal to this value, the apcupsd will initiate a +system shutdown. + +@quotation Note +@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work +in a conjunction, so the first that occurs will cause the initation of a +shutdown. +@end quotation") + (remaining-minutes + (integer 3) + "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 a conjunction, so the first that occurs will cause the initation of a +shutdown. +@end quotation") + (timeout + (integer 0) + "If during a power failure, the UPS has run on batteries for this many +seconds or longer, apcupsd will initiate a system shutdown. The value of 0 +disables this timer. + +@quotation Note +@code{battery-level}, @code{remaining-minutes}, and @code{timeout} work +in a conjunction, so the first that occurs will cause the initation of a +shutdown. +@end quotation") + (annoy-interval + (integer 300) + "The time in seconds between annoying users (via the @code{'annoyme} event) +to sign off prior to system shutdown. 0 disables.") + (annoy-delay + (integer 60) + "The initial delay in seconds after a power failure before warning users to +get off the system.") + (no-logon + (enum-no-logon 'disable) + "The condition which determines when users are prevented from logging in +during a power failure.") + (kill-delay + (integer 0) + "If this is non-zero, the 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.") + + ;; Configuration statements for Network Information Server + (net-server + (boolean #f) + "If enabled, a network information server process will be started.") + (net-server-ip + (string "127.0.0.1") + "An IP address on which the NIS server will listen for incoming +connections.") + (net-server-port + (integer 3551) + "An IP port on which the NIS server will listen for incoming connections.") + (net-server-events-file + 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 name.") + (net-server-events-file-max-size + (integer 10) + "The maximum size of the events file in kilobytes.") + ;; Configuration statements used if sharing a UPS with more than one machine + (class (enum-class 'standalone) + "Normally standalone unless you share an UPS using an APC ShareUPS card.") + (mode (enum-mode 'disable) + "Normally disable unless you share an UPS using an APC ShareUPS card.") + ;; Configuration statements to control apcupsd system logging + (stat-time + (integer 0) + "The time interval in seconds between writing the status file, 0 +disables.") + (log-stats + (boolean #f) + "Also write the stats as a logs. This generates a lot of output.") + (data-time + (integer 0) + "The time interval in seconds between writing the data records to the log +file, 0 disables.") + (facility + maybe-string + "The logging facility for the syslog.") + + ;; Event handlers + (event-handlers + (apcupsd-event-handlers (apcupsd-event-handlers)) + "Handlers for events produced by apcupsd." + empty-serializer)) + +(define (%apccontrol config) + (program-file + "apccontrol" + #~(begin + (use-modules (ice-9 format) + (ice-9 match) + (ice-9 popen) + (srfi srfi-9) + #$@(apcupsd-event-handlers-modules + (apcupsd-configuration-event-handlers config))) + ;; Script dir depends on these, and the configuration depends on the + ;; script dir. To sever the cyclic dependency, pass the file names via + ;; environment variables. + (define conf (getenv "GUIX_APCUPSD_CONF")) + (define powerfail-file (getenv "GUIX_APCUPSD_POWERFAIL_FILE")) + + (define (err . args) + (apply format (current-error-port) args)) + (define (wall . args) + (system* #$(file-append util-linux "/bin/wall") (apply format #f args))) + (define (apcupsd . args) + (apply system* #$(file-append apcupsd "/sbin/apcupsd") "-f" conf args)) + (define (mail-to-root subject body) + (let ((port (open-pipe* OPEN_WRITE + "/run/privileged/bin/sendmail" + "-F" "apcupsd" + "root"))) + (format port "Subject: ~a~%~%~a~&" subject body) + (close-pipe port))) + (match (cdr (command-line)) + (((? string? cmd) name connected powered) + (let ((connected? (match connected + ("1" #t) + ("0" #f))) + (powered? (match powered + ("1" #t) + ("0" #f)))) + (match cmd + ;; I am sure this could be done by macro, but meh. Last release + ;; of apcupsd was in 2016, so maintaining this will not be much + ;; work. + ("killpower" + #$@(apcupsd-event-handlers-killpower + (apcupsd-configuration-event-handlers config))) + ("commfailure" + #$@(apcupsd-event-handlers-commfailure + (apcupsd-configuration-event-handlers config))) + ("commok" + #$@(apcupsd-event-handlers-commok + (apcupsd-configuration-event-handlers config))) + ("powerout" + #$@(apcupsd-event-handlers-powerout + (apcupsd-configuration-event-handlers config))) + ("onbattery" + #$@(apcupsd-event-handlers-onbattery + (apcupsd-configuration-event-handlers config))) + ("offbattery" + #$@(apcupsd-event-handlers-offbattery + (apcupsd-configuration-event-handlers config))) + ("mainsback" + #$@(apcupsd-event-handlers-mainsback + (apcupsd-configuration-event-handlers config))) + ("failing" + #$@(apcupsd-event-handlers-failing + (apcupsd-configuration-event-handlers config))) + ("timeout" + #$@(apcupsd-event-handlers-timeout + (apcupsd-configuration-event-handlers config))) + ("loadlimit" + #$@(apcupsd-event-handlers-loadlimit + (apcupsd-configuration-event-handlers config))) + ("runlimit" + #$@(apcupsd-event-handlers-runlimit + (apcupsd-configuration-event-handlers config))) + ("doreboot" + #$@(apcupsd-event-handlers-doreboot + (apcupsd-configuration-event-handlers config))) + ("doshutdown" + #$@(apcupsd-event-handlers-doshutdown + (apcupsd-configuration-event-handlers config))) + ("annoyme" + #$@(apcupsd-event-handlers-annoyme + (apcupsd-configuration-event-handlers config))) + ("emergency" + #$@(apcupsd-event-handlers-emergency + (apcupsd-configuration-event-handlers config))) + ("changeme" + #$@(apcupsd-event-handlers-changeme + (apcupsd-configuration-event-handlers config))) + ("remotedown" + #$@(apcupsd-event-handlers-remotedown + (apcupsd-configuration-event-handlers config))) + ("startselftest" + #$@(apcupsd-event-handlers-startselftest + (apcupsd-configuration-event-handlers config))) + ("endselftest" + #$@(apcupsd-event-handlers-endselftest + (apcupsd-configuration-event-handlers config))) + ("battdetach" + #$@(apcupsd-event-handlers-battdetach + (apcupsd-configuration-event-handlers config))) + ("battattach" + #$@(apcupsd-event-handlers-battattach + (apcupsd-configuration-event-handlers config))) + (_ + (err "Unknown event: ~a~%" cmd) + (err "Iff the event was emitted by apcupsd, this is a bug.~%") + (err "Please report to bug-guix@gnu.org.~%") + (exit #f))))) + (args + (err "Unknown arguments: ~a~%" args) + (err "Iff the arguments were passed by apcupsd, this is a bug.~%") + (err "Please report to bug-guix@gnu.org.~%") + (exit #f)))))) + +(define (apcupsd-script-dir config) + (computed-file + "apcupsd-script-dir" + #~(begin + (mkdir #$output) + (chdir #$output) + (symlink #$(%apccontrol config) "apccontrol")))) + +(define (apcupsd-config-file config) + (let ((run-dir (apcupsd-configuration-run-dir config))) + (mixed-text-file + "apcupsd.conf" + "\ +## apcupsd.conf v1.1 ## +# +# for apcupsd - GNU Guix +# +# \"apcupsd\" POSIX config file (generated by apcupsd-service-type) +" + (serialize-configuration config apcupsd-configuration-fields) + ;; This one is confusing. The manual page states: + ;; + ;; > It must be changed when running more than one copy of apcupsd on the + ;; > same computer to control multiple UPSes. + ;; + ;; However would you not want the lock to be per-device, not per-process? + ;; I decided to follow the documentation, but I do not understand why it + ;; should be like this. I do not have multiple UPSes to try. + (serialize-string 'lock-dir (string-append run-dir "/lock")) + (serialize-string 'power-fail-dir run-dir) + (serialize-string 'no-login-dir run-dir) + (serialize-string 'stat-file (string-append run-dir "/apcupsd.status")) + "SCRIPTDIR " (apcupsd-script-dir config) "\n"))) + +(define (apcupsd-activation config) + (match-record config (run-dir) + #~(begin + (use-modules (guix build utils)) + (mkdir-p #$(string-append run-dir "/lock"))))) + +(define (apcupsd-shepherd-services config) + (match-record config + ( apcupsd pid-file debug-level run-dir + shepherd-service-name auto-start?) + (let ((config-file (apcupsd-config-file config))) + (list + (shepherd-service + (documentation "Run the apcupsd daemon.") + (requirement '(user-processes)) + (provision (list shepherd-service-name)) + (auto-start? auto-start?) + (start #~(make-forkexec-constructor + '(#$(file-append apcupsd "/sbin/apcupsd") + "-b" ;do not daemonize + "-f" #$config-file + "-P" #$pid-file + "-d" #$(number->string debug-level)) + #:log-file + #$(format #f "/var/log/~a.log" shepherd-service-name) + #:environment-variables + (cons* (string-append "GUIX_APCUPSD_CONF=" + #$config-file) + #$(string-append "GUIX_APCUPSD_POWERFAIL_FILE=" + run-dir "/powerfail") + (default-environment-variables)))) + (stop #~(make-kill-destructor)) + (actions (list (shepherd-configuration-action config-file)))))))) + +(define (apcupsd-pam-extensions config) + ;; The apcupsd can be configured to prevent users from logging in on certain + ;; conditions. This is implemented by creation of a "nologin" file, and + ;; using a pam nologin module to prevent the login (if the file exists). + (define pam-nologin + (pam-entry + (control "required") + (module "pam_nologin.so") + (arguments (list (string-append "file=" + (apcupsd-configuration-run-dir config) + "/nologin"))))) + + (list (pam-extension + (transformer + (lambda (pam) + (pam-service + (inherit pam) + (auth (cons pam-nologin (pam-service-auth pam))))))))) + +(define apcupsd-service-type + (service-type + (name 'apcupsd) + (description "Configure and optionally start the apcupsd.") + (extensions (list (service-extension activation-service-type + apcupsd-activation) + (service-extension shepherd-root-service-type + apcupsd-shepherd-services) + (service-extension pam-root-service-type + apcupsd-pam-extensions))) + (compose identity) + (extend (lambda (cfg lst) + (fold (cut <> <>) cfg lst))) + (default-value (apcupsd-configuration))))