- services.go: use sudo systemctl so bee user can control system services - api.go: always return 200 with output field even on error, so the frontend shows the actual systemctl message instead of "exit status 1" - pages.go: button shows "..." while pending then restores label; output panel is full-width under the table with ✓/✗ status indicator; output auto-scrolls to bottom Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package platform
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
func (s *System) ListBeeServices() ([]string, error) {
|
|
seen := map[string]bool{}
|
|
var out []string
|
|
for _, pattern := range []string{
|
|
"/etc/systemd/system/bee-*.service",
|
|
"/lib/systemd/system/bee-*.service",
|
|
"/etc/systemd/system/bee-*.timer",
|
|
"/lib/systemd/system/bee-*.timer",
|
|
} {
|
|
matches, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, match := range matches {
|
|
base := filepath.Base(match)
|
|
name := base
|
|
if strings.HasSuffix(base, ".service") {
|
|
name = strings.TrimSuffix(base, ".service")
|
|
}
|
|
// Skip template units (e.g. bee-journal-mirror@) — they have no instances to query.
|
|
if strings.HasSuffix(name, "@") {
|
|
continue
|
|
}
|
|
// bee-selfheal is timer-managed; showing the oneshot service as inactive is misleading.
|
|
if name == "bee-selfheal" && strings.HasSuffix(base, ".service") {
|
|
continue
|
|
}
|
|
if !seen[name] {
|
|
seen[name] = true
|
|
out = append(out, name)
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|
|
|
|
func (s *System) ServiceState(name string) string {
|
|
raw, err := exec.Command("systemctl", "is-active", name).CombinedOutput()
|
|
if err == nil {
|
|
return strings.TrimSpace(string(raw))
|
|
}
|
|
raw, err = exec.Command("systemctl", "show", name, "--property=ActiveState", "--value").CombinedOutput()
|
|
if err != nil {
|
|
return "unknown"
|
|
}
|
|
state := strings.TrimSpace(string(raw))
|
|
if state == "" {
|
|
return "unknown"
|
|
}
|
|
return state
|
|
}
|
|
|
|
func (s *System) ServiceDo(name string, action ServiceAction) (string, error) {
|
|
// bee-web runs as the bee user; sudo is required to control system services.
|
|
// /etc/sudoers.d/bee grants bee NOPASSWD:ALL.
|
|
raw, err := exec.Command("sudo", "systemctl", string(action), name).CombinedOutput()
|
|
return string(raw), err
|
|
}
|
|
|
|
func (s *System) ServiceStatus(name string) (string, error) {
|
|
raw, err := exec.Command("systemctl", "status", name, "--no-pager").CombinedOutput()
|
|
return string(raw), err
|
|
}
|