From 67d5d5bb36b53ee23398659db7c3ce31e2c2a06e Mon Sep 17 00:00:00 2001 From: ayde64 <99ikone@gmail.com> Date: Wed, 7 Oct 2026 22:41:18 +0200 Subject: [PATCH] Run calendar module in isolated persistent container --- Dockerfile | 9 +++++++++ README.md | 15 +++++++-------- backend/server.js | 13 ++++++++++--- compose.yml | 8 ++++++++ module.json | 4 +++- 5 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 Dockerfile create mode 100644 compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9c2139e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:24-alpine +WORKDIR /app +RUN mkdir -p /var/lib/mpm-module && chown node:node /var/lib/mpm-module +COPY --chown=node:node module.json ./module.json +COPY --chown=node:node backend ./backend +COPY --chown=node:node frontend ./frontend +USER node +EXPOSE 41002 +CMD ["node", "backend/server.js"] diff --git a/README.md b/README.md index 484c174..6afb128 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,20 @@ # Kalender MVP -Ein kleines, eigenständiges Kalender-Modul für MPM. Es zeigt einen Monatskalender, erlaubt Termine anzulegen und speichert sie in SQLite. +Ein kleines, eigenständiges Kalender-Modul für MPM. Es zeigt einen Monatskalender, erlaubt Termine anzulegen und speichert sie je MPM-Benutzer getrennt in SQLite. -## Voraussetzungen +## Containerbetrieb -- Node.js 24 oder neuer (verwendet `node:sqlite`) -- Installation als MPM-Modul aus einem Paket mit `module.json` im ZIP-Stammverzeichnis - -MPM stellt die Modul-Identität über den Module Gateway bereit. Termine werden je MPM-Benutzer getrennt gespeichert. Die SQLite-Datei liegt derzeit im temporären Systemverzeichnis und ist daher nicht für dauerhafte Produktivdaten gedacht. +MPM installiert das Repository als eigenen Compose-Stack. Der App-Container läuft mit Node.js 24, stellt Port `41002` nur intern bereit und lauscht auf `0.0.0.0`. Die SQLite-Datei liegt unter `MPM_MODULE_DATA_DIR` im persistenten MPM-Modulvolume und überlebt Container- sowie Modul-Neustarts. ## Installation über den MPM-Marketplace -MPM kann dieses öffentliche Repository direkt hinzufügen und den aktuellen Stand des Standard-Branches installieren. `module.json` muss dafür im Repository-Stamm liegen. MPM prüft das Manifest und startet das Modul nach der Installation nicht automatisch. +Das öffentliche Repository kann mit der verbundenen Gitea-Instanz direkt aus dem Marketplace installiert werden. `module.json`, `compose.yml` und `Dockerfile` liegen im Repository-Stamm. MPM installiert den Standard-Branch; der Start erfolgt anschließend über die Modulverwaltung. ## Projektstruktur -- `module.json` – MPM-Modulmanifest +- `module.json` – MPM-Modulmanifest einschließlich Container-Service +- `compose.yml` – interner App-Service für den Modul-Stack +- `Dockerfile` – Node.js-24-Laufzeit ohne externe npm-Abhängigkeiten - `backend/server.js` – HTTP- und SQLite-Logik - `backend/platform-module-sdk.js` – MPM-Gateway-Integration - `frontend/index.html` – Kalenderoberfläche diff --git a/backend/server.js b/backend/server.js index dcad45d..9a40cdb 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,13 +1,14 @@ 'use strict'; const fs = require('node:fs'); const path = require('node:path'); -const os = require('node:os'); const { DatabaseSync } = require('node:sqlite'); const { createModuleServer, loadManifest, createLogger } = require('./platform-module-sdk'); process.umask(0o077); const manifest = loadManifest(path.join(__dirname, '..', 'module.json')); const logger = createLogger({ moduleId: manifest.id }); -const db = new DatabaseSync(path.join(os.tmpdir(), manifest.id + '.sqlite')); +const dataDirectory = process.env.MPM_MODULE_DATA_DIR || path.join(__dirname, '..', 'data'); +fs.mkdirSync(dataDirectory, { recursive: true, mode: 0o700 }); +const db = new DatabaseSync(path.join(dataDirectory, manifest.id + '.sqlite')); db.exec('CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, title TEXT NOT NULL CHECK(length(title)<=120), event_date TEXT NOT NULL, notes TEXT NOT NULL DEFAULT "" CHECK(length(notes)<=1000), created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)'); const listEvents = db.prepare('SELECT id,title,event_date,notes FROM events WHERE user_id=? AND event_date>=? AND event_datelogger.info('Kalender MVP gestartet',{port:Number(process.env.PORT||manifest.port)})); +server.listen(Number(process.env.PORT||manifest.port),'0.0.0.0',()=>logger.info('Kalender MVP gestartet',{port:Number(process.env.PORT||manifest.port)})); + +function shutdown() { + try { db.close(); } finally { server.close(() => process.exit(0)); } +} +process.once('SIGTERM', shutdown); +process.once('SIGINT', shutdown); diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..e67f577 --- /dev/null +++ b/compose.yml @@ -0,0 +1,8 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + expose: + - "41002" + restart: unless-stopped diff --git a/module.json b/module.json index 81683af..4007a9c 100644 --- a/module.json +++ b/module.json @@ -9,5 +9,7 @@ "entrypoint": "server.js", "port": 41002, "healthcheck": "/health", - "apiVersion": "v1" + "apiVersion": "v1", + "composeFile": "compose.yml", + "appService": "app" }