provide cli function stop, restart and reload for hbd
Thought for 1s
This commit is contained in:
@@ -75,6 +75,20 @@ def build_parser():
|
||||
help="Notification title (default: '[LEVEL] test')",
|
||||
)
|
||||
|
||||
# --- stop ---
|
||||
stop_p = subparsers.add_parser("stop", help="Stop the running hbd instance")
|
||||
stop_p.add_argument("-c", "--config", dest="configfile", help="Config file path (YAML)")
|
||||
|
||||
# --- reload ---
|
||||
reload_p = subparsers.add_parser("reload", help="Reload configuration (SIGHUP)")
|
||||
reload_p.add_argument("-c", "--config", dest="configfile", help="Config file path (YAML)")
|
||||
|
||||
# --- restart ---
|
||||
restart_p = subparsers.add_parser("restart", help="Restart the running hbd instance")
|
||||
restart_p.add_argument("-c", "--config", dest="configfile", help="Config file path (YAML)")
|
||||
restart_p.add_argument("-f", "--foreground", action="store_true", help="Run in foreground after restart")
|
||||
restart_p.add_argument("-v", "--verbose", action="store_true", help="Verbose output after restart")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
@@ -154,6 +168,96 @@ def cmd_notify(args):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _read_pid(configfile) -> int | None:
|
||||
"""Return the PID from the pidfile, or None if not found / not running."""
|
||||
import os
|
||||
config = load_config(configfile)
|
||||
pidfile = config.get("pidfile", "")
|
||||
if not pidfile:
|
||||
print("Error: no pidfile configured.", file=sys.stderr)
|
||||
return None
|
||||
try:
|
||||
with open(pidfile) as f:
|
||||
pid = int(f.read().strip())
|
||||
# Verify process is actually running
|
||||
os.kill(pid, 0)
|
||||
return pid
|
||||
except FileNotFoundError:
|
||||
print(f"PID file not found ({pidfile}). Is hbd running?", file=sys.stderr)
|
||||
return None
|
||||
except ProcessLookupError:
|
||||
print(f"PID file exists but process {pid} is not running.", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error reading pidfile: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def cmd_stop(args):
|
||||
import os, signal as _signal, time
|
||||
pid = _read_pid(args.configfile)
|
||||
if pid is None:
|
||||
sys.exit(1)
|
||||
print(f"Stopping hbd (pid {pid})...")
|
||||
os.kill(pid, _signal.SIGTERM)
|
||||
# Wait up to 10 s for the process to exit
|
||||
for _ in range(20):
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except ProcessLookupError:
|
||||
print("hbd stopped.")
|
||||
return
|
||||
print("Warning: hbd did not stop within 10 seconds.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_reload(args):
|
||||
import os, signal as _signal
|
||||
pid = _read_pid(args.configfile)
|
||||
if pid is None:
|
||||
sys.exit(1)
|
||||
print(f"Sending SIGHUP to hbd (pid {pid})...")
|
||||
os.kill(pid, _signal.SIGHUP)
|
||||
print("Reload signal sent.")
|
||||
|
||||
|
||||
def cmd_restart(args):
|
||||
import os, signal as _signal, time, subprocess
|
||||
pid = _read_pid(args.configfile)
|
||||
if pid is not None:
|
||||
print(f"Stopping hbd (pid {pid})...")
|
||||
os.kill(pid, _signal.SIGTERM)
|
||||
for _ in range(20):
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except ProcessLookupError:
|
||||
print("hbd stopped.")
|
||||
break
|
||||
else:
|
||||
print("Warning: hbd did not stop within 10 seconds.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("hbd does not appear to be running — starting fresh.")
|
||||
|
||||
# Re-launch hbd with the same config
|
||||
cmd = [sys.executable, "-m", "hbd.server.cli", "serve"]
|
||||
if args.configfile:
|
||||
cmd += ["-c", args.configfile]
|
||||
if getattr(args, "foreground", False):
|
||||
cmd += ["-f"]
|
||||
if getattr(args, "verbose", False):
|
||||
cmd += ["-v"]
|
||||
|
||||
if getattr(args, "foreground", False):
|
||||
# Run in foreground — replace current process
|
||||
os.execv(sys.executable, cmd)
|
||||
else:
|
||||
subprocess.Popen(cmd, start_new_session=True)
|
||||
print("hbd restarted.")
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
@@ -166,6 +270,18 @@ def main(argv=None):
|
||||
cmd_notify(args)
|
||||
return
|
||||
|
||||
if args.command == "stop":
|
||||
cmd_stop(args)
|
||||
return
|
||||
|
||||
if args.command == "reload":
|
||||
cmd_reload(args)
|
||||
return
|
||||
|
||||
if args.command == "restart":
|
||||
cmd_restart(args)
|
||||
return
|
||||
|
||||
# Default: run the server (supports both `hbd serve ...` and `hbd ...`)
|
||||
config = load_config(args.configfile)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ SERVER_DEFAULTS = {
|
||||
|
||||
# Persistence
|
||||
"pickfile": os.path.join(os.path.expanduser("~"), ".hb.pick"), # File to store host state between restarts
|
||||
"pidfile": os.path.join(os.path.expanduser("~"), ".hb.pid"), # PID file for stop/restart/reload
|
||||
|
||||
# Logging
|
||||
"logfile": os.path.join(os.path.expanduser("~"), ".hb.log"),
|
||||
|
||||
@@ -475,6 +475,16 @@ def run(config, config_path=None):
|
||||
|
||||
notify_mod.initlog(logfile=config.get("logfile", "messages.log"))
|
||||
users_mod.load_users(config)
|
||||
|
||||
# Write pidfile
|
||||
pidfile = config.get("pidfile", "")
|
||||
if pidfile:
|
||||
try:
|
||||
with open(pidfile, "w") as f:
|
||||
f.write(str(os.getpid()))
|
||||
except Exception as e:
|
||||
logger.warning("Failed to write pidfile %s: %s", pidfile, e)
|
||||
|
||||
eventlog(None, "INFO", f"hbd version {__version__} starting up")
|
||||
|
||||
if config_path:
|
||||
@@ -497,6 +507,12 @@ def run(config, config_path=None):
|
||||
logger.info("hbd shutdown complete")
|
||||
eventlog(None, "INFO", f"hbd version {__version__} shutdown")
|
||||
notify_mod.closelog()
|
||||
# Remove pidfile
|
||||
if pidfile:
|
||||
try:
|
||||
os.unlink(pidfile)
|
||||
except Exception:
|
||||
pass
|
||||
# Explicitly close the loop
|
||||
try:
|
||||
# Cancel all remaining tasks
|
||||
|
||||
Reference in New Issue
Block a user