fix: reliable Mudfish removal on plugin uninstall

Decky 3.3 often SIGKILLs plugins 5s after SIGTERM because its socket
listener spins on EOF and starves the event loop (seen for several
plugins). So:
- _unload/_uninstall run synchronously (an awaited worker thread could
  never resume), and the daemon stop no longer waits.
- A transient systemd path unit watches the plugins folder; if this
  plugin's folder stays gone for 20s it stops the daemon and deletes
  /opt/mudfish and the plugin settings, then removes itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ysj7705andClaude Opus 5.5 committed 2026-10-24 22:14:51 +09:00
1 parent 3c28c9dac2
commit bc45f66da5
5 files changed
+75 -7

No files matched your search

+30 -2
View File
@@ -42,8 +42,9 @@ def version_of(bin_path: str) -> str:
return os.path.basename(os.path.dirname(os.path.dirname(bin_path)))
def stop_daemon(run=subprocess.run) -> None:
run(["systemctl", "stop", DAEMON_UNIT], capture_output=True, timeout=20)
def stop_daemon(run=subprocess.run, wait: bool = True) -> None:
cmd = ["systemctl", "stop", DAEMON_UNIT] + ([] if wait else ["--no-block"])
run(cmd, capture_output=True, timeout=20)
def remove_nm_connection(run=subprocess.run) -> None:
@@ -51,6 +52,33 @@ def remove_nm_connection(run=subprocess.run) -> None:
run(["nmcli", "connection", "delete", "mudfish-tap"], capture_output=True, timeout=10)
CLEANUP_UNIT = "mudfish-decky-cleanup"
CLEANUP_DELAY = 20
def ensure_cleanup_watcher(plugin_dir: str, settings_dir: str, run=subprocess.run) -> None:
"""Remove Mudfish when the plugin is uninstalled, independently of the plugin process.
Decky 3.3 often SIGKILLs plugins before _uninstall runs, so a transient systemd path unit watches
the plugins directory; when this plugin's folder has stayed gone for CLEANUP_DELAY seconds (not a
reinstall/update), it stops the daemon and deletes /opt/mudfish and the plugin settings.
"""
if run(["systemctl", "is-active", "--quiet", f"{CLEANUP_UNIT}.path"],
capture_output=True, timeout=10).returncode == 0:
return
script = "; ".join([
f"sleep {CLEANUP_DELAY}",
f'[ -e "{plugin_dir}/plugin.json" ] && exit 0',
f"systemctl stop {DAEMON_UNIT}",
"nmcli connection delete mudfish-tap",
f"rm -rf {MUDFISH_ROOT}",
f'rm -rf "{settings_dir}"',
f"systemctl stop {CLEANUP_UNIT}.path",
])
run(["systemd-run", f"--unit={CLEANUP_UNIT}", f"--path-property=PathChanged={os.path.dirname(plugin_dir)}",
"/bin/sh", "-c", script], capture_output=True, timeout=10)
def api_base(var_dir: str) -> str:
try:
with open(os.path.join(var_dir, ".ipport")) as f: