c173ca3653624ebe53cec46f4df5020d48457653
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
ae6ed4ac1e |
Rebuild API Server on the PilotVault structure
Mirror PilotVault's API Server layout and add the superadmin console,
plugin system, runtime PocketBase settings, and user/organization
management. The car domain (cars, service records, parts, sharing) is
carried over unchanged apart from the auth switch.
Layout: main.go -> cmd/server/main.go; module carcontrol/api ->
drivervault/apiserver. internal/api is split by concern (auth, users,
orgs, settings, plugins, status, health, respond).
Auth: replace the server-minted HS256 JWT and the sessions collection
with a PocketBase token proxy. /api/auth/login relays PocketBase's
{token, record}, and every protected request re-resolves that token
against PocketBase, so a role change or deletion takes effect at once
instead of waiting out a token. AUTH_SECRET is obsolete and internal/auth
is gone. Per-device session listing/revocation goes with it: PocketBase
tokens are stateless. Changing a password rotates the user's token key,
which invalidates every token already issued.
Roles: add superadmin alongside user/admin, plus an organizations
collection and users.organization. Admins are scoped to their own
organization; superadmins span all of them. Guards prevent changing your
own role, deleting your own account, an admin touching a superadmin, and
deleting an organization that still has members.
Plugins: new internal/plugins package with one contract over two kinds --
builtin (compiled in) and external (any HTTP service, registered at
runtime with no rebuild). State persists to plugins.json; secrets are
masked on read and preserved when saved back at the mask.
PocketBase settings: /api/admin/pb-config applies a new connection at
runtime and persists it to .env. It deliberately does not require a
working service account, so a wrong or unreachable connection can still
be fixed from the panel.
Panel: rebuilt as the superadmin console -- login gate, status, users,
organizations, PocketBase, plugins, and the endpoint reference.
Clients: update the Web App and Phone App for the PocketBase token shape,
the move of user management to /api/users ({users}/{user} envelopes, with
password resets folded into PATCH), and the removal of sessions. Both now
mirror the server's real guards rather than the old last-admin rule, and
parse PocketBase's field-level error shape.
Config: modern POCKETBASE_*/API_ADDR names with legacy PB_*/PORT
fallbacks, so existing .env files keep working. Also fixes /api/status
probing the Web App on 8090 instead of DriverVault's 5173.
Run scripts/setup-pocketbase.mjs to add the organizations collection and
grow users.role; every client must log in once more.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|