diff --git a/api/backups.ts b/api/backups.ts index 851bb7b..629a4cb 100644 --- a/api/backups.ts +++ b/api/backups.ts @@ -113,16 +113,19 @@ export function checkHost(host: string): string { } /** - * An absolute path on the far side. + * A directory on the far side, absolute or relative. * - * This one IS interpreted by the remote shell — `ssh host mkdir -p /path` runs through it — so the - * characters a shell would act on are refused rather than escaped. Refusing is checkable by reading it; - * escaping is checkable by being right about every shell. + * RELATIVE IS NORMAL, and requiring a leading slash was simply wrong. `scp file host:db-backups/fofos/` + * lands under the remote account's home, which is what every ssh tool does and what anybody typing a + * destination expects — the home directory is the one place they are certain to be able to write. + * + * What is still refused: the characters a shell would act on. This value IS interpreted remotely — + * `ssh host mkdir -p ` runs through a shell — so they are rejected rather than escaped. Refusing + * is checkable by reading this function; escaping is checkable only by being right about every shell. */ export function checkRemotePath(path: string): string { const trimmed = path.trim(); if (!trimmed) throw new Error('A remote directory is required.'); - if (!trimmed.startsWith('/')) throw new Error('The remote directory must be an absolute path.'); if (/["'`$;&|<>()\\\s]/.test(trimmed)) { throw new Error('The remote directory may not contain spaces, quotes or shell characters.'); } diff --git a/web/BackupsRow.tsx b/web/BackupsRow.tsx index a2674b7..1db5095 100644 --- a/web/BackupsRow.tsx +++ b/web/BackupsRow.tsx @@ -175,7 +175,7 @@ export const BackupsRow = () => { {configured && settings.sshHost ? ( {' '} - → {settings.sshHost}:{settings.sshPath} + → {settings.sshHost}:{settings.sshPath?.startsWith('/') ? settings.sshPath : `~/${settings.sshPath}`} ) : null}

@@ -278,7 +278,7 @@ export const BackupsRow = () => { setSshPath(event.target.value)} - placeholder="/srv/backups/fofos" + placeholder="db-backups/fofos" className="w-full font-mono text-xs" autoComplete="off" spellCheck={false} @@ -296,7 +296,10 @@ export const BackupsRow = () => { {/* No password field, and there will not be one: this runs with nobody at a terminal, so it authenticates with the owner's SSH key and stores no credential of its own. */} -

Authenticated with this machine’s SSH key.

+

+ Authenticated with this machine’s SSH key. A path without a leading slash is relative to + the remote home. +