From bbf3b899f7f107ac5af04a64e8c50785ae45e15e Mon Sep 17 00:00:00 2001 From: elpatron Date: Sat, 28 Feb 2026 15:18:57 +0100 Subject: [PATCH] =?UTF-8?q?Restic-Kompatibilit=C3=A4t,=20POST,=20rekursive?= =?UTF-8?q?s=20MKCOL,=20findstr=20/C:=20Fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Server: POST für Uploads, rekursives MKCOL/PUT, ensureFolderExists - PUT: fehlende Elternordner werden erstellt - Scripts: findstr /C: für Literalsuche (Punkt-Konflikt behoben) - Docs: Restic + rclone Hinweis Made-with: Cursor --- docs/browser-token-auth.md | 8 ++++++ scripts/start-webdav.cmd | 4 +-- scripts/stop-webdav.cmd | 4 +-- src/server.js | 52 ++++++++++++++++++++++++++++++++------ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/docs/browser-token-auth.md b/docs/browser-token-auth.md index 79107a8..a9d3a9e 100644 --- a/docs/browser-token-auth.md +++ b/docs/browser-token-auth.md @@ -81,6 +81,14 @@ C:\Pfad\zu\internxt-webdav\scripts\stop-webdav.cmd 8080 Der Server startet im Hintergrund und ist nach ~5 Sekunden bereit. +## Restic + rclone + +```bash +restic -r rclone:internxt-webdav:repo-name init +``` + +Der Server erstellt fehlende Ordner rekursiv (MKCOL). Bei 500-Fehlern: Server-Log prüfen (`PUT Fehler:`), Token mit `npm run token-refresh` erneuern. + ## WebDAV-Credentials (für Duplicati, Explorer) Der Server erwartet **Basic Auth**. Ohne `WEBDAV_USER`/`WEBDAV_PASS` in `.env` akzeptiert er **beliebige** Credentials – Sie können in Duplicati z.B. Benutzername `backup` und Passwort `geheim` eintragen. Mit `WEBDAV_USER` und `WEBDAV_PASS` werden nur diese Credentials akzeptiert. diff --git a/scripts/start-webdav.cmd b/scripts/start-webdav.cmd index bb47593..07b80b5 100644 --- a/scripts/start-webdav.cmd +++ b/scripts/start-webdav.cmd @@ -19,8 +19,8 @@ if %errorlevel% neq 0 ( exit /b 1 ) -REM Pruefen ob Server bereits laeuft (0.0.0.0:0 = Listening, sprachunabhaengig) -netstat -an | findstr ":%PORT% " | findstr "0.0.0.0:0" > nul 2>&1 +REM Pruefen ob Server bereits laeuft (0.0.0.0:0 = Listening) +netstat -an | findstr /C:":%PORT% " | findstr /C:"0.0.0.0:0" > nul 2>&1 if %errorlevel% equ 0 ( echo WebDAV-Server laeuft bereits. exit /b 0 diff --git a/scripts/stop-webdav.cmd b/scripts/stop-webdav.cmd index 9b815bf..5bb4029 100644 --- a/scripts/stop-webdav.cmd +++ b/scripts/stop-webdav.cmd @@ -8,9 +8,9 @@ if "%1"=="" (set PORT=3005) else (set PORT=%1) REM Prozess auf Port finden und beenden REM Filter: Port + "0.0.0.0:0" = Listening (sprachunabhaengig) -for /f "tokens=5" %%a in ('netstat -ano 2^>nul ^| findstr ":%PORT% " ^| findstr "0.0.0.0:0"') do ( +for /f "tokens=5" %%a in ('netstat -ano 2^>nul ^| findstr /C:":%PORT% " ^| findstr /C:"0.0.0.0:0"') do ( taskkill /PID %%a /F > nul 2>&1 - echo WebDAV-Server beendet (PID %%a). + echo WebDAV-Server beendet - PID %%a exit /b 0 ) diff --git a/src/server.js b/src/server.js index 3bbfb58..3c127cb 100644 --- a/src/server.js +++ b/src/server.js @@ -67,8 +67,8 @@ function basicAuth(req, res, next) { app.use(basicAuth); -// Request-Body: PUT als Raw (Datei-Upload), PROPFIND als Text -app.use(express.raw({ type: (req) => req.method === 'PUT', limit: '1gb' })); +// Request-Body: PUT/POST als Raw (Datei-Upload), PROPFIND als Text +app.use(express.raw({ type: (req) => req.method === 'PUT' || req.method === 'POST', limit: '1gb' })); app.use(express.text({ type: 'application/xml', limit: '1kb' })); /** @@ -254,7 +254,37 @@ async function getContext() { } /** - * MKCOL Handler – Ordner anlegen + * Stellt sicher, dass ein Ordnerpfad existiert (erstellt fehlende Eltern rekursiv). + * @returns {Promise<{ uuid: string } | null>} Ordner oder null + */ +async function ensureFolderExists(storage, rootUuid, path) { + const segments = pathToSegments(path); + let currentUuid = rootUuid; + + for (const segment of segments) { + const [contentPromise] = storage.getFolderContentByUuid({ folderUuid: currentUuid }); + const content = await contentPromise; + const child = content?.children?.find((c) => { + const name = getPlainName(c.name, c.plain_name ?? c.plainName, c.parent_id ?? c.parentId, null); + return sanitizeForPath(name).toLowerCase() === sanitizeForPath(segment).toLowerCase(); + }); + if (child) { + currentUuid = child.uuid; + } else { + const [createPromise] = storage.createFolderByUuid({ + parentFolderUuid: currentUuid, + plainName: segment, + }); + const created = await createPromise; + currentUuid = created?.uuid; + if (!currentUuid) return null; + } + } + return { uuid: currentUuid }; +} + +/** + * MKCOL Handler – Ordner anlegen (rekursiv: fehlende Eltern werden erstellt) */ async function handleMkcol(req, res) { let path = req.url || '/'; @@ -279,7 +309,10 @@ async function handleMkcol(req, res) { try { const { storage, rootUuid } = await getContext(); - const parent = await resolveFolder(storage, rootUuid, parentPath); + const parent = + parentPath && parentPath !== '/' + ? await ensureFolderExists(storage, rootUuid, parentPath) + : { uuid: rootUuid }; if (!parent) { res.status(409).send('Übergeordneter Ordner existiert nicht'); return; @@ -582,7 +615,10 @@ async function handlePut(req, res) { const parentPath = segmentsToPath(segments.slice(0, -1)); const fileName = segments[segments.length - 1]; - const parent = await resolveFolder(storage, rootUuid, parentPath); + let parent = await resolveFolder(storage, rootUuid, parentPath); + if (!parent && parentPath && parentPath !== '/') { + parent = await ensureFolderExists(storage, rootUuid, parentPath); + } if (!parent) { res.status(409).send('Zielordner existiert nicht'); return; @@ -664,7 +700,7 @@ async function handlePut(req, res) { res.status(201).send(); } catch (err) { - console.error('PUT Fehler:', err.message); + console.error('PUT Fehler:', path, err.message); if (err.message?.includes('Token') || err.response?.status === 401) { res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com'); return; @@ -676,7 +712,7 @@ async function handlePut(req, res) { // WebDAV Endpoints app.options('*', (req, res) => { res.set('DAV', '1, 2'); - res.set('Allow', 'OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, MOVE'); + res.set('Allow', 'OPTIONS, PROPFIND, GET, HEAD, PUT, POST, DELETE, MKCOL, MOVE'); res.sendStatus(200); }); @@ -700,7 +736,7 @@ app.use((req, res, next) => { }); return; } - if (req.method === 'PUT') { + if (req.method === 'PUT' || req.method === 'POST') { handlePut(req, res).catch((err) => { console.error('PUT unhandled:', err); if (!res.headersSent) res.status(500).send(err.message);