From a13b30f95bef2475d8fa78576932fc59e1a79f42 Mon Sep 17 00:00:00 2001 From: Andy Doan Date: Wed, 16 Jul 2025 15:07:57 -0500 Subject: [PATCH 1/4] contrib: Add on-change handler for run-and-report This provides a mechanism by which we can trigger remote actions using fioconfig and the new `run-and-report` command. In order to prevent the execution of random commands, we include a "actions" directory where a user can be explicit about which remote actions they want to allow for their product. This commit includes a "reboot" command that's been requested in the past. Signed-off-by: Andy Doan --- contrib/actions/reboot | 5 ++++ contrib/fioconfig-oneshot | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100755 contrib/actions/reboot create mode 100755 contrib/fioconfig-oneshot diff --git a/contrib/actions/reboot b/contrib/actions/reboot new file mode 100755 index 0000000..2492f73 --- /dev/null +++ b/contrib/actions/reboot @@ -0,0 +1,5 @@ +#!/bin/sh -ex + +# Schedule a reboot in one minute to give fioconfig the chance to create a +# TestResult on the backend so the operator will know the device is rebooting. +systemctl reboot --when="+5 seconds" diff --git a/contrib/fioconfig-oneshot b/contrib/fioconfig-oneshot new file mode 100755 index 0000000..eb7f622 --- /dev/null +++ b/contrib/fioconfig-oneshot @@ -0,0 +1,63 @@ +#!/bin/sh -e + +# This is an OnChanged handler that can handle requests to run a command +# one time, capture the output, and report it back to the device-gateway. + +[ -z "${CONFIG_FILE}" ] && (echo "No CONFIG_FILE specified"; exit 1) +[ -f "${CONFIG_FILE}" ] || (echo "${CONFIG_FILE} does not exist. Assuming config file was deleted"; exit 0) + +ACTIONS_DIR=${ACTIONS_DIR-"/usr/share/fioconfig/actions"} +[ -d "${ACTIONS_DIR}" ] || (echo "${ACTIONS_DIR} does not exist"; exit 1) + +cmd_id=$(cat ${CONFIG_FILE} | grep COMMAND_ID | cut -d= -f2) +[ -z "${cmd_id}" ] && (echo "No COMMAND_ID found in config file"; exit 1) +echo "Command ID is ${cmd_id}" + +capture=$(cat ${CONFIG_FILE} | grep COMMAND_CAPTURE | cut -d= -f2) + +action=$1 +active_file="${STORAGE_DIR}/oneshot.${action}.state" +completed_file="${active_file}.completed" + +echo "Action is ${action}" +cmd="${ACTIONS_DIR}/${action}" +if [ -x "${cmd}" ] ; then + if [ "${capture}" = "1" ]; then + artifacts_dir=$(mktemp -d fioconfig-oneshot.XXXXXX) + trap 'rm -rf ${artifacts_dir}' EXIT + + echo "Capturing stdout/stderr" + cmd="${FIOCONFIG_BIN} run-and-report --artifacts-dir ${artifacts_dir} --id ${cmd_id} --name ${action} ${cmd}" + fi +else + echo "Action not found in ${ACTIONS_DIR} or is not executable" + echo -n "${cmd_id}" > ${completed_file} + if [ "${capture}" = "1" ]; then + # report something back to the operator + ${FIOCONFIG_BIN} run-and-report --id ${cmd_id} --name ${action} /bin/sh -c "echo Action(${cmd}) not found; exit 1" + fi + exit 1 +fi + +# We'll execute under two conditions: +# 1) We've never been run (oneshot.state.completed does not exist) +# 3) We have a new command ($cmd_id != oneshot.state.completed's COMMANDID) + +runcmd() { + ${cmd} ; echo -n "${cmd_id}" > ${completed_file} + rm -rf ${artifacts_dir} + exit +} + +if [ ! -f "${completed_file}" ] ; then + echo "Completion file, ${completed_file}, does not exist" + runcmd +fi + +completed_id=$(cat ${completed_file}) +if [ "${completed_id}" != "${cmd_id}" ] ; then + echo "Running command: COMMAND_ID has changed from ${completed_id} -> ${cmd_id}" + runcmd +fi + +echo "Command not needed. Current COMMAND_ID is ${cmd_id}" From a9aa8332bf5688e1b05cd26d8fb7580fd53a3ef6 Mon Sep 17 00:00:00 2001 From: Andy Doan Date: Wed, 16 Jul 2025 15:16:05 -0500 Subject: [PATCH 2/4] contrib: Add a action to run fio-diag.sh Signed-off-by: Andy Doan --- contrib/actions/diag | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 contrib/actions/diag diff --git a/contrib/actions/diag b/contrib/actions/diag new file mode 100755 index 0000000..67ef906 --- /dev/null +++ b/contrib/actions/diag @@ -0,0 +1,4 @@ +#!/bin/sh -ex + +/usr/sbin/fio-diag.sh +dmesg > $ARTIFACTS/dmesg.log From e6273423d8e681b4623939bdc7b58caff64e6e06 Mon Sep 17 00:00:00 2001 From: Andy Doan Date: Wed, 20 Aug 2025 13:38:25 -0500 Subject: [PATCH 3/4] Add init logic to configure remote actions This change helps the user facing UX part of this feature. By sharing what actions are availble to the device, fioctl and webui can know what options to present to the user for triggering a remote action. It can also *reject* an invalid trigger rather than waiting for it to fail. --- internal/remote_actions.go | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 internal/remote_actions.go diff --git a/internal/remote_actions.go b/internal/remote_actions.go new file mode 100644 index 0000000..06f6f91 --- /dev/null +++ b/internal/remote_actions.go @@ -0,0 +1,65 @@ +//go:build !disable_remoteactions + +package internal + +import ( + "bytes" + "errors" + "log/slog" + "os" + "path/filepath" + "strings" +) + +type raInitCallback struct { + newActions []byte +} + +func (r *raInitCallback) ConfigFiles(app *App) []ConfigFileReq { + const actionsDir = "/usr/share/fioconfig/actions" + var actions []string + entries, err := os.ReadDir(actionsDir) + if err != nil && !errors.Is(err, os.ErrNotExist) { + // This is a bug - we can't read the directory for some reason + slog.Error("Unable to initialize remote actions", "error", err) + return nil + } else if err == nil { + for _, entry := range entries { + actions = append(actions, entry.Name()) + } + } + actionsStr := strings.Join(actions, ",") + + content, err := os.ReadFile(filepath.Join(app.SecretsDir, "fio-remote-actions")) + if err != nil { + if os.IsNotExist(err) { + content = nil + } else { + slog.Warn("Unable to read configured remote actions", "error", err) + return nil + } + } + + if !bytes.Equal(content, []byte(actionsStr)) { + slog.Info("Configured remote actions changed", "old", string(content), "new", actionsStr) + file := ConfigFileReq{ + Name: "fio-remote-actions", + Unencrypted: true, + Value: actionsStr, + } + r.newActions = []byte(actionsStr) + return []ConfigFileReq{file} + } + + // prevent init logic from calling OnComplete by removing ourselves + delete(initCallbacks, "remote-actions") + return nil +} + +func (r raInitCallback) OnComplete(app *App) { + delete(initCallbacks, "remote-actions") +} + +func init() { + initCallbacks["remote-actions"] = &raInitCallback{} +} From 05418c5e5be6c3dec60ba8d284eb890684a828e4 Mon Sep 17 00:00:00 2001 From: Andy Doan Date: Fri, 12 Dec 2025 17:31:18 -0600 Subject: [PATCH 4/4] Makefile: Fix rule for nopkcs11 Signed-off-by: Andy Doan --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index e561cc6..cceb63c 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ bin/fioconfig-%: FORCE GOARCH=$(shell echo $* | cut -f2 -d\-) \ go build -tags vpn $(LDFLAGS) -o $@ main.go +.PHONY: bin/fioconfig-nopkcs11 bin/fioconfig-nopkcs11: CGO_ENABLED=0 go build -tags vpn,disable_pkcs11 $(LDFLAGS) -o $@ main.go