exclude docker sockets and host system paths from volume backups

This commit is contained in:
jarek committed 2026-07-31 10:10:55 +02:00
1 parent 36dac8b030
commit 25f10616a5
4 files changed
+76 -9

No files matched your search

+10 -2
View File
@@ -4,6 +4,8 @@
* unit-testable under `bun test`.
*/
import { isUnbackupableBindSource } from './unbackupable-mounts';
export interface VolumeInfo {
name: string;
mountPoint: string;
@@ -11,6 +13,10 @@ export interface VolumeInfo {
/** Bind mounts only: the host source path, for display (name is the container
* destination so it matches the backup engine's filter). */
source?: string;
/** Bind mounts only: true when the host source is a socket or host system path
* the backup engine refuses to capture. Shown in the picker but not selectable,
* and excluded from "backup all" — mirrors the engine's isUnbackupableBindSource. */
unbackupable?: boolean;
}
/** A raw Docker mount as returned by the containers API (list or inspect).
@@ -44,7 +50,9 @@ export function volumeInfoFromBind(bind: { hostPath: string; containerPath: stri
return {
name: mountType === 'bind' ? bind.containerPath : bind.hostPath,
mountPoint: bind.containerPath,
mountType
mountType,
source: mountType === 'bind' ? bind.hostPath : undefined,
unbackupable: mountType === 'bind' ? isUnbackupableBindSource(bind.hostPath) || undefined : undefined
};
}
@@ -64,7 +72,7 @@ export function normalizeMounts(mounts: RawMount[] | null | undefined): VolumeIn
const destination = m.destination || m.Destination || '';
const source = m.source || m.Source || '';
if (type === 'bind') {
out.push({ name: destination || source, mountPoint: destination, mountType: 'bind', source: source || undefined });
out.push({ name: destination || source, mountPoint: destination, mountType: 'bind', source: source || undefined, unbackupable: isUnbackupableBindSource(source) || undefined });
} else {
const name = m.name || m.Name || source || destination || '';
out.push({ name, mountPoint: destination, mountType: 'volume' });
+42
View File
@@ -0,0 +1,42 @@
/**
* Bind mounts that must never be backed up: Docker/containerd sockets and host
* system paths that are infrastructure, not data. Backing up a socket is
* meaningless (it's a device node, not files) and a "backup all" that captures
* /proc or /var/run is pointless and slow.
*
* Dependency-free so BOTH the backup engine (discovery-core.ts, which enumerates
* mounts itself for "backup all") and the UI picker (utils/mounts.ts) can share
* the exact same rule with no drift.
*
* Matches on the HOST SOURCE path of a bind mount, not the container destination
* (the source is what identifies "this is the host's docker socket", regardless
* of where a container maps it).
*/
/** Exact host source paths (or their children) that are never backed up. */
const UNBACKUPABLE_PREFIXES = [
'/proc',
'/sys',
'/dev',
'/var/run',
'/run',
];
/** Exact socket files, matched anywhere, in case they live outside the prefixes
* above (e.g. rootless docker under /var/run/user/<uid>, already covered by
* /var/run, but a socket bound from an unusual path still gets caught here). */
const UNBACKUPABLE_SOCKET_BASENAMES = ['docker.sock', 'containerd.sock'];
/** True if a bind mount's host SOURCE is infrastructure that must not be backed
* up (a socket or a host system path). Only applies to bind mounts — named
* volumes are always data. */
export function isUnbackupableBindSource(hostSource: string | undefined | null): boolean {
if (!hostSource || !hostSource.startsWith('/')) return false;
const path = hostSource.replace(/\/+$/, ''); // drop trailing slash
const base = path.slice(path.lastIndexOf('/') + 1);
if (UNBACKUPABLE_SOCKET_BASENAMES.includes(base)) return true;
for (const prefix of UNBACKUPABLE_PREFIXES) {
if (path === prefix || path.startsWith(prefix + '/')) return true;
}
return false;
}