Commit Graph
5 Commits
Author SHA1 Message Date
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
tajniak81andClaude Opus 5 f08849e50c Docker: a build context that isn't 3.6GB, and health you can see
The all-in-one image builds from the project root, and Docker only reads
.dockerignore from the context root — so the ones under "API Server" and
"Web App" never applied to it and every AIO build shipped the whole tree,
"Phone App/build" included. A root .dockerignore allow-lists the paths that
build actually copies.

The dev split stack passed neither PB_BOOTSTRAP nor the SUPERADMIN vars, so
it created the schema and then no user to log in with. It passes them now,
and .env.example says so.

WEBAPP_URL was never set anywhere, leaving the panel status page probing
localhost:8090 — itself — and always reporting the Web App as down. Each
compose file now points it at wherever the Web App really is, and the BFF
grew a real /healthz instead of letting the SPA fallback answer probes with
index.html and look healthy no matter what.

In the AIO, PocketBase and the API Server drop to an unprivileged user;
only nginx stays root to bind :80. The entrypoint takes ownership of the
two volumes first, so data written by the old root-only image stays
writable. All three images carry a HEALTHCHECK, every compose file declares
one too (so depends_on still gates against an older pulled image), and
web-app waits for the API Server to be serving rather than merely started.

Also: pinned alpine/golang/node and PocketBase 0.39.11, so a rebuild months
from now produces the same image; nginx forwards WebSocket upgrades instead
of stripping them, with the map in http.d where Alpine actually reads it;
and a .gitattributes keeps entrypoint.sh on LF, because a CRLF shebang from
a Windows clone fails at container start with "no such file or directory".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 17:07:52 +02:00
tajniak81andClaude Opus 4.8 01d82ebda9 Docker: persist API Server state, wire up OCPP, drop legacy env names
Audited every Dockerfile, compose file and .env.example against the code
they deploy. Four things had drifted:

Persistence. The API Server writes plugins.json and rewrites .env (the
panel's retarget-PocketBase flow) relative to its working directory,
which was a root-owned /app while the process runs as the app user - so
both writes failed, and no volume was declared to keep them anyway. The
binary moves to /usr/local/bin and the working directory becomes a /data
volume owned by app. The AIO image gets the same via directory=/data on
its supervisord program.

OCPP. Charger control was undeployable: OCPP_REQUIRE_TLS defaults to true
and appeared in no Docker file, so a charger dialling the plain-HTTP
/ocpp/{serial} was rejected with nothing explaining why. Both OCPP vars
are now threaded through the compose files and env examples, with the
reasoning (the per-charger control token rides in a Basic-auth header).

CORS. API Server/docker-compose.yml defaulted to localhost:5173, the Vite
dev port, where every other file uses 8090.

Env names. .env.example has called PB_URL/PB_ADMIN_*/PORT legacy for a
while, but the Docker layer still used them. Container-side names are now
POCKETBASE_*/API_ADDR; the .env keys operators set stay PB_ADMIN_* so
existing .env files keep working.

Left alone deliberately: alpine:latest stays unpinned (cannot verify
current tags or test the build from here), and the golang/node bases
already match go.mod and Vite 8's floor.

Validated as YAML only - there is no Docker CLI on this machine, so no
image was built and the /data ownership fix follows standard volume
semantics rather than an observed run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 22:46:01 +02:00
tajniak81andClaude Opus 4.8 1e76c2b7f9 Refresh docs and fix Docker builds for current layout
READMEs: correct the auth model (PocketBase token relay, not JWT/sessions),
document the full feature set (technical checks, fuel, maintenance, documents,
reminders, attachments, integrations, OCPP charging control), the shipping
built-in connectors (toyota, anker-solix), and the current endpoint surface.

Docker: build against the current repo layout — Go 1.26, cmd/server entry
point, Web App source under web/. Add the missing Web App Dockerfile (Go BFF)
and .dockerignore, drop the obsolete AUTH_SECRET, modernise CORS var naming,
and standardise on drivervault-* naming.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 11:05:46 +02:00
tajniak81 ba3f227361 Initial commit 2026-07-06 08:50:52 +02:00