diff --git a/Makefile b/Makefile index df82071..7a46c0f 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ COMMON_ARGS += --build-arg=TAG=$(TAG) empty := space = $(empty) $(empty) -TARGETS = amd-ucode bnx2-bnx2x gvisor hello-world-service intel-ucode iscsi-tools nvidia-container-toolkit nvidia-fabricmanager nvidia-open-gpu-kernel-modules +TARGETS = amd-ucode bnx2-bnx2x gvisor hello-world-service intel-ucode iscsi-tools nut-client nvidia-container-toolkit nvidia-fabricmanager nvidia-open-gpu-kernel-modules NONFREE_TARGETS = all: $(TARGETS) ## Builds all known pkgs. diff --git a/README.md b/README.md index 52a0721..195b550 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,12 @@ All system extensions provided by Sidero Labs can be found in the [ghcr.io regis | ----------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------- | -------------- | | [iscsi-tools](storage/iscsi-tools/) | [ghcr.io/siderolabs/iscsi-tools](https://github.com/siderolabs/extensions/pkgs/container/iscsi-tools) | Open iSCSI tools | `v0.1.0` | +### Power + +| Name | Image | Description | Version Format | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------- | +| [nut-client](power/nut-client/) | [ghcr.io/siderolabs/nut-client](https://github.com/siderolabs/talos-extensions/pkgs/container/nut-client) | [Network UPS Tools](https://networkupstools.org) upsmon client | `upstream version`-`talos version` | + ### NVIDIA GPU | Name | Description | Version Format | diff --git a/hack/release.toml b/hack/release.toml index a6d3537..c0ab783 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -6,9 +6,9 @@ github_repo = "siderolabs/extensions" match_deps = "^github.com/((talos-systems|siderolabs)/[a-zA-Z0-9-]+)$" # previous release -previous = "v1.2.0" +previous = "v1.2.1" -pre_release = false +pre_release = true preface = """\ See [Talos Linux documentation](https://www.talos.dev/v1.2/talos-guides/configuration/system-extensions/) for information on using system extensions. @@ -16,10 +16,10 @@ See [Talos Linux documentation](https://www.talos.dev/v1.2/talos-guides/configur [notes] - [notes.updates] - title = "Updates" + [notes.power] + title = "Power management" description = """\ -Update to support Talos v1.2.1 +Support for Network UPS Tools client system extension to handle node graceful shutdown on power events from supported UPS's. """ diff --git a/power/nut-client/README.md b/power/nut-client/README.md new file mode 100644 index 0000000..4c94b43 --- /dev/null +++ b/power/nut-client/README.md @@ -0,0 +1,61 @@ +# nut-client extension + +## Usage + +Enable the extension in the machine configuration before installing Talos: + +```yaml +machine: + install: + extensions: + - image: ghcr.io/siderolabs/nut-client: +``` + +Configure the extension via .machine.files +You must replace upsmonHost and upsmonPasswd to match configuration on your nut server. +See [upsd.users](https://networkupstools.org/docs/man/upsd.users.html) man page for details. + + +On Talos SHUTDOWNCMD must be `/sbin/poweroff` + +```yaml +machine: + files: + - path: /var/etc/nut/upsmon.conf + permissions: 0o600 + op: create + content: |- + MONITOR ${upsmonHost} 1 remote ${upsmonPasswd} slave + SHUTDOWNCMD "/sbin/poweroff" + +``` + +## Testing + +Confirm extension service is running + +```bash +$ talosctl service ext-nut-client +NODE 192.168.1.1 +ID ext-nut-client +STATE Running +HEALTH ? +EVENTS [Running]: Started task ext-nut-client (PID 2263) for container ext-nut-client (59m59s ago) + [Preparing]: Creating service runner (59m59s ago) + [Preparing]: Running pre state (59m59s ago) + [Waiting]: Waiting for service "cri" to be "up" (59m59s ago) + [Waiting]: Waiting for service "cri" to be "up", network (1h0m0s ago) + [Waiting]: Waiting for service "cri" to be registered, network (1h0m1s ago) + [Waiting]: Waiting for service "containerd" to be "up", service "cri" to be registered, network (1h0m2s ago) + [Waiting]: Waiting for service "containerd" to be "up", service "cri" to be "up", network (1h0m3s ago) +``` + +**CAUTION** this will power off all connected systems. + +Trigger a 'Full System Shutdown' on the nut-server + +```bash +# upsmon -c fsd +``` + +all connected upsmon clients should perform a full shutdown and power off. diff --git a/power/nut-client/files/group b/power/nut-client/files/group new file mode 100644 index 0000000..5004e53 --- /dev/null +++ b/power/nut-client/files/group @@ -0,0 +1,2 @@ +dialout:x:20:nut +nut:x:131: diff --git a/power/nut-client/files/passwd b/power/nut-client/files/passwd new file mode 100644 index 0000000..11ded7f --- /dev/null +++ b/power/nut-client/files/passwd @@ -0,0 +1,2 @@ +root:x:0:0:root:/:/sbin/false +nut:x:123:131::/var/lib/nut:/sbin/false diff --git a/power/nut-client/manifest.yaml b/power/nut-client/manifest.yaml new file mode 100644 index 0000000..19ed816 --- /dev/null +++ b/power/nut-client/manifest.yaml @@ -0,0 +1,10 @@ +version: v1alpha1 +metadata: + name: nut-client + version: "$VERSION" + author: Sidero Labs + description: | + This system extension provides the network-ups-tools upsmon service. + compatibility: + talos: + version: ">= v1.2.0" diff --git a/power/nut-client/nut-client.yaml b/power/nut-client/nut-client.yaml new file mode 100644 index 0000000..a8830f5 --- /dev/null +++ b/power/nut-client/nut-client.yaml @@ -0,0 +1,47 @@ +name: nut-client +depends: + - service: cri + - network: + - addresses + - connectivity + - etcfiles +container: + entrypoint: ./upsmon + args: + - -F + mounts: + - source: /lib + destination: /lib + type: bind + options: + - bind + - ro + - source: /usr/lib + destination: /usr/lib + type: bind + options: + - bind + - ro + # config via .machine.files + - source: /var/etc/nut + destination: /usr/local/etc/nut + type: bind + options: + - bind + - ro + # /sbin/init talks to apid + - source: /system/run/apid/apid.sock + destination: /system/run/apid/apid.sock + type: bind + options: + - rshared + - rbind + - rw + # symlinked to /sbin/poweroff + - source: /sbin/init + destination: /sbin/init + type: bind + options: + - bind + - ro +restart: always diff --git a/power/nut-client/patches/replace_system.patch b/power/nut-client/patches/replace_system.patch new file mode 100644 index 0000000..e048969 --- /dev/null +++ b/power/nut-client/patches/replace_system.patch @@ -0,0 +1,43 @@ +--- a/clients/upsmon.c 2022-04-23 04:56:06.000000000 -0700 ++++ b/clients/upsmon.c 2022-09-04 13:21:28.072170502 -0700 +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include + + #include "nut_stdint.h" + #include "upsclient.h" +@@ -1874,6 +1875,23 @@ + static void runparent(int fd) + __attribute__((noreturn)); + ++static int runcmd(const char *cmd) ++{ ++ int sret, status; ++ pid_t pid; ++ char *argv[] = {cmd, NULL}; ++ char *env[] = {NULL}; ++ ++ /* TODO support tokenizing cmd */ ++ sret = posix_spawn(&pid, cmd, NULL, NULL, argv, env); ++ if (sret != 0) ++ /* parent */ ++ exit(EXIT_SUCCESS); ++ ++ sret = waitpid(pid, &status, 0); ++ return status; ++} ++ + static void runparent(int fd) + { + ssize_t ret; +@@ -1907,7 +1925,7 @@ + /* have to do this here - child is unprivileged */ + set_pdflag(); + +- sret = system(shutdowncmd); ++ sret = runcmd(shutdowncmd); + + if (sret != 0) + upslogx(LOG_ERR, "parent: Unable to call shutdown command: %s", diff --git a/power/nut-client/pkg.yaml b/power/nut-client/pkg.yaml new file mode 100644 index 0000000..7d55487 --- /dev/null +++ b/power/nut-client/pkg.yaml @@ -0,0 +1,101 @@ +name: nut-client +variant: scratch +shell: /toolchain/bin/bash +dependencies: + - stage: base + - image: "{{ .PKGS_PREFIX }}/openssl:{{ .PKGS_VERSION }}" + - image: "{{ .PKGS_PREFIX }}/util-linux:{{ .PKGS_VERSION }}" +steps: + - sources: + - url: https://github.com/networkupstools/nut/releases/download/v{{ .NUT_VERSION }}/nut-{{ .NUT_VERSION }}.tar.gz + destination: nut.tar.gz + sha256: c3e5a708da797b7c70b653d37b1206a000fcb503b85519fe4cdf6353f792bfe5 + sha512: 3c413ae54088045a713eb80cf1bdda474f41bb3b67c7c0248aa7a0c4d441dce1ff42627a2735273d7e36892d1f2eeb895220cf28af63fec2fa0c7a267f82d577 + prepare: + - | + sed -i 's#$VERSION#{{ .VERSION }}#' /pkg/manifest.yaml + - | + tar -xzf nut.tar.gz --strip-components=1 + - | + # no shell for you + patch -p1 < /pkg/patches/replace_system.patch + - | + export PATH="$PATH:/toolchain/bin" + + # Create symlinks for binaries required by libtoolize. + ln -s /toolchain/bin/sed /bin/sed + ln -s /toolchain/bin/grep /bin/grep + + # Create symlinks for files used when building. + mkdir -p /usr/bin + ln -s /toolchain/bin/pkg-config /usr/bin/pkg-config + ln -s /toolchain/bin/file /usr/bin/file + + cp /toolchain/share/automake-1.16/config.guess config.guess + cp /toolchain/lib/libstdc++* /lib + autoreconf -if + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + ./configure \ + --prefix=/usr/local \ + --libexecdir=/usr/local/lib/nut \ + --datadir=/usr/local/share/nut \ + --sysconfdir=/usr/local/etc/nut \ + --with-drvpath=/usr/local/lib/nut \ + --with-statepath=/var/run/nut \ + --with-altpidpath=/var/run/nut \ + --with-udev-dir=/usr/local/etc/udev \ + --with-systemdsystemunitdir=no \ + --with-systemdshutdowndir=no \ + --with-systemdtmpfilesdir=no \ + --with-user=nut \ + --with-group=nut \ + --with-openssl \ + --disable-static \ + --without-avahi \ + --without-cgi \ + --without-freeipmi \ + --without-ipmi \ + --without-libltdl \ + --without-modbus \ + --without-neon \ + --without-nss \ + --without-powerman \ + --without-serial \ + --without-snmp \ + --without-wrap \ + --without-usb + build: + - | + make -j $(nproc) all + install: + - | + make DESTDIR=/rootfs install-exec + + containerRoot=/rootfs/usr/local/lib/containers/nut-client + mkdir -p "$containerRoot"/{etc,sbin,usr/local/lib} + cp /rootfs/usr/local/sbin/upsmon "$containerRoot/upsmon" + mv /rootfs/usr/local/lib/lib* "$containerRoot/usr/local/lib/" + cp /pkg/files/* "$containerRoot/etc/" + + # TODO replace /usr/bin/wall with either a wrapper for printk or a + # Go cmd to log to Talos Events + + # /sbin/init is bind mounted in nut-client.yaml + pushd "$containerRoot/sbin" + ln -sv ./init poweroff + popd + + # cleanup + rm -rf /rootfs/usr/local/bin + rm -rf /rootfs/usr/local/etc + rm -rf /rootfs/usr/local/lib/nut + rm -rf /rootfs/usr/local/sbin + +finalize: + - from: /rootfs + to: /rootfs + - from: /pkg/manifest.yaml + to: / + - from: /pkg/nut-client.yaml + to: /rootfs/usr/local/etc/containers/ diff --git a/power/nut-client/vars.yaml b/power/nut-client/vars.yaml new file mode 100644 index 0000000..b07f68b --- /dev/null +++ b/power/nut-client/vars.yaml @@ -0,0 +1 @@ +VERSION: "{{ .NUT_VERSION }}-{{ .BUILD_ARG_TAG }}" diff --git a/power/vars.yaml b/power/vars.yaml new file mode 100644 index 0000000..a26bd1d --- /dev/null +++ b/power/vars.yaml @@ -0,0 +1,2 @@ +# renovate: datasource=github-releases extractVersion=^(?.*)$ depName=networkupstools/nut +NUT_VERSION: 2.8.0