reach own docker daemon over tcp on socket-proxy setups; re-run host detection when an environment is added or edited; keep scanner on the host socket
This commit is contained in:
8 files changed
+277
-84
No files matched your search
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Pure host-path helpers. NO db / docker import, so unit-testable directly
|
||||
* (importing host-path.ts pulls in the DB layer and would boot the database).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether an environment can be used to inspect Dockhand's OWN container over a
|
||||
* plain tcp:// address (the socket-proxy fallback, #1203/#1204). Only plain-http
|
||||
* `direct` envs qualify: socket envs give no host:port (and we have no socket to
|
||||
* reach anyway), https/mTLS envs need certs a bare tcp:// would drop, hawser envs
|
||||
* are remote agents.
|
||||
*/
|
||||
export function isSelfInspectCandidate(env: {
|
||||
connectionType?: string | null;
|
||||
protocol?: string | null;
|
||||
host?: string | null;
|
||||
port?: number | null;
|
||||
}): boolean {
|
||||
return env.connectionType === 'direct'
|
||||
&& (env.protocol ?? 'http') === 'http'
|
||||
&& !!env.host && !!env.port;
|
||||
}
|
||||
+170
-72
@@ -22,6 +22,7 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import * as http from 'node:http';
|
||||
import { resolve } from 'node:path';
|
||||
import { isSelfInspectCandidate } from './host-path-core';
|
||||
|
||||
// Cache the host data dir to avoid repeated API calls
|
||||
let cachedHostDataDir: string | null = null;
|
||||
@@ -30,9 +31,16 @@ let detectionAttempted = false;
|
||||
// Cache ALL mounts for path translation (not just DATA_DIR)
|
||||
let cachedMounts: Array<{ source: string; destination: string }> | null = null;
|
||||
|
||||
// Cache Dockhand's own Docker access method (detected from container inspect)
|
||||
// Used by scanner to replicate how Dockhand connects to Docker
|
||||
// Cache Dockhand's own Docker access method (detected from container inspect).
|
||||
// cachedOwnDockerHost holds ONLY a DOCKER_HOST the user set on Dockhand's own
|
||||
// container. The scanner reads it to replicate how Dockhand reaches Docker, so it
|
||||
// must stay exactly what 1.0.39 exposed: null unless the user set DOCKER_HOST.
|
||||
let cachedOwnDockerHost: string | null = null;
|
||||
// cachedAutoTcpHost holds a tcp:// address DISCOVERED via a DB env (#1203, socket-
|
||||
// proxy with no docker.sock and no DOCKER_HOST). It's for self-update ONLY - the
|
||||
// scanner must NOT follow it, or it flips into TCP-mode and joins the wrong network
|
||||
// on split-network hosts (#1204). Kept separate from cachedOwnDockerHost for that.
|
||||
let cachedAutoTcpHost: string | null = null;
|
||||
let cachedOwnNetworkMode: string | null = null;
|
||||
let cachedOwnAllNetworks: string[] | null = null;
|
||||
let cachedOwnExtraHosts: string[] | null = null;
|
||||
@@ -73,6 +81,141 @@ export function getOwnContainerId(): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Docker container inspect fields we read to populate the caches. */
|
||||
interface OwnContainerInfo {
|
||||
Mounts?: Array<{ Type?: string; Source: string; Destination: string }>;
|
||||
Config?: { Env?: string[] };
|
||||
HostConfig?: { ExtraHosts?: string[] };
|
||||
NetworkSettings?: { Networks?: Record<string, unknown> };
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the mounts / DOCKER_HOST / network / ExtraHosts caches from a
|
||||
* container inspect result. Shared by the socket self-inspect and the DB-env
|
||||
* fallback so both paths cache identically. `ownDockerHostOverride` sets
|
||||
* cachedAutoTcpHost (self-update ONLY) when the inspect was reached over a DB env
|
||||
* TCP route; the socket path instead reads a real DOCKER_HOST from the container's
|
||||
* own env vars into cachedOwnDockerHost (which the scanner honors).
|
||||
*/
|
||||
function populateCachesFromInspect(info: OwnContainerInfo, ownDockerHostOverride?: string): void {
|
||||
cachedMounts = (info.Mounts || []).map(m => ({ source: m.Source, destination: m.Destination }));
|
||||
console.log(`[HostPath] Cached ${cachedMounts.length} mount(s)`);
|
||||
|
||||
if (ownDockerHostOverride) {
|
||||
// Discovered via a DB env (#1203). Self-update may use it, but the scanner
|
||||
// must NOT (it would flip to TCP-mode and hit the wrong net, #1204), so this
|
||||
// goes to the self-update-only cache, never cachedOwnDockerHost.
|
||||
cachedAutoTcpHost = ownDockerHostOverride;
|
||||
} else {
|
||||
// Read DOCKER_HOST from Dockhand's own env vars (how it was configured).
|
||||
for (const v of info.Config?.Env || []) {
|
||||
if (v.startsWith('DOCKER_HOST=')) {
|
||||
cachedOwnDockerHost = v.substring('DOCKER_HOST='.length);
|
||||
console.log(`[HostPath] Detected own DOCKER_HOST: ${cachedOwnDockerHost}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Networks: primary (custom net first, else bridge) + full list, so callers
|
||||
// can warn on fragile split-network setups (#1011).
|
||||
const networks = info.NetworkSettings?.Networks;
|
||||
if (networks) {
|
||||
const custom = Object.keys(networks).filter(n => n !== 'bridge' && n !== 'none' && n !== 'host');
|
||||
cachedOwnNetworkMode = custom.length > 0 ? custom[0] : networks.bridge ? 'bridge' : null;
|
||||
cachedOwnAllNetworks = Object.keys(networks);
|
||||
if (cachedOwnNetworkMode) {
|
||||
console.log(`[HostPath] Detected own network: ${cachedOwnNetworkMode} (all: ${cachedOwnAllNetworks.join(', ')})`);
|
||||
}
|
||||
}
|
||||
|
||||
cachedOwnExtraHosts = info.HostConfig?.ExtraHosts?.length ? [...info.HostConfig.ExtraHosts] : null;
|
||||
if (cachedOwnExtraHosts) {
|
||||
console.log(`[HostPath] Detected own ExtraHosts: ${cachedOwnExtraHosts.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the host path for DATA_DIR from a container inspect result: honor an
|
||||
* explicit HOST_DATA_DIR override, else match the DATA_DIR mount (or a parent
|
||||
* mount). Sets and returns cachedHostDataDir, or null if no mount matches.
|
||||
*/
|
||||
function resolveDataDirFromInspect(info: OwnContainerInfo, dataDir: string): string | null {
|
||||
if (cachedHostDataDir) return cachedHostDataDir;
|
||||
|
||||
const dataMount = info.Mounts?.find(m => m.Destination === dataDir);
|
||||
if (dataMount) {
|
||||
cachedHostDataDir = dataMount.Source;
|
||||
console.log(`[HostPath] Detected host path for ${dataDir}: ${cachedHostDataDir}`);
|
||||
return cachedHostDataDir;
|
||||
}
|
||||
for (const mount of info.Mounts || []) {
|
||||
if (dataDir.startsWith(mount.Destination + '/') || dataDir === mount.Destination) {
|
||||
cachedHostDataDir = mount.Source + dataDir.substring(mount.Destination.length);
|
||||
console.log(`[HostPath] Detected host path for ${dataDir} via parent mount: ${cachedHostDataDir}`);
|
||||
return cachedHostDataDir;
|
||||
}
|
||||
}
|
||||
console.warn(`[HostPath] Could not find mount for ${dataDir} in container mounts`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback for socket-proxy deployments (#1203/#1204): no docker.sock mounted and
|
||||
* no DOCKER_HOST set, so the socket self-inspect above failed. Reach Docker
|
||||
* through a DB-configured plain-http `direct` environment whose daemon actually
|
||||
* hosts THIS container (a genuinely remote daemon 404s on our id and is skipped),
|
||||
* and cache its tcp:// address + network so scanner/self-update sidecars work.
|
||||
* Returns the resolved DATA_DIR host path, or null if no env hosts us.
|
||||
*/
|
||||
async function inspectOwnContainerViaDbEnv(containerId: string, dataDir: string): Promise<string | null> {
|
||||
// Lazy import: keep host-path.ts light at module load so it stays importable
|
||||
// in unit tests without pulling in the DB/docker layers (better-sqlite3).
|
||||
const { getEnvironments } = await import('./db');
|
||||
const { dockerFetch } = await import('./docker');
|
||||
|
||||
let envs;
|
||||
try {
|
||||
envs = await getEnvironments();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const candidates = envs.filter(isSelfInspectCandidate);
|
||||
for (const env of candidates) {
|
||||
try {
|
||||
const res = await dockerFetch(`/containers/${containerId}/json`, {}, env.id);
|
||||
if (!res.ok) {
|
||||
await res.body?.cancel().catch(() => {});
|
||||
continue;
|
||||
}
|
||||
const info = await res.json() as OwnContainerInfo;
|
||||
const tcpHost = `tcp://${env.host}:${env.port}`;
|
||||
populateCachesFromInspect(info, tcpHost);
|
||||
console.log(`[HostPath] Reached own container via direct env "${env.name}"; own DOCKER_HOST=${tcpHost}`);
|
||||
return resolveDataDirFromInspect(info, dataDir);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset host detection so the next detectHostDataDir() re-inspects. Called when
|
||||
* environments change (create/update), so a socket-proxy deployment that had no
|
||||
* `direct` env at boot picks one up without a restart (#1203).
|
||||
*/
|
||||
export function resetHostDetection(): void {
|
||||
detectionAttempted = false;
|
||||
cachedHostDataDir = null;
|
||||
cachedMounts = null;
|
||||
cachedOwnDockerHost = null;
|
||||
cachedAutoTcpHost = null;
|
||||
cachedOwnNetworkMode = null;
|
||||
cachedOwnAllNetworks = null;
|
||||
cachedOwnExtraHosts = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host path for our DATA_DIR mount by inspecting our own container
|
||||
*/
|
||||
@@ -149,77 +292,23 @@ export async function detectHostDataDir(): Promise<string | null> {
|
||||
};
|
||||
};
|
||||
|
||||
// Cache ALL mounts for later path translation (used by rewriteComposeVolumePaths)
|
||||
cachedMounts = (containerInfo.Mounts || []).map(m => ({
|
||||
source: m.Source,
|
||||
destination: m.Destination
|
||||
}));
|
||||
console.log(`[HostPath] Cached ${cachedMounts.length} mount(s)`);
|
||||
|
||||
// Cache DOCKER_HOST from Dockhand's own env vars (if set)
|
||||
// This tells us how Dockhand was configured to reach Docker
|
||||
const envVars = containerInfo.Config?.Env || [];
|
||||
for (const v of envVars) {
|
||||
if (v.startsWith('DOCKER_HOST=')) {
|
||||
cachedOwnDockerHost = v.substring('DOCKER_HOST='.length);
|
||||
console.log(`[HostPath] Detected own DOCKER_HOST: ${cachedOwnDockerHost}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache Dockhand's networks. Picks one as the primary networkMode
|
||||
// (custom net first, falling back to bridge) and keeps the full list
|
||||
// so callers can warn when a setup is fragile — e.g. socket-proxy
|
||||
// living on a network other than the one the scanner joins (#1011).
|
||||
const networks = containerInfo.NetworkSettings?.Networks;
|
||||
if (networks) {
|
||||
const custom = Object.keys(networks).filter(
|
||||
n => n !== 'bridge' && n !== 'none' && n !== 'host'
|
||||
);
|
||||
cachedOwnNetworkMode = custom.length > 0 ? custom[0]
|
||||
: networks.bridge ? 'bridge' : null;
|
||||
cachedOwnAllNetworks = Object.keys(networks);
|
||||
if (cachedOwnNetworkMode) {
|
||||
console.log(`[HostPath] Detected own network: ${cachedOwnNetworkMode} (all: ${cachedOwnAllNetworks.join(', ')})`);
|
||||
}
|
||||
}
|
||||
|
||||
cachedOwnExtraHosts = containerInfo.HostConfig?.ExtraHosts?.length
|
||||
? [...containerInfo.HostConfig.ExtraHosts]
|
||||
: null;
|
||||
if (cachedOwnExtraHosts) {
|
||||
console.log(`[HostPath] Detected own ExtraHosts: ${cachedOwnExtraHosts.join(', ')}`);
|
||||
}
|
||||
|
||||
// Explicit override wins for DATA_DIR path, but we still inspect to populate
|
||||
// mounts/network/DOCKER_HOST/ExtraHosts caches for sibling sidecars.
|
||||
if (cachedHostDataDir) {
|
||||
return cachedHostDataDir;
|
||||
}
|
||||
|
||||
// Find the mount for our DATA_DIR
|
||||
const dataMount = containerInfo.Mounts?.find(m => m.Destination === dataDir);
|
||||
|
||||
if (dataMount) {
|
||||
cachedHostDataDir = dataMount.Source;
|
||||
console.log(`[HostPath] Detected host path for ${dataDir}: ${cachedHostDataDir}`);
|
||||
return cachedHostDataDir;
|
||||
}
|
||||
|
||||
// Check if DATA_DIR is a subdirectory of a mount
|
||||
for (const mount of containerInfo.Mounts || []) {
|
||||
if (dataDir.startsWith(mount.Destination + '/') || dataDir === mount.Destination) {
|
||||
const relativePath = dataDir.substring(mount.Destination.length);
|
||||
cachedHostDataDir = mount.Source + relativePath;
|
||||
console.log(`[HostPath] Detected host path for ${dataDir} via parent mount: ${cachedHostDataDir}`);
|
||||
return cachedHostDataDir;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(`[HostPath] Could not find mount for ${dataDir} in container mounts`);
|
||||
return null;
|
||||
// Populate mounts/DOCKER_HOST/network/ExtraHosts caches (from the container's
|
||||
// own env vars, since this path reached Docker over socket/DOCKER_HOST).
|
||||
populateCachesFromInspect(containerInfo);
|
||||
return resolveDataDirFromInspect(containerInfo, dataDir);
|
||||
} catch (err) {
|
||||
console.warn(`[HostPath] Failed to query Docker API: ${err}`);
|
||||
console.warn(`[HostPath] Failed to query Docker API via socket/DOCKER_HOST: ${err}`);
|
||||
// Socket-proxy fallback (#1203/#1204): no docker.sock and no DOCKER_HOST, so
|
||||
// reach Docker through a configured plain-http direct env. Guarded on no
|
||||
// DOCKER_HOST so a user who set an (unreachable) DOCKER_HOST is never rerouted.
|
||||
if (!process.env.DOCKER_HOST) {
|
||||
const viaEnv = await inspectOwnContainerViaDbEnv(containerId, dataDir);
|
||||
if (viaEnv) return viaEnv;
|
||||
}
|
||||
// Nothing found. Do NOT keep detectionAttempted latched: a direct env added
|
||||
// AFTER startup should heal on the next detect without a restart. Only a
|
||||
// successful detection (above / socket path) leaves the caches populated.
|
||||
detectionAttempted = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -240,6 +329,15 @@ export function getOwnDockerHost(): string | null {
|
||||
return cachedOwnDockerHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP address discovered via a DB env when Dockhand has no docker.sock and no
|
||||
* DOCKER_HOST (#1203 socket-proxy). For self-update ONLY - the scanner deliberately
|
||||
* does not read this, so it keeps using the host socket like 1.0.39 did (#1204).
|
||||
*/
|
||||
export function getAutoDetectedDockerHost(): string | null {
|
||||
return cachedAutoTcpHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Docker network Dockhand is attached to.
|
||||
* Used to place scanner containers on the same network so they can reach
|
||||
|
||||
@@ -5,6 +5,7 @@ import { authorize } from '$lib/server/authorize';
|
||||
import { auditEnvironment } from '$lib/server/audit';
|
||||
import { invalidateTokenCacheForUser } from '$lib/server/api-tokens';
|
||||
import { refreshSubprocessEnvironments } from '$lib/server/subprocess-manager';
|
||||
import { resetHostDetection, detectHostDataDir } from '$lib/server/host-path';
|
||||
import { serializeLabels, parseLabels, MAX_LABELS } from '$lib/utils/label-colors';
|
||||
import { cleanPem } from '$lib/utils/pem';
|
||||
import { validateEnvName } from '$lib/utils/env-name';
|
||||
@@ -125,6 +126,11 @@ export const POST: RequestHandler = async (event) => {
|
||||
// Notify event collectors to pick up the new environment
|
||||
refreshSubprocessEnvironments();
|
||||
|
||||
// Re-run host detection: a socket-proxy deployment with no direct env at boot
|
||||
// can now reach Docker through this env without a restart (#1203). Fire-and-forget.
|
||||
resetHostDetection();
|
||||
void detectHostDataDir();
|
||||
|
||||
// Auto-assign Admin role to creator (Enterprise only)
|
||||
if (auth.isEnterprise && auth.authEnabled && auth.isAuthenticated && !auth.isAdmin) {
|
||||
const user = auth.user;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getStacksDir } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditEnvironment } from '$lib/server/audit';
|
||||
import { refreshSubprocessEnvironments } from '$lib/server/subprocess-manager';
|
||||
import { resetHostDetection, detectHostDataDir } from '$lib/server/host-path';
|
||||
import { serializeLabels, parseLabels, MAX_LABELS } from '$lib/utils/label-colors';
|
||||
import { cleanPem } from '$lib/utils/pem';
|
||||
import { validateEnvName } from '$lib/utils/env-name';
|
||||
@@ -155,6 +156,11 @@ export const PUT: RequestHandler = async (event) => {
|
||||
return json({ error: 'Environment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Re-run host detection: editing an env (e.g. pointing it at a socket proxy)
|
||||
// lets a socketless deployment reach Docker without a restart (#1203).
|
||||
resetHostDetection();
|
||||
void detectHostDataDir();
|
||||
|
||||
// Notify event collectors if collectActivity or collectMetrics setting changed
|
||||
if (data.collectActivity !== undefined || data.collectMetrics !== undefined) {
|
||||
refreshSubprocessEnvironments();
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getOwnContainerId,
|
||||
getHostDockerSocket,
|
||||
getOwnDockerHost,
|
||||
getAutoDetectedDockerHost,
|
||||
getOwnExtraHosts,
|
||||
getOwnNetworkMode
|
||||
} from '$lib/server/host-path';
|
||||
@@ -17,7 +18,7 @@ const UPDATER_LABEL = 'dockhand.updater';
|
||||
|
||||
/** Get TCP Docker host if configured, null otherwise. */
|
||||
function getDockerTcpHost(): string | null {
|
||||
const dockerHost = process.env.DOCKER_HOST || getOwnDockerHost();
|
||||
const dockerHost = process.env.DOCKER_HOST || getOwnDockerHost() || getAutoDetectedDockerHost();
|
||||
return dockerHost?.startsWith('tcp://') ? dockerHost : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,59 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { getOwnContainerId, getOwnDockerHost } from '$lib/server/host-path';
|
||||
import { getRegistryManifestDigest, unixSocketRequest } from '$lib/server/docker';
|
||||
import { getOwnContainerId, getOwnDockerHost, getAutoDetectedDockerHost } from '$lib/server/host-path';
|
||||
import { getRegistryManifestDigest, unixSocketRequest, dockerFetch } from '$lib/server/docker';
|
||||
import { getEnvironments } from '$lib/server/db';
|
||||
import { compareVersions } from '$lib/utils/version';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
/** Fetch from the local Docker directly (not through environment routing) */
|
||||
function localDockerFetch(path: string, options: RequestInit = {}): Promise<Response> {
|
||||
const dockerHost = process.env.DOCKER_HOST || getOwnDockerHost();
|
||||
/**
|
||||
* When there is no local socket and no DOCKER_HOST (e.g. a socket-proxy setup
|
||||
* that mounts no docker.sock, #1203), find the environment whose daemon actually
|
||||
* runs the Dockhand container by inspecting our own container ID on each
|
||||
* candidate. This is deterministic even with several `direct` envs, one of which
|
||||
* is a genuinely remote host - the remote daemon returns 404 for our ID, so we
|
||||
* never pick it. Memoized: the answer is stable for the process lifetime.
|
||||
* Returns the env id, or null if none host us.
|
||||
*/
|
||||
let ownEnvIdMemo: number | null | undefined;
|
||||
async function resolveOwnEnvId(containerId: string): Promise<number | null> {
|
||||
if (ownEnvIdMemo !== undefined) return ownEnvIdMemo;
|
||||
|
||||
const envs = await getEnvironments();
|
||||
// Socket/local envs first (cheapest, almost always us), then direct.
|
||||
const candidates = [
|
||||
...envs.filter((e) => e.connectionType === 'socket' || !e.connectionType),
|
||||
...envs.filter((e) => e.connectionType === 'direct')
|
||||
];
|
||||
for (const env of candidates) {
|
||||
try {
|
||||
const res = await dockerFetch(`/containers/${containerId}/json`, {}, env.id);
|
||||
if (res.ok) {
|
||||
console.log(`[SelfUpdate] Dockhand runs on environment "${env.name}" (id ${env.id}, ${env.connectionType || 'socket'}); using it for update checks`);
|
||||
ownEnvIdMemo = env.id;
|
||||
return env.id;
|
||||
}
|
||||
} catch {
|
||||
// Env unreachable or does not host us; try the next candidate.
|
||||
}
|
||||
}
|
||||
console.log('[SelfUpdate] No configured environment hosts the Dockhand container; cannot reach Docker for update check');
|
||||
ownEnvIdMemo = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch from the Docker daemon running Dockhand itself (not via env routing,
|
||||
* which fails on private-registry images - see the private-registry fix).
|
||||
*
|
||||
* Order: explicit DOCKER_HOST tcp -> local socket if present -> the environment
|
||||
* that actually hosts our own container. The last path covers socket-proxy
|
||||
* setups with no docker.sock mount and no DOCKER_HOST (#1203), so the user does
|
||||
* not have to set DOCKER_HOST (which breaks scanner networking, #1204).
|
||||
*/
|
||||
async function localDockerFetch(path: string, options: RequestInit = {}): Promise<Response> {
|
||||
const dockerHost = process.env.DOCKER_HOST || getOwnDockerHost() || getAutoDetectedDockerHost();
|
||||
|
||||
if (dockerHost?.startsWith('tcp://')) {
|
||||
// TCP connection (socat proxy, socket-proxy, remote Docker)
|
||||
@@ -15,8 +61,21 @@ function localDockerFetch(path: string, options: RequestInit = {}): Promise<Resp
|
||||
return fetch(url, options);
|
||||
}
|
||||
|
||||
// Unix socket (default)
|
||||
const socketPath = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
|
||||
if (existsSync(socketPath)) {
|
||||
return unixSocketRequest(socketPath, path, options);
|
||||
}
|
||||
|
||||
const containerId = getOwnContainerId();
|
||||
if (containerId) {
|
||||
const ownEnvId = await resolveOwnEnvId(containerId);
|
||||
if (ownEnvId !== null) {
|
||||
return dockerFetch(path, options, ownEnvId);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing usable: fall through to the socket path so the caller gets the
|
||||
// original ENOENT rather than a silent success.
|
||||
return unixSocketRequest(socketPath, path, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { getOwnDockerHost } from '$lib/server/host-path';
|
||||
import { getOwnDockerHost, getAutoDetectedDockerHost } from '$lib/server/host-path';
|
||||
import { unixSocketRequest } from '$lib/server/docker';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
/** Fetch from the local Docker directly. Supports TCP and Unix socket. */
|
||||
function localDockerFetch(path: string): Promise<Response> {
|
||||
const dockerHost = process.env.DOCKER_HOST || getOwnDockerHost();
|
||||
const dockerHost = process.env.DOCKER_HOST || getOwnDockerHost() || getAutoDetectedDockerHost();
|
||||
if (dockerHost?.startsWith('tcp://')) {
|
||||
return fetch(dockerHost.replace('tcp://', 'http://') + path);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Box, Images, HardDrive, Network, Cpu, Server, Crown, Building2, Layers, Clock, Code, Package, ExternalLink, Search, FileText, Tag, Sparkles, Bug, ChevronDown, ChevronRight, Plug, ScrollText, Shield, MessageSquarePlus, GitBranch, Coffee, Monitor, Cog, MemoryStick, Database, CircleArrowUp, Loader2, CheckCircle2 } from 'lucide-svelte';
|
||||
import { Box, Images, HardDrive, Network, Cpu, Server, Crown, Building2, Layers, Clock, Code, Package, ExternalLink, Search, FileText, Tag, Sparkles, Bug, ChevronDown, ChevronRight, Plug, ScrollText, Shield, MessageSquarePlus, GitBranch, Coffee, Monitor, Cog, MemoryStick, Database, CircleArrowUp, Loader2, CheckCircle2, TriangleAlert } from 'lucide-svelte';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { licenseStore } from '$lib/stores/license';
|
||||
@@ -512,8 +512,9 @@
|
||||
Up to date
|
||||
</button>
|
||||
{:else if updateCheckError}
|
||||
<button class="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors" onclick={checkForUpdates} title={updateCheckError}>
|
||||
Check for updates
|
||||
<button class="flex items-center gap-1 text-xs text-foreground hover:text-muted-foreground transition-colors" onclick={checkForUpdates} title={updateCheckError}>
|
||||
<TriangleAlert class="w-3.5 h-3.5 shrink-0" />
|
||||
Update check unavailable — click to retry
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user