This commit is contained in:
jarek committed 2026-03-02 07:59:58 +01:00
1 parent 4b430340db
commit 9c451aedf9
120 files changed
+7777 -6046

No files matched your search

+19 -15
View File
@@ -1,22 +1,22 @@
import { json } from '@sveltejs/kit';
import { authorize } from '$lib/server/authorize';
import { getOwnContainerId, getHostDockerSocket } from '$lib/server/host-path';
import { buildRegistryAuthHeader, unixSocketRequest, unixSocketStreamRequest } from '$lib/server/docker';
import type { RequestHandler } from './$types';
import { prefersJSON, sseToJSON } from '$lib/server/sse';
const UPDATER_IMAGE = 'fnsys/dockhand-updater:latest';
const UPDATER_LABEL = 'dockhand.updater';
const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
/**
* Fetch from the local Docker socket directly.
* Self-update always operates on the local engine — no environment routing needed.
*/
async function localDockerFetch(path: string, options: RequestInit = {}): Promise<Response> {
const socketPath = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
return fetch(`http://localhost${path}`, {
...options,
// @ts-ignore - Bun supports unix sockets
unix: socketPath
});
/** Fetch from the local Docker socket (buffered). */
function localDockerFetch(path: string, options: RequestInit = {}): Promise<Response> {
return unixSocketRequest(DOCKER_SOCKET, path, options);
}
/** Fetch from the local Docker socket (streaming body for pull progress). */
function localDockerStreamFetch(path: string, options: RequestInit = {}): Promise<Response> {
return unixSocketStreamRequest(DOCKER_SOCKET, path, options);
}
/**
@@ -34,9 +34,10 @@ async function pullImageLocal(imageName: string, onProgress?: (line: string) =>
}
}
const response = await localDockerFetch(
const authHeaders = await buildRegistryAuthHeader(imageName);
const response = await localDockerStreamFetch(
`/images/create?fromImage=${encodeURIComponent(fromImage)}&tag=${encodeURIComponent(tag)}`,
{ method: 'POST' }
{ method: 'POST', headers: authHeaders }
);
if (!response.ok) {
@@ -400,11 +401,14 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
}
});
return new Response(stream, {
const sseResponse = new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no'
}
});
if (prefersJSON(request)) return sseToJSON(sseResponse);
return sseResponse;
};
+96 -11
View File
@@ -1,19 +1,15 @@
import { json } from '@sveltejs/kit';
import { authorize } from '$lib/server/authorize';
import { getOwnContainerId } from '$lib/server/host-path';
import { getRegistryManifestDigest } from '$lib/server/docker';
import { getRegistryManifestDigest, unixSocketRequest } from '$lib/server/docker';
import { compareVersions } from '$lib/utils/version';
import type { RequestHandler } from './$types';
/**
* Fetch from the local Docker socket directly (not through environment routing)
*/
async function localDockerFetch(path: string, options: RequestInit = {}): Promise<Response> {
const socketPath = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
return fetch(`http://localhost${path}`, {
...options,
// @ts-ignore - Bun supports unix sockets
unix: socketPath
});
const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
/** Fetch from the local Docker socket directly (not through environment routing) */
function localDockerFetch(path: string, options: RequestInit = {}): Promise<Response> {
return unixSocketRequest(DOCKER_SOCKET, path, options);
}
/**
@@ -78,6 +74,92 @@ export const GET: RequestHandler = async ({ cookies }) => {
});
}
// Extract tag from image name
const colonIdx = currentImage.lastIndexOf(':');
const tag = colonIdx > -1 ? currentImage.substring(colonIdx + 1) : 'latest';
const imageWithoutTag = colonIdx > -1 ? currentImage.substring(0, colonIdx) : currentImage;
// Check if this is a versioned tag (e.g., v1.0.18, 1.0.18, v1.0.18-baseline)
const versionMatch = tag.match(/^(v?\d+\.\d+\.\d+)(-baseline)?$/);
if (versionMatch) {
// Version-based check: compare against latest released version from changelog
const currentTagVersion = versionMatch[1];
const suffix = versionMatch[2] || ''; // '-baseline' or ''
try {
const changelogResponse = await fetch(
'https://raw.githubusercontent.com/Finsys/dockhand/main/src/lib/data/changelog.json',
{ signal: AbortSignal.timeout(5000) }
);
if (!changelogResponse.ok) {
return json({
updateAvailable: false,
currentImage,
containerName,
isComposeManaged,
error: 'Could not fetch changelog from GitHub'
});
}
const changelog = await changelogResponse.json() as Array<{
version: string;
comingSoon?: boolean;
date?: string;
changes?: Array<{ type: string; text: string }>;
}>;
// Find latest released version (first entry without comingSoon)
const latestRelease = changelog.find(entry => !entry.comingSoon);
if (!latestRelease) {
return json({
updateAvailable: false,
currentImage,
containerName,
isComposeManaged,
error: 'No released version found in changelog'
});
}
const latestVersion = latestRelease.version;
const hasNewer = compareVersions(latestVersion, currentTagVersion) > 0;
if (hasNewer) {
// Build new image tag preserving registry prefix and suffix
const newTag = `v${latestVersion.replace(/^v/, '')}${suffix}`;
const newImage = `${imageWithoutTag}:${newTag}`;
return json({
updateAvailable: true,
currentImage,
newImage,
latestVersion: latestVersion.replace(/^v/, ''),
containerName,
isComposeManaged
});
}
return json({
updateAvailable: false,
currentImage,
containerName,
isComposeManaged
});
} catch (err) {
return json({
updateAvailable: false,
currentImage,
containerName,
isComposeManaged,
error: 'Version check failed: ' + String(err)
});
}
}
// Digest-based check for mutable tags (:latest, :baseline, etc.)
// Inspect image via local Docker socket to get RepoDigests
const imageResponse = await localDockerFetch(`/images/${encodeURIComponent(currentImageId)}/json`);
if (!imageResponse.ok) {
@@ -105,6 +187,7 @@ export const GET: RequestHandler = async ({ cookies }) => {
return json({
updateAvailable: false,
currentImage,
newImage: currentImage,
containerName,
isComposeManaged,
isLocalImage: true
@@ -117,6 +200,7 @@ export const GET: RequestHandler = async ({ cookies }) => {
return json({
updateAvailable: false,
currentImage,
newImage: currentImage,
containerName,
isComposeManaged,
error: 'Could not query registry'
@@ -128,6 +212,7 @@ export const GET: RequestHandler = async ({ cookies }) => {
return json({
updateAvailable: hasUpdate,
currentImage,
newImage: currentImage,
currentDigest: localDigests[0],
newDigest: registryDigest,
containerName,
@@ -1,16 +1,13 @@
import { json } from '@sveltejs/kit';
import { authorize } from '$lib/server/authorize';
import { unixSocketRequest } from '$lib/server/docker';
import type { RequestHandler } from './$types';
/**
* Fetch from the local Docker socket directly
*/
async function localDockerFetch(path: string): Promise<Response> {
const socketPath = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
return fetch(`http://localhost${path}`, {
// @ts-ignore - Bun supports unix sockets
unix: socketPath
});
const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
/** Fetch from the local Docker socket directly */
function localDockerFetch(path: string): Promise<Response> {
return unixSocketRequest(DOCKER_SOCKET, path);
}
/**