mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-15 10:33:17 +02:00
Background services without systemd (runit, OpenRC, ...)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e7e233951f
commit
f63e708572
8 files changed
+325
-110
No files matched your search
+57
-18
@@ -19,7 +19,11 @@ from pathlib import Path, PurePosixPath
|
||||
from typing import Callable, Optional
|
||||
|
||||
from ConfigMerge import ApplySet, MergeConfig, MergeResult
|
||||
from Core import BackupsDir, ChildEnvironment, DownloadsDir, IsWindows, LoadConfig, PristineDir, StartMenuDir, StartupDir
|
||||
import Services
|
||||
from Core import (
|
||||
AutostartDir, BackupsDir, ChildEnvironment, DownloadsDir, HasSystemd, IsWindows, LoadConfig, PristineDir,
|
||||
StartMenuDir, StartupDir,
|
||||
)
|
||||
from Manifests import Leftover, Program, Render
|
||||
from Receipts import IniRecord, LoadReceipt, PlacedFile, Receipt
|
||||
from Sources import ClearDownloads, Download, LatestCommit, SourceError
|
||||
@@ -55,6 +59,8 @@ def _Run(Command: list[str], Log: LogFn, Check: bool = True, Cwd: Optional[Path]
|
||||
|
||||
|
||||
def _Systemctl(Log: LogFn, *Args: str, Check: bool = False) -> int:
|
||||
if not HasSystemd:
|
||||
return 0 # services run through Services.py instead
|
||||
return _Run(["systemctl", "--user", *Args], Log, Check=Check)
|
||||
|
||||
|
||||
@@ -499,11 +505,30 @@ def _ApplyShortcuts(Program: Program, Context: dict, OldReceipt: Optional[Receip
|
||||
)
|
||||
|
||||
|
||||
def _ApplyWindowsServices(Program: Program, Context: dict, OldReceipt: Optional[Receipt], NewReceipt: Receipt, Log: LogFn) -> None:
|
||||
"""A Startup-folder shortcut runs the service at login (through
|
||||
ServiceRun.py, which records its PID); enabling also starts it now."""
|
||||
import WindowsSupport
|
||||
def _PlaceAutostart(Program: Program, Service, Command: tuple, OldReceipt: Optional[Receipt],
|
||||
NewReceipt: Receipt, Log: LogFn) -> None:
|
||||
"""Linux without systemd: the XDG autostart entry that starts a service at
|
||||
login. Recorded like a shortcut, so it goes when the service is turned off
|
||||
or the program removed (and one that was already there comes back)."""
|
||||
Entry = AutostartDir / f"{Service.Name}.desktop"
|
||||
Previous = OldReceipt.FindFile(str(Entry)) if OldReceipt is not None else None
|
||||
Placed = PlacedFile(str(Entry), "Shortcut", Displaced=Previous.Displaced if Previous else "")
|
||||
if Previous is None and Entry.exists():
|
||||
Displaced = BackupsDir / Program.Name / "Displaced" / _BackupName(str(Entry))
|
||||
Displaced.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(Entry, Displaced)
|
||||
Placed.Displaced = str(Displaced)
|
||||
Entry.parent.mkdir(parents=True, exist_ok=True)
|
||||
Entry.write_text(Services.AutostartEntry(Service.Name, *Command))
|
||||
NewReceipt.Files.append(Placed)
|
||||
Log(f"Wrote {Entry}")
|
||||
|
||||
|
||||
def _ApplyStandInServices(Program: Program, Context: dict, OldReceipt: Optional[Receipt], NewReceipt: Receipt, Log: LogFn) -> None:
|
||||
"""Services SaraSuite runs itself (Windows; Linux without systemd): at
|
||||
login from a Startup-folder shortcut / an autostart entry, through
|
||||
ServiceRun.py; enabling also starts it now. An update restarts it, so
|
||||
it runs the new code."""
|
||||
WasEnabled = set(OldReceipt.EnabledServices) if OldReceipt else set()
|
||||
Runner = Program.ProgramDir / "ServiceRun.py"
|
||||
for Service in Program.Services:
|
||||
@@ -511,13 +536,19 @@ def _ApplyWindowsServices(Program: Program, Context: dict, OldReceipt: Optional[
|
||||
WorkingDir = Path(Render(Service.WorkingDir, Context)) if Service.WorkingDir else Program.AppDir
|
||||
if Program.OptionEnabled(Service.Option, NewReceipt.Options):
|
||||
NewReceipt.EnabledServices.append(Service.Id)
|
||||
_PlaceShortcut(
|
||||
Program, StartupDir / f"{Service.Name}.lnk", f'"{Runner}" {Service.Name} "{Script}"',
|
||||
WorkingDir, None, f"{Service.Name} (background service)", OldReceipt, NewReceipt, Log,
|
||||
)
|
||||
WindowsSupport.StartService(Service.Name, Program.PythonW, Runner, Script, WorkingDir, Log)
|
||||
if IsWindows:
|
||||
_PlaceShortcut(
|
||||
Program, StartupDir / f"{Service.Name}.lnk", f'"{Runner}" {Service.Name} "{Script}" "{WorkingDir}"',
|
||||
WorkingDir, None, f"{Service.Name} (background service)", OldReceipt, NewReceipt, Log,
|
||||
)
|
||||
else:
|
||||
_PlaceAutostart(Program, Service, (Program.Python, Runner, Script, WorkingDir), OldReceipt, NewReceipt, Log)
|
||||
if Service.Id in WasEnabled and not IsWindows: # Windows stopped it before the update
|
||||
Services.Restart(Service.Name, Program.PythonW, Runner, Script, WorkingDir, Log)
|
||||
else:
|
||||
Services.Start(Service.Name, Program.PythonW, Runner, Script, WorkingDir, Log)
|
||||
elif Service.Id in WasEnabled:
|
||||
WindowsSupport.StopService(Service.Name, Log)
|
||||
Services.Stop(Service.Name, Log)
|
||||
|
||||
|
||||
def _StopWindowsServices(Program: Program, Receipt: Optional[Receipt], Log: LogFn) -> None:
|
||||
@@ -525,17 +556,15 @@ def _StopWindowsServices(Program: Program, Receipt: Optional[Receipt], Log: LogF
|
||||
its files or venv are replaced or removed."""
|
||||
if not IsWindows or Receipt is None:
|
||||
return
|
||||
import WindowsSupport
|
||||
|
||||
for Name in Receipt.EnabledServices:
|
||||
WindowsSupport.StopService(Name, Log)
|
||||
Services.Stop(Name, Log)
|
||||
|
||||
|
||||
def _ApplyServices(Program: Program, OldReceipt: Optional[Receipt], NewReceipt: Receipt, Log: LogFn, Context: Optional[dict] = None) -> None:
|
||||
if not Program.Services and not (OldReceipt and OldReceipt.EnabledServices):
|
||||
return
|
||||
if IsWindows:
|
||||
_ApplyWindowsServices(Program, Context or Program.Context(NewReceipt.Answers), OldReceipt, NewReceipt, Log)
|
||||
if IsWindows or not HasSystemd:
|
||||
_ApplyStandInServices(Program, Context or Program.Context(NewReceipt.Answers), OldReceipt, NewReceipt, Log)
|
||||
return
|
||||
_Systemctl(Log, "daemon-reload")
|
||||
WasEnabled = set(OldReceipt.EnabledServices) if OldReceipt else set()
|
||||
@@ -557,7 +586,12 @@ def _AfterIntegrationChanges(Program: Program, Log: LogFn) -> None:
|
||||
if any(Template.Target.endswith(".desktop") for Template in Program.Templates) and Applications.exists():
|
||||
_Run(["update-desktop-database", str(Applications)], Log, Check=False)
|
||||
for Unit in Program.RestartUnits:
|
||||
_Systemctl(Log, "try-restart", Unit)
|
||||
if HasSystemd:
|
||||
_Systemctl(Log, "try-restart", Unit)
|
||||
else:
|
||||
# No systemd to restart it: stop it, and D-Bus starts it again
|
||||
# when it's next needed (xdg-desktop-portal is D-Bus activated).
|
||||
_Run(["pkill", "-x", "-u", str(os.getuid()), Unit.removesuffix(".service")], Log, Check=False)
|
||||
|
||||
|
||||
def Apply(
|
||||
@@ -686,7 +720,9 @@ def PlanRemoval(Program: Program) -> RemovalPlan:
|
||||
Plan.Items.append(RemovalItem(str(Downloaded), "Program", "downloaded source"))
|
||||
|
||||
if Current is not None:
|
||||
ServiceNote = "background service, stopped and taken out of Startup" if IsWindows else "systemd user service, stopped and disabled"
|
||||
ServiceNote = ("background service, stopped and taken out of Startup" if IsWindows
|
||||
else "systemd user service, stopped and disabled" if HasSystemd
|
||||
else "background service, stopped and taken out of autostart")
|
||||
for Unit in Current.EnabledServices:
|
||||
Plan.Items.append(RemovalItem(Unit, "Integration", ServiceNote))
|
||||
for Placed in Current.Files:
|
||||
@@ -733,6 +769,9 @@ def Remove(Program: Program, RemoveConfigs: bool, RemoveRuntime: bool, Log: LogF
|
||||
if Current is not None:
|
||||
if IsWindows:
|
||||
_StopWindowsServices(Program, Current, Log)
|
||||
elif not HasSystemd:
|
||||
for Name in Current.EnabledServices:
|
||||
Services.Stop(Name, Log)
|
||||
else:
|
||||
for Unit in Current.EnabledServices:
|
||||
_Systemctl(Log, "disable", "--now", Unit)
|
||||
|
||||
Reference in new issue
Block a user