Restic-Kompatibilität, POST, rekursives MKCOL, findstr /C: Fix
- 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
This commit is contained in:
@@ -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.
|
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)
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ if %errorlevel% neq 0 (
|
|||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
REM Pruefen ob Server bereits laeuft (0.0.0.0:0 = Listening, sprachunabhaengig)
|
REM Pruefen ob Server bereits laeuft (0.0.0.0:0 = Listening)
|
||||||
netstat -an | findstr ":%PORT% " | findstr "0.0.0.0:0" > nul 2>&1
|
netstat -an | findstr /C:":%PORT% " | findstr /C:"0.0.0.0:0" > nul 2>&1
|
||||||
if %errorlevel% equ 0 (
|
if %errorlevel% equ 0 (
|
||||||
echo WebDAV-Server laeuft bereits.
|
echo WebDAV-Server laeuft bereits.
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ if "%1"=="" (set PORT=3005) else (set PORT=%1)
|
|||||||
|
|
||||||
REM Prozess auf Port finden und beenden
|
REM Prozess auf Port finden und beenden
|
||||||
REM Filter: Port + "0.0.0.0:0" = Listening (sprachunabhaengig)
|
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
|
taskkill /PID %%a /F > nul 2>&1
|
||||||
echo WebDAV-Server beendet (PID %%a).
|
echo WebDAV-Server beendet - PID %%a
|
||||||
exit /b 0
|
exit /b 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ function basicAuth(req, res, next) {
|
|||||||
|
|
||||||
app.use(basicAuth);
|
app.use(basicAuth);
|
||||||
|
|
||||||
// Request-Body: PUT als Raw (Datei-Upload), PROPFIND als Text
|
// Request-Body: PUT/POST als Raw (Datei-Upload), PROPFIND als Text
|
||||||
app.use(express.raw({ type: (req) => req.method === 'PUT', limit: '1gb' }));
|
app.use(express.raw({ type: (req) => req.method === 'PUT' || req.method === 'POST', limit: '1gb' }));
|
||||||
app.use(express.text({ type: 'application/xml', limit: '1kb' }));
|
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) {
|
async function handleMkcol(req, res) {
|
||||||
let path = req.url || '/';
|
let path = req.url || '/';
|
||||||
@@ -279,7 +309,10 @@ async function handleMkcol(req, res) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { storage, rootUuid } = await getContext();
|
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) {
|
if (!parent) {
|
||||||
res.status(409).send('Übergeordneter Ordner existiert nicht');
|
res.status(409).send('Übergeordneter Ordner existiert nicht');
|
||||||
return;
|
return;
|
||||||
@@ -582,7 +615,10 @@ async function handlePut(req, res) {
|
|||||||
const parentPath = segmentsToPath(segments.slice(0, -1));
|
const parentPath = segmentsToPath(segments.slice(0, -1));
|
||||||
const fileName = segments[segments.length - 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) {
|
if (!parent) {
|
||||||
res.status(409).send('Zielordner existiert nicht');
|
res.status(409).send('Zielordner existiert nicht');
|
||||||
return;
|
return;
|
||||||
@@ -664,7 +700,7 @@ async function handlePut(req, res) {
|
|||||||
|
|
||||||
res.status(201).send();
|
res.status(201).send();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('PUT Fehler:', err.message);
|
console.error('PUT Fehler:', path, err.message);
|
||||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||||
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
||||||
return;
|
return;
|
||||||
@@ -676,7 +712,7 @@ async function handlePut(req, res) {
|
|||||||
// WebDAV Endpoints
|
// WebDAV Endpoints
|
||||||
app.options('*', (req, res) => {
|
app.options('*', (req, res) => {
|
||||||
res.set('DAV', '1, 2');
|
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);
|
res.sendStatus(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -700,7 +736,7 @@ app.use((req, res, next) => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (req.method === 'PUT') {
|
if (req.method === 'PUT' || req.method === 'POST') {
|
||||||
handlePut(req, res).catch((err) => {
|
handlePut(req, res).catch((err) => {
|
||||||
console.error('PUT unhandled:', err);
|
console.error('PUT unhandled:', err);
|
||||||
if (!res.headersSent) res.status(500).send(err.message);
|
if (!res.headersSent) res.status(500).send(err.message);
|
||||||
|
|||||||
Reference in New Issue
Block a user