Initial import: nasmon health monitor, split out of home_nas
Go service (nasmon/) plus nfpm packaging (packaging/), carried over from the home_nas monorepo. Adds rpm packaging alongside the existing deb build (Fedora/RHEL/Rocky dependency names via nfpm overrides, per-format systemd unit path, dpkg+rpm-compatible maintainer scripts) and a Gitea Actions workflow that builds both formats on every push. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// Package hostrun executes privileged commands on the host. nasmon runs in a
|
||||
// sandboxed mount namespace (ProtectSystem=strict), so anything that must
|
||||
// mount, write /etc or talk to other daemons goes through a transient systemd
|
||||
// unit (systemd-run) and thereby runs in the host's namespace.
|
||||
package hostrun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Runner executes a command with the given stdin and returns its combined
|
||||
// output; err is non-nil on a non-zero exit or when it could not be started.
|
||||
type Runner func(ctx context.Context, stdin string, name string, args ...string) (out string, err error)
|
||||
|
||||
// Direct runs the command in-process (development, tests, non-systemd hosts).
|
||||
func Direct(ctx context.Context, stdin string, name string, args ...string) (string, error) {
|
||||
return capture(exec.CommandContext(ctx, name, args...), stdin)
|
||||
}
|
||||
|
||||
// Host runs the command as a transient systemd unit in the host's mount
|
||||
// namespace. --pipe forwards stdin/stdout/stderr, --wait the exit status.
|
||||
func Host(ctx context.Context, stdin string, name string, args ...string) (string, error) {
|
||||
full := append([]string{"--wait", "--pipe", "--collect", "--quiet", "--", name}, args...)
|
||||
return capture(exec.CommandContext(ctx, "systemd-run", full...), stdin)
|
||||
}
|
||||
|
||||
func capture(cmd *exec.Cmd, stdin string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout, cmd.Stderr = &buf, &buf
|
||||
if stdin != "" {
|
||||
cmd.Stdin = strings.NewReader(stdin)
|
||||
}
|
||||
err := cmd.Run()
|
||||
out := strings.TrimSpace(buf.String())
|
||||
if err != nil {
|
||||
var ee *exec.ExitError
|
||||
if errors.As(err, &ee) && out != "" {
|
||||
return out, errors.New(out)
|
||||
}
|
||||
return out, fmt.Errorf("%s: %w", cmd.Args[0], err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Auto picks Host when systemd manages the system and systemd-run exists,
|
||||
// Direct otherwise.
|
||||
func Auto() Runner {
|
||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||
if _, err := exec.LookPath("systemd-run"); err == nil {
|
||||
return Host
|
||||
}
|
||||
}
|
||||
return Direct
|
||||
}
|
||||
|
||||
// WriteFile atomically replaces path with content on the host (mode 0644,
|
||||
// parent directory created). A previous version is kept as path.bak so the
|
||||
// caller can Restore it if validation fails.
|
||||
func WriteFile(ctx context.Context, run Runner, path, content string) error {
|
||||
script := `set -e; umask 022; d=$(dirname "$1"); mkdir -p "$d"; [ -f "$1" ] && cp -f "$1" "$1.bak"; cat > "$1.tmp"; mv -f "$1.tmp" "$1"`
|
||||
_, err := run(ctx, content, "sh", "-c", script, "sh", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore puts path.bak back in place of path (no-op when there is no backup).
|
||||
func Restore(ctx context.Context, run Runner, path string) error {
|
||||
_, err := run(ctx, "", "sh", "-c", `[ -f "$1.bak" ] && mv -f "$1.bak" "$1" || true`, "sh", path)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user