diff --git a/VERSION b/VERSION index 3a3aab6..1727484 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.0.23 +v1.0.24 diff --git a/package.json b/package.json index 3b7f4f3..f7efbb3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "dockhand", "private": true, - "version": "1.0.23", + "version": "1.0.24", "type": "module", "scripts": { "dev": "npx vite dev", diff --git a/src/lib/data/changelog.json b/src/lib/data/changelog.json index ed4d3b2..8e753f6 100644 --- a/src/lib/data/changelog.json +++ b/src/lib/data/changelog.json @@ -1,4 +1,13 @@ [ + { + "version": "1.0.24", + "date": "2026-04-03", + "changes": [ + { "type": "fix", "text": "browsing HTTP registries fails with SSL error (#868)" }, + { "type": "fix", "text": "git stack deploy options (build, re-pull, force redeploy) not persisted in edit dialog" } + ], + "imageTag": "fnsys/dockhand:v1.0.24" + }, { "version": "1.0.23", "date": "2026-04-03", diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index 2628d6f..54d1921 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -2091,6 +2091,9 @@ export async function getGitStacks(environmentId?: number): Promise { host: 'ghcr.io', path: '', fullRegistry: 'ghcr.io' } * 'registry.example.com:5000/myorg' -> { host: 'registry.example.com:5000', path: '/myorg', fullRegistry: 'registry.example.com:5000/myorg' } */ -export function parseRegistryUrl(url: string): { host: string; path: string; fullRegistry: string } { +export function parseRegistryUrl(url: string): { host: string; path: string; fullRegistry: string; protocol: string } { + // Detect protocol (default to https) + const protocol = url.startsWith('http://') ? 'http' : 'https'; // Remove protocol const withoutProtocol = url.replace(/^https?:\/\//, ''); // Remove trailing slash @@ -2633,11 +2635,11 @@ export function parseRegistryUrl(url: string): { host: string; path: string; ful // Split on first slash (after port if present) const slashIndex = trimmed.indexOf('/'); if (slashIndex === -1) { - return { host: trimmed, path: '', fullRegistry: trimmed }; + return { host: trimmed, path: '', fullRegistry: trimmed, protocol }; } const host = trimmed.substring(0, slashIndex); const path = trimmed.substring(slashIndex); // includes leading / - return { host, path, fullRegistry: trimmed }; + return { host, path, fullRegistry: trimmed, protocol }; } /** @@ -2822,7 +2824,7 @@ export async function getRegistryAuthHeader( try { // Parse URL to extract host (V2 API is always at the host root) const parsed = parseRegistryUrl(registryUrl); - const apiBaseUrl = `https://${parsed.host}`; + const apiBaseUrl = `${parsed.protocol}://${parsed.host}`; // Step 1: Challenge request to /v2/ (always at registry root, not under org path) const challengeResponse = await fetch(`${apiBaseUrl}/v2/`, { @@ -2931,7 +2933,7 @@ export async function getRegistryAuth( const parsed = parseRegistryUrl(registry.url); // V2 API endpoints are always at the registry host root - const baseUrl = `https://${parsed.host}`; + const baseUrl = `${parsed.protocol}://${parsed.host}`; // Get auth header using proper token flow const credentials = registry.username && registry.password