diff --git a/cli/commands/commands_linux.go b/cli/commands/commands_linux.go index cbbf4ea8..c7a843fa 100644 --- a/cli/commands/commands_linux.go +++ b/cli/commands/commands_linux.go @@ -8,7 +8,7 @@ func addCommands(d *mflags.Dispatcher) { // Server command is now defined in commands.go (renamed from dev) // Cloud registration commands - d.Dispatch("server register", Infer("server register", "Register this cluster with miren.cloud", Register, + d.Dispatch("server register", Infer("server register", "Register this cluster with miren.cloud", RegisterStandalone, WithExample(mflags.Example{ Name: "Register with cloud", Body: "miren server register --name my-cluster", diff --git a/cli/commands/server_register.go b/cli/commands/server_register.go index 1b6e930b..339bf32a 100644 --- a/cli/commands/server_register.go +++ b/cli/commands/server_register.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "os/exec" "strings" "time" @@ -17,6 +18,18 @@ type RegisterOptions struct { OutputDir string `short:"o" long:"output" description:"Output directory for registration" default:"/var/lib/miren/server"` } +// RegisterStandalone is the CLI entrypoint for `miren server register`. It runs +// Register and then bounces the local miren.service if one is active, so the +// user doesn't have to restart manually. The install paths call Register +// directly because they own the service lifecycle themselves. +func RegisterStandalone(ctx *Context, opts RegisterOptions) error { + if err := Register(ctx, opts); err != nil { + return err + } + restartMirenServiceIfActive(ctx) + return nil +} + // Register handles cluster registration with miren.cloud func Register(ctx *Context, opts RegisterOptions) error { clean := map[string]string{} @@ -201,11 +214,28 @@ func Register(ctx *Context, opts RegisterOptions) error { } ctx.Info("Configuration saved to: %s", opts.OutputDir) - ctx.Warn("If you're already running the miren server, you must now restart it to apply the new registration.") - return nil } +// restartMirenServiceIfActive bounces the systemd miren.service so a newly +// saved registration takes effect, but only when systemd is managing a +// currently-running server. Other deployment styles (docker, manual, dev) are +// left alone — install paths run their own lifecycle steps afterward, and +// nothing the user needs to act on remains. +func restartMirenServiceIfActive(ctx *Context) { + if _, err := exec.LookPath("systemctl"); err != nil { + return + } + if err := exec.Command("systemctl", "is-active", "--quiet", "miren.service").Run(); err != nil { + return + } + if err := exec.Command("systemctl", "restart", "miren.service").Run(); err != nil { + ctx.Warn("Failed to restart miren.service: %v. Restart manually to apply the new registration.", err) + return + } + ctx.Info("Restarted miren.service to apply the new registration.") +} + // RegisterStatus displays the current registration status func RegisterStatus(ctx *Context, opts struct { Dir string `short:"d" long:"dir" description:"Registration directory" default:"/var/lib/miren/server"`