diff --git a/PLAN.md b/PLAN.md index 68c4a1a..cdfe3f7 100644 --- a/PLAN.md +++ b/PLAN.md @@ -810,6 +810,12 @@ something long in one, switch away and back, refresh — it's all still there. not exist), the task list read a task's Running flag outside its lock (a real data race, never seen), the Virtualization export config was written in place rather than atomically, and the dead code. Nothing in auth, the proxies or the shell boundaries. + One self-inflicted finding: the battery's skip list left out `POST + /api/files/thumbs/gpu-access`, so the sweep pressed *Allow GPU access* on `core` three + times over — the drop-in landed and the hub restarted (that is what the headless pass + saw as a refused connection). Kept, since it is the fix the tab offers; `core` makes + previews and re-encodes on its UHD iGPU now. The handler refuses a second run while one + is in flight (409), since the second `systemd-run` only collided with the first's timer. - **Docker read-only view** — `docker ps`/logs; dockhand stays the source of truth. - **Notifications** — push on service failure / SMART warning / UPS event. - **Terminal extras** — search, copy-mode, font/theme, split panes, upload via diff --git a/internal/api/thumbs.go b/internal/api/thumbs.go index fa773d3..3c9ebde 100644 --- a/internal/api/thumbs.go +++ b/internal/api/thumbs.go @@ -403,6 +403,10 @@ func (s *Server) thumbsGPUAccess(w http.ResponseWriter, r *http.Request) { jsonErr(w, 400, "nothing to do: Ham Lab is root, has no root, or is not under systemd here") return } + if t, busy := s.tasks.RunningOf("thumbs:gpu"); busy { // a second run would only collide with the first's restart timer + jsonErr(w, 409, "already being done (task "+t.ID+")") + return + } full := priv.Wrap("sh", "-c", gpuAccessScript) t, err := s.tasks.Start("thumbs:gpu", "give Ham Lab access to the GPU", exec.Command(full[0], full[1:]...)) if err != nil {