Verbindungen, Remote-Modus und Headless-Betrieb
Eine Verbindung ist eine gespeicherte andere Instanz (Name, Adresse, API-Token mit Schreibrecht dort). Verbindungen gehören zur lokalen Instanz, sind nur per Admin-Session erreichbar 👤 und nicht im Bundle; der Token wird nie ausgeliefert (nur token_prefix).
{ "id": 1, "name": "Live-Server", "url": "https://www.firma.de", "token_prefix": "ic_Ab3dEf4",
"last_sync_at": 1791313300, "last_sync": "push", "created_at": 1791313195 }
| Endpunkt | Beschreibung |
|---|---|
GET /connections |
Alle Verbindungen |
POST /connections |
{ "name", "url", "token" } → 201. Die Adresse wird normalisiert (ohne Schema: https://) |
PUT /connections/{id} |
Name, Adresse, Token ändern (leerer Token = unverändert). Eine andere Adresse setzt den Abgleich-Stand zurück |
DELETE /connections/{id} |
Löschen → 204 |
GET /connections/{id}/status |
Fragt den Server ab: reachable, error, remote (Antwort von /bundle/info des Servers), local_schema, synced, local_changed / remote_changed (Inhalt hier bzw. dort seit dem letzten Abgleich geändert, Live-Daten ausgenommen), local_version, local_commit, version_mismatch (anderes Schema oder andere Version – bei Entwicklungs-Builds anderer Commit) |
POST /connections/select |
{ "id": 1 } schaltet den Remote-Modus ein, { "id": 0 } aus → 204 |
Remote-Modus: select setzt das Cookie ic_remote. Solange es gesetzt ist, reicht die Instanz alle Anfragen einer Admin-Session an /api/v1/…, /media/… und /_assets/… mit dem gespeicherten Token an den Server der Verbindung weiter – das Admin arbeitet dann live mit ihm. Ausgenommen (bleiben lokal): /auth/…, /users, /tokens, /connections…, /bundle/push, /bundle/pull. Schreibende Anfragen brauchen wie sonst X-IC-CSRF; ohne Session wird nichts weitergereicht. GET /auth/status enthält dann remote (die Verbindung). Weitergereichte Antworten tragen Cache-Control: no-store, weil dieselbe Adresse je nach Auswahl von einer anderen Instanz kommt.
Headless-Betrieb: ohne Admin-App, Anmeldung und Sitzungen – fest per IC_HEADLESS=1 bzw. -headless oder umschaltbar per PUT /system. /cms-admin, /auth/… und alle 👤-Endpunkte liefern 404, Session-Cookies werden ignoriert. Website, Token-API, HTTP-Eingänge und öffentliche Pens laufen normal; /healthz und /bundle/info melden "headless": true. Einen Token legt illusioncms -data <ordner> -create-token "Name" an (Schreibrecht, Ausgabe auf stdout).
Master-Token (IC_MASTER_TOKEN, mindestens 32 Zeichen): gilt wie ein API-Token mit Schreibrecht (Header oder bei GET ?token=), ohne Benutzerkonto und ohne Eintrag in der Datenbank – für Server, die nur per Verbindung gepflegt werden, und als Notausgang. Konto- und Token-Verwaltung (👤) bleibt ihm verschlossen. Autor im Verlauf: Master-Token.
GET /system ✏️ · PUT /system ✏️
Betrieb dieser Instanz – auch per Token, also aus einer anderen Instanz über eine Verbindung.
{ "admin_enabled": true, "forced": false, "write_tokens": 2, "master_token": false, "version": "v0.1.9" }
PUT mit { "admin_enabled": false } schaltet das Admin ab (gespeichert, gilt auch nach einem Neustart), true wieder ein. 409, wenn das Admin per Startoption abgeschaltet ist (forced) oder beim Abschalten weder ein Token mit Schreibrecht noch ein Master-Token existiert (sonst käme niemand mehr hinein). Auf dem Server selbst schaltet illusioncms -enable-admin es wieder ein.
Sicherheits-Header
GET /security ✏️ · PUT /security ✏️ – Einstellungen dieser Instanz (nicht im Bundle) und die daraus erzeugten Header. PUT ist ein Merge-Update.
{
"settings": {
"referrer_policy": "strict-origin-when-cross-origin",
"framing": "sameorigin",
"frame_domains": [],
"hsts": false, "hsts_max_age": 31536000, "hsts_subdomains": false, "hsts_preload": false,
"permissions_policy": false,
"csp": "", "csp_report_only": false,
"hide_version": false
},
"headers": {
"all": [{ "name": "X-Content-Type-Options", "value": "nosniff" }, { "name": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }],
"site": [{ "name": "X-Frame-Options", "value": "SAMEORIGIN" }, { "name": "Content-Security-Policy", "value": "frame-ancestors 'self'" }]
}
}
| Feld | Werte |
|---|---|
referrer_policy |
ein Wert der Referrer-Policy-Spezifikation oder "" (kein Header) |
framing |
sameorigin, deny, any (kein Schutz) oder domains mit frame_domains (https://partner.de, *.partner.de). Gesendet als X-Frame-Options und/oder CSP frame-ancestors; steht frame-ancestors schon in csp, gilt diese Angabe |
hsts* |
max-age 0 bis 63072000 s; preload braucht hsts_subdomains und mindestens 1 Jahr. Nur über HTTPS (direkt oder X-Forwarded-Proto: https von einem lokalen/privaten Proxy) |
permissions_policy |
sperrt Kamera, Mikrofon, Standort, Zahlungen, USB, Serial, Bluetooth, HID, MIDI, Topics |
csp, csp_report_only |
eigene Content-Security-Policy (max. 4000 Zeichen, Zeilenumbrüche werden zu Leerzeichen); report_only sendet sie als …-Report-Only |
hide_version |
/healthz liefert ohne Anmeldung nur status und db |
all gilt für alle Antworten, site nur für Website-Seiten (nicht /cms-admin, /api, /media, /_assets, /healthz). Ungültige Werte: 400.
IllusionDB-Verbindungen
Verbindungen zu IllusionDB-Servern für den Workflow-Node illusiondb und idb.* im Function-Node. Gespeichert in der Instanz (nicht im Bundle); Workflows verweisen über den Namen.
GET /illusiondb ✏️ – [{ "name": "shop", "url": "https://db.firma.de", "has_token": true }] (der Token wird nie ausgegeben).
PUT /illusiondb ✏️ – ersetzt die Liste: [{ "name": "shop", "url": "https://db.firma.de", "token": "…" }]. Name: Buchstaben, Ziffern, Leerzeichen, . _ - (max. 64, eindeutig ohne Groß-/Kleinschreibung); Adresse http(s)://… (ein angehängtes /api/v1 wird entfernt). Fehlt token (oder ist null), bleibt der bisherige der Verbindung – beim Umbenennen über old_name zugeordnet; "" entfernt ihn. Antwort wie GET.
POST /illusiondb/{name}/test ✏️ – { "ok": true, "version": "1.4.0", "tables": 12 }, sonst 502 mit der Meldung von IllusionDB (z. B. authentication required).
GET /illusiondb/{name}/tables ✏️ – lesbare Tabellen aller Datenquellen (ohne n:m-Hilfstabellen, eine Minute zwischengespeichert, ?fresh=1 lädt neu): [{ "id", "name", "schema", "source", "source_id", "ref", "public_read" }]. ref ist der Name, wie ihn der Node verwendet (bei Mehrdeutigkeit Datenquelle/name).
GET /illusiondb/{name}/tables/{table} ✏️ – {table} wie im Node (Name, Datenquelle/name oder ID): { "table": {…}, "columns": [{ "name", "ui_type", "field_kind", "primary_key", "nullable" }], "views": [{ "id", "name", "type", "default" }] }. Unbekannte Tabelle: 404.
E-Mail-Versand
GET /system/mail ✏️ – SMTP-Zugang für den Workflow-Node mail: { "host": "smtp.firma.de", "port": 587, "user": "web@firma.de", "from": "Website <web@firma.de>", "has_password": true } (das Passwort wird nie ausgegeben).
PUT /system/mail ✏️ – gleiche Felder plus password; fehlt password, bleibt das bisherige. Port 465 = TLS, sonst STARTTLS (mit Benutzer Pflicht). Gespeichert in der Instanz, nicht im Bundle.
POST /system/mail/test ✏️ mit { "to": "ich@firma.de" } schickt eine Testmail – 204, sonst 502 mit der Meldung des Servers.
Medienspeicher
Ablage der Mediendateien dieser Instanz: lokal (data/media, Standard) oder ein S3-kompatibler Speicher (Garage, AWS, Hetzner, Cloudflare R2 …). Gespeichert in der Einstellung storage (nicht im Bundle). Die Adressen bleiben /media/… – das CMS liefert auch S3-Dateien selbst aus (Range, 304), der Bucket bleibt privat. Alle Endpunkte gehen auch per Token, also über eine Verbindung im Remote-Modus.
GET /storage ✏️
{
"type": "s3",
"s3": { "endpoint": "https://fsn1.your-objectstorage.com", "region": "fsn1", "bucket": "firma-media", "prefix": "cms/",
"access_key": "AKIA…", "path_style": false, "secret_set": true },
"cache_mb": 0,
"cache": { "enabled": true, "max_bytes": 536870912, "used_bytes": 10485760, "entries": 120, "default_bytes": 536870912 },
"media": { "files": 812, "bytes": 734003200 },
"migration": { "status": "idle" },
"encryption": "file"
}
Das Secret wird nie ausgegeben (secret_set). Bei type: "local" steht eine eingetragene S3-Konfiguration als Entwurf in s3. media zählt die Objekte der aktiven Ablage (eine Minute zwischengespeichert, partial: true nach 20 s Zeitlimit). encryption: Schlüssel aus IC_ENCRYPTION_KEY (env) oder data/secret.key (file). Ließ sich S3 beim Start nicht aktivieren (z. B. Schlüssel geändert), stehen error/error_code dabei und die lokale Ablage ist aktiv.
PUT /storage ✏️ – { "s3": { "endpoint", "region", "bucket", "prefix", "access_key", "secret_key", "path_style" }, "cache_mb": 0 }. Fehlt secret_key (oder ist leer), bleibt das gespeicherte – solange access_key gleich bleibt. Ist S3 aktiv, wird die neue Konfiguration erst getestet und gilt dann sofort; bucket/prefix lassen sich dann nur per Migration ändern (409 storage.migrationRequired). Bei lokaler Ablage ist es nur ein Entwurf; leere Felder löschen ihn. cache_mb (0 = Startoption -media-cache-mb) ist die Größe des Plattencaches vor S3. Antwort wie GET.
POST /storage/check ✏️ – { "s3": {…} } (ohne s3: die gespeicherte Konfiguration) testet mit einem Probe-Objekt (schreiben, lesen, löschen): { "ok": true }, sonst 400 (Konfiguration, z. B. storage.endpointInvalid) bzw. 502 mit storage.unreachable, storage.authFailed, storage.noBucket, storage.accessDenied oder storage.checkFailed und der Meldung des Speichers in params.error.
POST /storage/migrate ✏️ – geführte Migration als Hintergrund-Job: { "target": "s3", "s3": {…}, "delete_old": false } bzw. { "target": "local" }. Antwort 202 mit dem Status. Ablauf (phase): check (Ziel testen) → scan → copy (übersprungen wird, was am Ziel mit gleicher Größe und nicht älter schon liegt) → verify (jedes kopierte Objekt am Ziel vorhanden, gleiche Größe) → switch (Einstellung speichern, umschalten, Seiten-Cache leeren) → optional cleanup (alte Objekte löschen). Schreibzugriffe während der Migration (Uploads, Löschen, Bundle-Import) gehen an die alte Ablage und werden mitgeschrieben; beim Umschalten werden sie kurz angehalten, die geänderten Dateien nachgezogen, danach landen sie am Ziel. Fehler und Abbruch lassen die alte Ablage aktiv. 409 bei laufender Migration (storage.migrationRunning) oder gleichem Ziel (storage.sameTarget).
GET /storage/migrate ✏️ – Status:
{ "status": "running", "phase": "copy", "from": "local", "to": "s3", "delete_old": false,
"total_files": 812, "total_bytes": 734003200, "done_files": 300, "done_bytes": 201326592,
"skipped_files": 0, "verified_files": 0, "changed_files": 0, "deleted_files": 0, "started_at": 1791571753 }
status: idle, running, done, failed (mit error, error_code, error_params), canceled. changed_files = beim Umschalten nachgezogene Änderungen, cleanup_errors = alte Objekte, die sich nicht löschen ließen (nur Warnung).
DELETE /storage/migrate ✏️ – bricht ab (bis vor switch; danach 409 storage.migrationTooLate, ohne Job 409 storage.migrationNotRunning).