From 14c9f0f70df29f70a26a50bde5413d97d7b18746 Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Sun, 21 Sep 2025 01:34:22 +0200 Subject: [PATCH] chore: add "support" for non-linux platforms (#6) --- errors.go | 2 - helpers.go | 2 - structs.go | 2 - systemctl.go | 158 ++++------------------------ systemctl_darwin.go | 65 ++++++++++++ systemctl_linux.go | 248 ++++++++++++++++++++++++++++++++++++++++++++ util.go | 2 - 7 files changed, 332 insertions(+), 147 deletions(-) create mode 100644 systemctl_darwin.go create mode 100644 systemctl_linux.go diff --git a/errors.go b/errors.go index 4aca7a3..c97327b 100644 --- a/errors.go +++ b/errors.go @@ -1,5 +1,3 @@ -//go:build linux - package systemctl import ( diff --git a/helpers.go b/helpers.go index 46d60a3..5cf416f 100644 --- a/helpers.go +++ b/helpers.go @@ -1,5 +1,3 @@ -//go:build linux - package systemctl import ( diff --git a/structs.go b/structs.go index 83c5af1..e758437 100644 --- a/structs.go +++ b/structs.go @@ -1,5 +1,3 @@ -//go:build linux - package systemctl type Options struct { diff --git a/systemctl.go b/systemctl.go index 2b4b2e9..8f36c65 100644 --- a/systemctl.go +++ b/systemctl.go @@ -1,11 +1,7 @@ -//go:build linux - package systemctl import ( "context" - "regexp" - "strings" "github.com/taigrr/systemctl/properties" ) @@ -17,12 +13,7 @@ import ( // reloaded, all sockets systemd listens on behalf of user configuration will // stay accessible. func DaemonReload(ctx context.Context, opts Options) error { - args := []string{"daemon-reload", "--system"} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return daemonReload(ctx, opts) } // Reenables one or more units. @@ -31,12 +22,7 @@ func DaemonReload(ctx context.Context, opts Options) error { // the unit configuration directory, then recreates the symlink to the unit again, // atomically. Can be used to change the symlink target. func Reenable(ctx context.Context, unit string, opts Options) error { - args := []string{"reenable", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return reenable(ctx, unit, opts) } // Disables one or more units. @@ -45,12 +31,7 @@ func Reenable(ctx context.Context, unit string, opts Options) error { // the unit configuration directory, and hence undoes any changes made by // enable or link. func Disable(ctx context.Context, unit string, opts Options) error { - args := []string{"disable", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return disable(ctx, unit, opts) } // Enable one or more units or unit instances. @@ -60,12 +41,7 @@ func Disable(ctx context.Context, unit string, opts Options) error { // manager configuration is reloaded (in a way equivalent to daemon-reload), // in order to ensure the changes are taken into account immediately. func Enable(ctx context.Context, unit string, opts Options) error { - args := []string{"enable", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return enable(ctx, unit, opts) } // Check whether any of the specified units are active (i.e. running). @@ -73,24 +49,8 @@ func Enable(ctx context.Context, unit string, opts Options) error { // Returns true if the unit is active, false if inactive or failed. // Also returns false in an error case. func IsActive(ctx context.Context, unit string, opts Options) (bool, error) { - args := []string{"is-active", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - stdout, _, _, err := execute(ctx, args) - stdout = strings.TrimSuffix(stdout, "\n") - switch stdout { - case "inactive": - return false, nil - case "active": - return true, nil - case "failed": - return false, nil - case "activating": - return false, nil - default: - return false, err - } + result, err := isActive(ctx, unit, opts) + return result, err } // Checks whether any of the specified unit files are enabled (as with enable). @@ -103,59 +63,14 @@ func IsActive(ctx context.Context, unit string, opts Options) (bool, error) { // See https://www.freedesktop.org/software/systemd/man/systemctl.html#is-enabled%20UNIT%E2%80%A6 // for more information func IsEnabled(ctx context.Context, unit string, opts Options) (bool, error) { - args := []string{"is-enabled", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - stdout, _, _, err := execute(ctx, args) - stdout = strings.TrimSuffix(stdout, "\n") - switch stdout { - case "enabled": - return true, nil - case "enabled-runtime": - return true, nil - case "linked": - return false, ErrLinked - case "linked-runtime": - return false, ErrLinked - case "alias": - return true, nil - case "masked": - return false, ErrMasked - case "masked-runtime": - return false, ErrMasked - case "static": - return true, nil - case "indirect": - return true, nil - case "disabled": - return false, nil - case "generated": - return true, nil - case "transient": - return true, nil - } - if err != nil { - return false, err - } - return false, ErrUnspecified + result, err := isEnabled(ctx, unit, opts) + return result, err } // Check whether any of the specified units are in a "failed" state. func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) { - args := []string{"is-failed", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - stdout, _, _, err := execute(ctx, args) - if matched, _ := regexp.MatchString(`inactive`, stdout); matched { - return false, nil - } else if matched, _ := regexp.MatchString(`active`, stdout); matched { - return false, nil - } else if matched, _ := regexp.MatchString(`failed`, stdout); matched { - return true, nil - } - return false, err + result, err := isFailed(ctx, unit, opts) + return result, err } // Mask one or more units, as specified on the command line. This will link @@ -165,46 +80,25 @@ func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) { // continue masking anyway. Calling Mask on a non-existing masked unit does not // return an error. Similarly, see Unmask. func Mask(ctx context.Context, unit string, opts Options) error { - args := []string{"mask", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return mask(ctx, unit, opts) } // Stop and then start one or more units specified on the command line. // If the units are not running yet, they will be started. func Restart(ctx context.Context, unit string, opts Options) error { - args := []string{"restart", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return restart(ctx, unit, opts) } // Show a selected property of a unit. Accepted properties are predefined in the // properties subpackage to guarantee properties are valid and assist code-completion. func Show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) { - args := []string{"show", "--system", unit, "--property", string(property)} - if opts.UserMode { - args[1] = "--user" - } - stdout, _, _, err := execute(ctx, args) - stdout = strings.TrimPrefix(stdout, string(property)+"=") - stdout = strings.TrimSuffix(stdout, "\n") - return stdout, err + str, err := show(ctx, unit, property, opts) + return str, err } // Start (activate) a given unit func Start(ctx context.Context, unit string, opts Options) error { - args := []string{"start", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return start(ctx, unit, opts) } // Get back the status string which would be returned by running @@ -213,22 +107,13 @@ func Start(ctx context.Context, unit string, opts Options) error { // Generally, it makes more sense to programatically retrieve the properties // using Show, but this command is provided for the sake of completeness func Status(ctx context.Context, unit string, opts Options) (string, error) { - args := []string{"status", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - stdout, _, _, err := execute(ctx, args) - return stdout, err + stat, err := status(ctx, unit, opts) + return stat, err } // Stop (deactivate) a given unit func Stop(ctx context.Context, unit string, opts Options) error { - args := []string{"stop", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return stop(ctx, unit, opts) } // Unmask one or more unit files, as specified on the command line. @@ -239,10 +124,5 @@ func Stop(ctx context.Context, unit string, opts Options) error { // If the unit doesn't exist but it's masked anyway, no error will be // returned. Gross, I know. Take it up with Poettering. func Unmask(ctx context.Context, unit string, opts Options) error { - args := []string{"unmask", "--system", unit} - if opts.UserMode { - args[1] = "--user" - } - _, _, _, err := execute(ctx, args) - return err + return unmask(ctx, unit, opts) } diff --git a/systemctl_darwin.go b/systemctl_darwin.go new file mode 100644 index 0000000..cee1974 --- /dev/null +++ b/systemctl_darwin.go @@ -0,0 +1,65 @@ +//go:build !linux + +package systemctl + +import ( + "context" + + "github.com/taigrr/systemctl/properties" +) + +func daemonReload(ctx context.Context, opts Options) error { + return nil +} + +func reenable(ctx context.Context, unit string, opts Options) error { + return nil +} + +func disable(ctx context.Context, unit string, opts Options) error { + return nil +} + +func enable(ctx context.Context, unit string, opts Options) error { + return nil +} + +func isActive(ctx context.Context, unit string, opts Options) (bool, error) { + return false, nil +} + +func isEnabled(ctx context.Context, unit string, opts Options) (bool, error) { + return false, nil +} + +func isFailed(ctx context.Context, unit string, opts Options) (bool, error) { + return false, nil +} + +func mask(ctx context.Context, unit string, opts Options) error { + return nil +} + +func restart(ctx context.Context, unit string, opts Options) error { + return nil +} + +func show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) { + return "", nil +} + +func start(ctx context.Context, unit string, opts Options) error { + return nil +} + +func status(ctx context.Context, unit string, opts Options) (string, error) { + return "", nil +} + +func stop(ctx context.Context, unit string, opts Options) error { + return nil +} + +func unmask(ctx context.Context, unit string, opts Options) error { + return nil +} diff --git a/systemctl_linux.go b/systemctl_linux.go new file mode 100644 index 0000000..a38a73c --- /dev/null +++ b/systemctl_linux.go @@ -0,0 +1,248 @@ +//go:build linux + +package systemctl + +import ( + "context" + "regexp" + "strings" + + "github.com/taigrr/systemctl/properties" +) + +// Reload systemd manager configuration. +// +// This will rerun all generators (see systemd. generator(7)), reload all unit +// files, and recreate the entire dependency tree. While the daemon is being +// reloaded, all sockets systemd listens on behalf of user configuration will +// stay accessible. +func daemonReload(ctx context.Context, opts Options) error { + args := []string{"daemon-reload", "--system"} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Reenables one or more units. +// +// This removes all symlinks to the unit files backing the specified units from +// the unit configuration directory, then recreates the symlink to the unit again, +// atomically. Can be used to change the symlink target. +func reenable(ctx context.Context, unit string, opts Options) error { + args := []string{"reenable", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Disables one or more units. +// +// This removes all symlinks to the unit files backing the specified units from +// the unit configuration directory, and hence undoes any changes made by +// enable or link. +func disable(ctx context.Context, unit string, opts Options) error { + args := []string{"disable", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Enable one or more units or unit instances. +// +// This will create a set of symlinks, as encoded in the [Install] sections of +// the indicated unit files. After the symlinks have been created, the system +// manager configuration is reloaded (in a way equivalent to daemon-reload), +// in order to ensure the changes are taken into account immediately. +func enable(ctx context.Context, unit string, opts Options) error { + args := []string{"enable", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Check whether any of the specified units are active (i.e. running). +// +// Returns true if the unit is active, false if inactive or failed. +// Also returns false in an error case. +func isActive(ctx context.Context, unit string, opts Options) (bool, error) { + args := []string{"is-active", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + stdout = strings.TrimSuffix(stdout, "\n") + switch stdout { + case "inactive": + return false, nil + case "active": + return true, nil + case "failed": + return false, nil + case "activating": + return false, nil + default: + return false, err + } +} + +// Checks whether any of the specified unit files are enabled (as with enable). +// +// Returns true if the unit is enabled, aliased, static, indirect, generated +// or transient. +// +// Returns false if disabled. Also returns an error if linked, masked, or bad. +// +// See https://www.freedesktop.org/software/systemd/man/systemctl.html#is-enabled%20UNIT%E2%80%A6 +// for more information +func isEnabled(ctx context.Context, unit string, opts Options) (bool, error) { + args := []string{"is-enabled", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + stdout = strings.TrimSuffix(stdout, "\n") + switch stdout { + case "enabled": + return true, nil + case "enabled-runtime": + return true, nil + case "linked": + return false, ErrLinked + case "linked-runtime": + return false, ErrLinked + case "alias": + return true, nil + case "masked": + return false, ErrMasked + case "masked-runtime": + return false, ErrMasked + case "static": + return true, nil + case "indirect": + return true, nil + case "disabled": + return false, nil + case "generated": + return true, nil + case "transient": + return true, nil + } + if err != nil { + return false, err + } + return false, ErrUnspecified +} + +// Check whether any of the specified units are in a "failed" state. +func isFailed(ctx context.Context, unit string, opts Options) (bool, error) { + args := []string{"is-failed", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + if matched, _ := regexp.MatchString(`inactive`, stdout); matched { + return false, nil + } else if matched, _ := regexp.MatchString(`active`, stdout); matched { + return false, nil + } else if matched, _ := regexp.MatchString(`failed`, stdout); matched { + return true, nil + } + return false, err +} + +// Mask one or more units, as specified on the command line. This will link +// these unit files to /dev/null, making it impossible to start them. +// +// Notably, Mask may return ErrDoesNotExist if a unit doesn't exist, but it will +// continue masking anyway. Calling Mask on a non-existing masked unit does not +// return an error. Similarly, see Unmask. +func mask(ctx context.Context, unit string, opts Options) error { + args := []string{"mask", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Stop and then start one or more units specified on the command line. +// If the units are not running yet, they will be started. +func restart(ctx context.Context, unit string, opts Options) error { + args := []string{"restart", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Show a selected property of a unit. Accepted properties are predefined in the +// properties subpackage to guarantee properties are valid and assist code-completion. +func show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) { + args := []string{"show", "--system", unit, "--property", string(property)} + if opts.UserMode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + stdout = strings.TrimPrefix(stdout, string(property)+"=") + stdout = strings.TrimSuffix(stdout, "\n") + return stdout, err +} + +// Start (activate) a given unit +func start(ctx context.Context, unit string, opts Options) error { + args := []string{"start", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Get back the status string which would be returned by running +// `systemctl status [unit]`. +// +// Generally, it makes more sense to programatically retrieve the properties +// using Show, but this command is provided for the sake of completeness +func status(ctx context.Context, unit string, opts Options) (string, error) { + args := []string{"status", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + return stdout, err +} + +// Stop (deactivate) a given unit +func stop(ctx context.Context, unit string, opts Options) error { + args := []string{"stop", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Unmask one or more unit files, as specified on the command line. +// This will undo the effect of Mask. +// +// In line with systemd, Unmask will return ErrDoesNotExist if the unit +// doesn't exist, but only if it's not already masked. +// If the unit doesn't exist but it's masked anyway, no error will be +// returned. Gross, I know. Take it up with Poettering. +func unmask(ctx context.Context, unit string, opts Options) error { + args := []string{"unmask", "--system", unit} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} diff --git a/util.go b/util.go index a17e249..2cc88cd 100644 --- a/util.go +++ b/util.go @@ -1,5 +1,3 @@ -//go:build linux - package systemctl import (