Never crash boot on plugin dropzone setup; default PLUGINS_DIR in image (#71)
All checks were successful
CD / Build and push images (push) Successful in 2m41s
CI / Lint, typecheck, test (push) Successful in 3m20s
CI / Auth e2e pack (push) Successful in 4m9s
CI / Import/export fidelity gate (push) Successful in 54s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Has been skipped
All checks were successful
CD / Build and push images (push) Successful in 2m41s
CI / Lint, typecheck, test (push) Successful in 3m20s
CI / Auth e2e pack (push) Successful in 4m9s
CI / Import/export fidelity gate (push) Successful in 54s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Has been skipped
The Test stage crash-looped: PluginWatcherService.onModuleInit did `mkdir`
on the default `./data/plugins` (→ /app/data, not writable by the non-root
user) and an unhandled EACCES aborted bootstrap. Two fixes:
- Harden the watcher: its dropzone is an optional convenience over the GUI
upload, so a setup failure now logs a warning and disables drop-to-install
instead of taking down the api.
- Bake writable defaults (UPLOADS_DIR/PLUGINS_DIR=/data/…) into the api image
so it works out of the box even where compose does not set them; compose
still mounts named volumes there for persistence.
Migrations applied cleanly ("No pending migrations"); this was purely the
boot-time directory permission.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
This commit is contained in:
parent
d7aa1fb6da
commit
f3938b7fdb
@ -25,7 +25,10 @@ RUN pnpm install --frozen-lockfile --filter @dorfteich/api... \
|
||||
|
||||
FROM node:22.15-alpine
|
||||
ARG APP_VERSION=0.0.0-dev
|
||||
ENV NODE_ENV=production APP_VERSION=${APP_VERSION}
|
||||
# Default the data dirs to the writable, node-owned locations created below, so
|
||||
# the image works out of the box even where compose does not set them; compose
|
||||
# still mounts named volumes here for persistence (UPLOADS_DIR/PLUGINS_DIR).
|
||||
ENV NODE_ENV=production APP_VERSION=${APP_VERSION} UPLOADS_DIR=/data/uploads PLUGINS_DIR=/data/plugins
|
||||
WORKDIR /app
|
||||
COPY --from=build --chown=node:node /out /app
|
||||
# Generate the Prisma client for this image's platform.
|
||||
|
||||
@ -39,12 +39,22 @@ export class PluginWatcherService implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
if (this.config.env.NODE_ENV === 'test') return;
|
||||
await this.storage.ensureServiceDirs();
|
||||
this.watcher = watch(this.storage.dropzoneDir, (_event, filename) => {
|
||||
if (!filename || !filename.endsWith('.zip')) return;
|
||||
this.schedule(filename.toString());
|
||||
});
|
||||
this.logger.info({ dir: this.storage.dropzoneDir }, 'watching plugin dropzone');
|
||||
// The dropzone is an optional convenience over the GUI upload. If its
|
||||
// directory cannot be created or watched (e.g. PLUGINS_DIR is not writable
|
||||
// on this deployment), log and carry on — it must never take down the api.
|
||||
try {
|
||||
await this.storage.ensureServiceDirs();
|
||||
this.watcher = watch(this.storage.dropzoneDir, (_event, filename) => {
|
||||
if (!filename || !filename.endsWith('.zip')) return;
|
||||
this.schedule(filename.toString());
|
||||
});
|
||||
this.logger.info({ dir: this.storage.dropzoneDir }, 'watching plugin dropzone');
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
{ err: error, dir: this.storage.dropzoneDir },
|
||||
'plugin dropzone unavailable; drop-to-install disabled (GUI upload still works)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onModuleDestroy(): void {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user