From 507af1dbff3fd179ee32215048a905bfb8fd3ae1 Mon Sep 17 00:00:00 2001 From: mram Date: Mon, 21 Sep 2026 22:34:10 +0200 Subject: [PATCH] Managed ComfyUI picks up Comfy-Desktop shared models/input/output automatically --- internal/service/service_linux.go | 8 +++++++- internal/service/service_windows.go | 9 +++++++++ internal/supervise/supervise.go | 29 +++++++++++++++++++++++++++- internal/supervise/supervise_test.go | 21 ++++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/internal/service/service_linux.go b/internal/service/service_linux.go index 6c7255e..44f40d0 100644 --- a/internal/service/service_linux.go +++ b/internal/service/service_linux.go @@ -15,6 +15,8 @@ import ( "os/signal" "path/filepath" "syscall" + + "gpu-turnstile/internal/supervise" ) // Name matches the Windows service name; the systemd unit is Name + ".service". @@ -66,7 +68,8 @@ func Run(run func(ctx context.Context) error) error { // BindPaths hole through ProtectHome/ProtectSystem: it reads its venv and // writes output/temp/user data under COMFY_DIR. A venv whose base // interpreter (pyvenv.cfg home) lives outside COMFY_DIR gets an additional -// read-only bind. +// read-only bind, and a Comfy-Desktop shared data dir (models, input, +// output) a read-write one. func renderUnit(exePath, configPath, comfyDir string) string { bind := "" if comfyDir != "" { @@ -74,6 +77,9 @@ func renderUnit(exePath, configPath, comfyDir string) string { if home := comfyVenvHome(comfyDir); home != "" { bind += "BindReadOnlyPaths=" + home + "\n" } + if shared := supervise.DesktopSharedDir(comfyDir); shared != "" { + bind += "BindPaths=" + shared + "\n" + } } return fmt.Sprintf(`[Unit] Description=gpu-turnstile GPU arbitration proxy for Ollama and ComfyUI diff --git a/internal/service/service_windows.go b/internal/service/service_windows.go index c3cfe60..ade40f0 100644 --- a/internal/service/service_windows.go +++ b/internal/service/service_windows.go @@ -24,6 +24,8 @@ import ( "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/mgr" + + "gpu-turnstile/internal/supervise" ) // Name is the Windows service name. @@ -382,6 +384,13 @@ func grantAll(exe, configPath string) error { return err } } + // Comfy-Desktop keeps models/input/output in a shared dir next + // to the install; the managed instance writes output there. + if shared := supervise.DesktopSharedDir(comfyDir); shared != "" { + if err := grantAccessTree(shared, "(OI)(CI)(M)"); err != nil { + return err + } + } } } return nil diff --git a/internal/supervise/supervise.go b/internal/supervise/supervise.go index c2f4e3c..1928ac4 100644 --- a/internal/supervise/supervise.go +++ b/internal/supervise/supervise.go @@ -44,7 +44,9 @@ func ComfyLayout(goos, dir string) (python, script string) { // DefaultComfyCommand builds the launch command for the standard venv // layout (see ComfyLayout): the script is passed relative to dir so dir // stays the working directory, and --port is taken from comfyURL when the -// URL carries one. +// URL carries one. On a Comfy-Desktop standalone install the shared data +// directory (models, input, output) is added as --*-directory flags so the +// managed instance sees the desktop app's models. func DefaultComfyCommand(goos, dir, comfyURL string) string { python, script := ComfyLayout(goos, dir) rel, err := filepath.Rel(dir, script) @@ -55,9 +57,34 @@ func DefaultComfyCommand(goos, dir, comfyURL string) string { if u, err := url.Parse(comfyURL); err == nil && u.Port() != "" { cmd += " --port " + u.Port() } + if shared := DesktopSharedDir(dir); shared != "" { + for _, sub := range []string{"models", "input", "output"} { + p := filepath.Join(shared, sub) + if st, err := os.Stat(p); err == nil && st.IsDir() { + cmd += ` --` + sub + `-directory "` + p + `"` + } + } + } return cmd } +// DesktopSharedDir returns the Comfy-Desktop shared data directory +// (/ComfyUI-Shared) when dir looks like a desktop standalone install +// (/ComfyUI-Installs//ComfyUI) and the shared models directory +// exists; "" otherwise. The desktop app keeps models, input and output +// there rather than inside the ComfyUI tree. +func DesktopSharedDir(dir string) string { + installs := filepath.Dir(filepath.Dir(dir)) + if filepath.Base(installs) != "ComfyUI-Installs" { + return "" + } + shared := filepath.Join(filepath.Dir(installs), "ComfyUI-Shared") + if st, err := os.Stat(filepath.Join(shared, "models")); err == nil && st.IsDir() { + return shared + } + return "" +} + // Process is one managed child process. type Process struct { name string diff --git a/internal/supervise/supervise_test.go b/internal/supervise/supervise_test.go index ffb20ef..1d92b0f 100644 --- a/internal/supervise/supervise_test.go +++ b/internal/supervise/supervise_test.go @@ -253,3 +253,24 @@ func TestStripANSI(t *testing.T) { } } } + +func TestDesktopSharedDir(t *testing.T) { + root := t.TempDir() + comfy := filepath.Join(root, "ComfyUI-Installs", "rtx5080", "ComfyUI") + if err := os.MkdirAll(comfy, 0o755); err != nil { + t.Fatal(err) + } + if got := DesktopSharedDir(comfy); got != "" { + t.Fatalf("no shared dir yet: got %q, want empty", got) + } + shared := filepath.Join(root, "ComfyUI-Shared") + if err := os.MkdirAll(filepath.Join(shared, "models"), 0o755); err != nil { + t.Fatal(err) + } + if got := DesktopSharedDir(comfy); got != shared { + t.Fatalf("got %q, want %q", got, shared) + } + if got := DesktopSharedDir(filepath.Join(root, "plain", "ComfyUI")); got != "" { + t.Fatalf("non-desktop layout: got %q, want empty", got) + } +}