diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 21bba61..c5291dc 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -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. diff --git a/apps/api/src/plugins/plugin-watcher.service.ts b/apps/api/src/plugins/plugin-watcher.service.ts index 1f12ef9..56bdae3 100644 --- a/apps/api/src/plugins/plugin-watcher.service.ts +++ b/apps/api/src/plugins/plugin-watcher.service.ts @@ -39,12 +39,22 @@ export class PluginWatcherService implements OnModuleInit, OnModuleDestroy { async onModuleInit(): Promise { 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 {