Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions src/lib/server/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as tls from 'node:tls';
import { createHash } from 'node:crypto';
import type { Environment } from './db';
import { getStackEnvVarsAsRecord } from './db';
import { getAdditionalVolumeBinds } from './mount-dedupe';
import { isSystemContainer } from './scheduler/tasks/update-utils';
import { deepDiff } from '../utils/diff.js';

Expand Down Expand Up @@ -1866,20 +1867,7 @@ export async function recreateContainerFromInspect(
}
}

// Preserve anonymous volumes from Mounts not in HostConfig.Binds
const existingBinds = new Set((hostConfig.Binds || []).map((b: string) => {
const parts = b.split(':');
return parts.length >= 2 ? parts[1] : parts[0];
}));
const mounts = inspectData.Mounts || [];
const additionalBinds: string[] = [];
for (const mount of mounts) {
if (mount.Type === 'volume' && mount.Name && mount.Destination) {
if (!existingBinds.has(mount.Destination)) {
additionalBinds.push(`${mount.Name}:${mount.Destination}`);
}
}
}
const additionalBinds = getAdditionalVolumeBinds(hostConfig, inspectData.Mounts || []);
if (additionalBinds.length > 0) {
createConfig.HostConfig = {
...hostConfig,
Expand Down
33 changes: 33 additions & 0 deletions src/lib/server/mount-dedupe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { getAdditionalVolumeBinds } from './mount-dedupe';

describe('getAdditionalVolumeBinds', () => {
it('skips volume mounts when the target already exists in HostConfig.Mounts', () => {
const additionalBinds = getAdditionalVolumeBinds(
{
Binds: ['/volume1/backups:/backup'],
Mounts: [{ Target: '/data' }]
},
[
{ Type: 'volume', Name: 'docsight_docsis_data', Destination: '/data' },
{ Type: 'bind', Name: 'ignored', Destination: '/backup' }
]
);

assert.deepEqual(additionalBinds, []);
});

it('adds volume mounts that are missing from HostConfig', () => {
const additionalBinds = getAdditionalVolumeBinds(
{
Binds: ['/volume1/backups:/backup'],
Mounts: []
},
[{ Type: 'volume', Name: 'docsight_docsis_data', Destination: '/data' }]
);

assert.deepEqual(additionalBinds, ['docsight_docsis_data:/data']);
});
});
36 changes: 36 additions & 0 deletions src/lib/server/mount-dedupe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type HostConfigLike = {
Binds?: string[] | null;
Mounts?: Array<{ Target?: string | null }> | null;
};

type InspectMountLike = {
Type?: string | null;
Name?: string | null;
Destination?: string | null;
};

/** Build extra bind strings for volume mounts missing from HostConfig. */
export function getAdditionalVolumeBinds(
hostConfig: HostConfigLike,
mounts: InspectMountLike[]
): string[] {
const existingMountTargets = new Set((hostConfig.Binds || []).map((bind: string) => {
const parts = bind.split(':');
return parts.length >= 2 ? parts[1] : parts[0];
}));

for (const mount of hostConfig.Mounts || []) {
if (mount?.Target) existingMountTargets.add(mount.Target);
}

const additionalBinds: string[] = [];
for (const mount of mounts || []) {
if (mount.Type === 'volume' && mount.Name && mount.Destination) {
if (!existingMountTargets.has(mount.Destination)) {
additionalBinds.push(`${mount.Name}:${mount.Destination}`);
}
}
}

return additionalBinds;
}
16 changes: 2 additions & 14 deletions src/routes/api/self-update/+server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { json } from '@sveltejs/kit';
import { authorize } from '$lib/server/authorize';
import { getAdditionalVolumeBinds } from '$lib/server/mount-dedupe';
import { getOwnContainerId, getHostDockerSocket, getOwnDockerHost, getOwnNetworkMode } from '$lib/server/host-path';
import { buildRegistryAuthHeader, unixSocketRequest, unixSocketStreamRequest } from '$lib/server/docker';
import type { RequestHandler } from './$types';
Expand Down Expand Up @@ -160,20 +161,7 @@ function buildCreateConfig(inspectData: any, newImage: string): any {
// Otherwise the old container's hostname is inherited, breaking self-identification
delete createConfig.Hostname;

// Preserve anonymous volumes from Mounts not in HostConfig.Binds
const existingBinds = new Set((hostConfig.Binds || []).map((b: string) => {
const parts = b.split(':');
return parts.length >= 2 ? parts[1] : parts[0];
}));
const mounts = inspectData.Mounts || [];
const additionalBinds: string[] = [];
for (const mount of mounts) {
if (mount.Type === 'volume' && mount.Name && mount.Destination) {
if (!existingBinds.has(mount.Destination)) {
additionalBinds.push(`${mount.Name}:${mount.Destination}`);
}
}
}
const additionalBinds = getAdditionalVolumeBinds(hostConfig, inspectData.Mounts || []);
if (additionalBinds.length > 0) {
createConfig.HostConfig = {
...createConfig.HostConfig,
Expand Down