Files
DriverVault/API Server/internal
tajniak81andClaude Opus 5 c173ca3653 Plugins: a save that fails should say so, not vanish on redeploy
Reported symptom: every plugin comes back disabled after redeploying the
image, having been enabled before it. The persistence design was already
right - each compose file mounts api_data:/data and points PLUGINS_FILE at
/data/plugins.json - so the fault was that a failed write to that file was
invisible. Three defects, each confirmed with a test before being fixed:

A failed write was reported as success. Upsert set rec.Enabled before it
persisted, and the handler folded the resulting error into the same
200-with-warning used for "saved, but the connector failed to start". The
panel reloaded, read the in-memory record and showed the plugin enabled;
only a restart revealed that nothing had reached the disk. A save that
fails now rolls back in memory and returns 500, so the panel row shows the
error instead of "Saved".

A corrupt state file silently wiped the rest. Load returned an error,
main.go logged it and carried on with an empty record set, so the next
toggle overwrote plugins.json and took every other plugin's config with
it. An unreadable file is now moved aside to plugins.json.corrupt, and
persistLocked writes through a temp file + rename so an interrupted write
cannot produce that corrupt file in the first place.

A state file holding "null" panicked the server with "assignment to entry
in nil map" on the next save, and a null entry nil-dereferenced in Load.
Both now decode to "nothing configured".

Two changes make the next such failure loud rather than silent.
StartPlugins probes writability at boot and warns that plugin changes will
not survive a restart. And the API Server image gains the root entrypoint
the AIO image already had - chown /data, then drop to app via su-exec -
because a host bind mount (API_DATA=/srv/...) or a volume created before
/data existed arrives root-owned, and the unprivileged process cannot
write to it.

Not addressed here: a deployment that never reuses the named volume
(docker compose down -v, a renamed compose project, an anonymous volume
from a bare docker run) loses the file whatever the code does. The new
boot warning tells the two apart - writable but empty means the volume is
the problem, not permissions.

go build, go vet and go test ./... all pass. The Dockerfile change is
reviewed but not built: there is no Docker CLI on this machine, so the
su-exec privilege drop follows standard Alpine practice rather than an
observed run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:17:47 +02:00
..