Run calendar module in isolated persistent container

This commit is contained in:
ayde64
2026-10-07 22:41:18 +02:00
parent bc5cac32df
commit 67d5d5bb36
5 changed files with 37 additions and 12 deletions

9
Dockerfile Normal file
View File

@@ -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"]

View File

@@ -1,21 +1,20 @@
# Kalender MVP # 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`) 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 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.
## Installation über den MPM-Marketplace ## 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 ## 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/server.js` – HTTP- und SQLite-Logik
- `backend/platform-module-sdk.js` – MPM-Gateway-Integration - `backend/platform-module-sdk.js` – MPM-Gateway-Integration
- `frontend/index.html` – Kalenderoberfläche - `frontend/index.html` – Kalenderoberfläche

View File

@@ -1,13 +1,14 @@
'use strict'; 'use strict';
const fs = require('node:fs'); const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const os = require('node:os');
const { DatabaseSync } = require('node:sqlite'); const { DatabaseSync } = require('node:sqlite');
const { createModuleServer, loadManifest, createLogger } = require('./platform-module-sdk'); const { createModuleServer, loadManifest, createLogger } = require('./platform-module-sdk');
process.umask(0o077); process.umask(0o077);
const manifest = loadManifest(path.join(__dirname, '..', 'module.json')); const manifest = loadManifest(path.join(__dirname, '..', 'module.json'));
const logger = createLogger({ moduleId: manifest.id }); 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)'); 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_date<? ORDER BY event_date,title'); const listEvents = db.prepare('SELECT id,title,event_date,notes FROM events WHERE user_id=? AND event_date>=? AND event_date<? ORDER BY event_date,title');
const insertEvent = db.prepare('INSERT INTO events(user_id,title,event_date,notes) VALUES(?,?,?,?)'); const insertEvent = db.prepare('INSERT INTO events(user_id,title,event_date,notes) VALUES(?,?,?,?)');
@@ -31,4 +32,10 @@ const server = createModuleServer({manifest,logger,routes:async(req,res,{path:pa
if(pathname.startsWith('/api/'))return json(res,404,{error:'Route nicht gefunden.'}); if(pathname.startsWith('/api/'))return json(res,404,{error:'Route nicht gefunden.'});
res.writeHead(404,{'content-type':'text/plain; charset=utf-8'});res.end('Nicht gefunden'); res.writeHead(404,{'content-type':'text/plain; charset=utf-8'});res.end('Nicht gefunden');
}}); }});
server.listen(Number(process.env.PORT||manifest.port),'127.0.0.1',()=>logger.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);

8
compose.yml Normal file
View File

@@ -0,0 +1,8 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
expose:
- "41002"
restart: unless-stopped

View File

@@ -9,5 +9,7 @@
"entrypoint": "server.js", "entrypoint": "server.js",
"port": 41002, "port": 41002,
"healthcheck": "/health", "healthcheck": "/health",
"apiVersion": "v1" "apiVersion": "v1",
"composeFile": "compose.yml",
"appService": "app"
} }