mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-13 18:18:12 +02:00
112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
"""Runs a SaraSuite program as a background service where there's no systemd
|
|
--user to do it: on Windows (started windowless with pythonw.exe), and on
|
|
Linux with another init system (runit on Void, OpenRC, s6, dinit, ...).
|
|
Started at login (Windows: a shortcut in the Startup folder; Linux: an XDG
|
|
autostart entry) and when SaraSuite or the program's own GUI (e.g. SaraSort
|
|
for SaraSortd) starts it:
|
|
|
|
python ServiceRun.py <Name> <Script> [<WorkingDir>]
|
|
|
|
Like the systemd unit it stands in for: one copy at a time; restarted 10
|
|
seconds after it fails (an exception or a non-zero exit), not after a clean
|
|
exit or a stop; its output goes to <Name>.log. Its PID is in <Name>.pid, so
|
|
anything can tell whether it runs, and stop it, without admin rights. Both
|
|
files are in SaraSuite's Services folder: %LOCALAPPDATA%\\SaraSuite\\Services
|
|
on Windows, ~/.local/share/SaraSuite/Services on Linux.
|
|
|
|
Installed next to each program that has a service (Programs/<Name>/Manifest.toml).
|
|
"""
|
|
|
|
import os
|
|
import runpy
|
|
import signal
|
|
import sys
|
|
import time
|
|
import traceback
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
IsWindows = sys.platform == "win32"
|
|
RestartSeconds = 10 # the unit's RestartSec
|
|
MaxLogBytes = 1_000_000 # then the log starts over, the old one kept as <Name>.log.1
|
|
Stopping = False
|
|
|
|
|
|
def ServicesDir() -> Path:
|
|
if IsWindows:
|
|
Base = Path(os.environ.get("LOCALAPPDATA") or Path.home() / "AppData" / "Local")
|
|
else:
|
|
Base = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share")
|
|
return Base / "SaraSuite" / "Services"
|
|
|
|
|
|
def _AlreadyRunning(PidFile: Path) -> bool:
|
|
"""Linux: whether the PID file's process is another ServiceRun.py still
|
|
running (not a reused PID). Windows relies on whoever starts it to check."""
|
|
if IsWindows:
|
|
return False
|
|
try:
|
|
Pid = int(PidFile.read_text().strip())
|
|
if Pid == os.getpid():
|
|
return False
|
|
return b"ServiceRun.py" in Path(f"/proc/{Pid}/cmdline").read_bytes()
|
|
except (OSError, ValueError):
|
|
return False
|
|
|
|
|
|
def _Stop(Signal, Frame) -> None:
|
|
global Stopping
|
|
Stopping = True
|
|
raise SystemExit(0)
|
|
|
|
|
|
def Main() -> None:
|
|
Name, Script = sys.argv[1], Path(sys.argv[2])
|
|
if len(sys.argv) > 3:
|
|
Folder = Path(sys.argv[3])
|
|
Folder.mkdir(parents=True, exist_ok=True)
|
|
os.chdir(Folder)
|
|
Folder = ServicesDir()
|
|
Folder.mkdir(parents=True, exist_ok=True)
|
|
# Not an f-string: SaraSuite fills in braced names like that one when it places this file.
|
|
PidFile = Folder / (Name + ".pid")
|
|
if _AlreadyRunning(PidFile):
|
|
return
|
|
|
|
LogFile = Folder / (Name + ".log")
|
|
if LogFile.exists() and LogFile.stat().st_size > MaxLogBytes:
|
|
LogFile.replace(LogFile.with_suffix(".log.1"))
|
|
sys.stdout = sys.stderr = open(LogFile, "a", buffering=1, encoding="utf-8", errors="replace")
|
|
|
|
PidFile.write_text(str(os.getpid()))
|
|
if not IsWindows:
|
|
signal.signal(signal.SIGTERM, _Stop) # a stop: finish cleanly, don't restart
|
|
sys.path.insert(0, str(Script.parent))
|
|
try:
|
|
while True:
|
|
print(f"== {datetime.now():%Y-%m-%d %H:%M:%S} starting {Script.name}")
|
|
try:
|
|
sys.argv = [str(Script)]
|
|
runpy.run_path(str(Script), run_name="__main__")
|
|
break
|
|
except SystemExit as Exit:
|
|
if Stopping or Exit.code in (None, 0):
|
|
break
|
|
print(f"== {Script.name} exited with {Exit.code}")
|
|
except Exception:
|
|
traceback.print_exc()
|
|
print(f"== restarting in {RestartSeconds} seconds")
|
|
time.sleep(RestartSeconds)
|
|
finally:
|
|
print(f"== {datetime.now():%Y-%m-%d %H:%M:%S} stopped")
|
|
# Only remove it if it's still ours (a newer start may have replaced it).
|
|
try:
|
|
if PidFile.read_text().strip() == str(os.getpid()):
|
|
PidFile.unlink()
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
Main()
|