1.0.7
This commit is contained in:
1 parent
bd7b832394
commit
7643807717
86 files changed
+3644
-1385
No files matched your search
@@ -1,9 +1,10 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getEnvironments, createEnvironment, assignUserRole, getRoleByName, getEnvironmentPublicIps, setEnvironmentPublicIp, getEnvUpdateCheckSettings, getEnvironmentTimezone, type Environment } from '$lib/server/db';
|
||||
import { getEnvironments, getEnvironmentByName, createEnvironment, assignUserRole, getRoleByName, getEnvironmentPublicIps, setEnvironmentPublicIp, getEnvUpdateCheckSettings, getEnvironmentTimezone, type Environment } from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { refreshSubprocessEnvironments } from '$lib/server/subprocess-manager';
|
||||
import { serializeLabels, parseLabels, MAX_LABELS } from '$lib/utils/label-colors';
|
||||
import { cleanPem } from '$lib/utils/pem';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
@@ -69,6 +70,12 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
return json({ error: 'Name is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Check if environment with this name already exists
|
||||
const existing = await getEnvironmentByName(data.name);
|
||||
if (existing) {
|
||||
return json({ error: 'An environment with this name already exists' }, { status: 409 });
|
||||
}
|
||||
|
||||
// Host is required for direct and hawser-standard connections
|
||||
const connectionType = data.connectionType || 'socket';
|
||||
if ((connectionType === 'direct' || connectionType === 'hawser-standard') && !data.host) {
|
||||
@@ -83,9 +90,9 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
host: data.host,
|
||||
port: data.port || 2375,
|
||||
protocol: data.protocol || 'http',
|
||||
tlsCa: data.tlsCa,
|
||||
tlsCert: data.tlsCert,
|
||||
tlsKey: data.tlsKey,
|
||||
tlsCa: cleanPem(data.tlsCa),
|
||||
tlsCert: cleanPem(data.tlsCert),
|
||||
tlsKey: cleanPem(data.tlsKey),
|
||||
tlsSkipVerify: data.tlsSkipVerify || false,
|
||||
icon: data.icon || 'globe',
|
||||
socketPath: data.socketPath || '/var/run/docker.sock',
|
||||
@@ -124,7 +131,6 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
return json(env);
|
||||
} catch (error) {
|
||||
console.error('Failed to create environment:', error);
|
||||
const message = error instanceof Error ? error.message : 'Failed to create environment';
|
||||
return json({ error: message }, { status: 500 });
|
||||
return json({ error: 'Failed to create environment' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { deleteGitStackFiles } from '$lib/server/git';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { refreshSubprocessEnvironments } from '$lib/server/subprocess-manager';
|
||||
import { serializeLabels, parseLabels, MAX_LABELS } from '$lib/utils/label-colors';
|
||||
import { cleanPem } from '$lib/utils/pem';
|
||||
import { unregisterSchedule } from '$lib/server/scheduler';
|
||||
import { closeEdgeConnection } from '$lib/server/hawser';
|
||||
|
||||
@@ -62,9 +63,9 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
host: data.host,
|
||||
port: data.port,
|
||||
protocol: data.protocol,
|
||||
tlsCa: data.tlsCa,
|
||||
tlsCert: data.tlsCert,
|
||||
tlsKey: data.tlsKey,
|
||||
tlsCa: cleanPem(data.tlsCa),
|
||||
tlsCert: cleanPem(data.tlsCert),
|
||||
tlsKey: cleanPem(data.tlsKey),
|
||||
tlsSkipVerify: data.tlsSkipVerify,
|
||||
icon: data.icon,
|
||||
socketPath: data.socketPath,
|
||||
|
||||
@@ -60,50 +60,54 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
headers['X-Hawser-Token'] = config.hawserToken;
|
||||
}
|
||||
|
||||
// For HTTPS with custom CA or skip verification, use subprocess to avoid Vite dev server TLS issues
|
||||
if (protocol === 'https' && (config.tlsCa || config.tlsSkipVerify)) {
|
||||
const fs = await import('node:fs');
|
||||
let tempCaPath = '';
|
||||
// For HTTPS with custom CA, client certs, or skip verification, use subprocess to avoid Vite dev server TLS issues
|
||||
if (protocol === 'https' && (config.tlsCa || config.tlsCert || config.tlsSkipVerify)) {
|
||||
// Clean PEM content (remove extra whitespace)
|
||||
const cleanPem = (pem: string) => pem
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.join('\n');
|
||||
|
||||
// Clean the certificate - remove leading/trailing whitespace from each line
|
||||
let cleanedCa = '';
|
||||
if (config.tlsCa && !config.tlsSkipVerify) {
|
||||
cleanedCa = config.tlsCa
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.join('\n');
|
||||
|
||||
tempCaPath = `/tmp/dockhand-ca-${Date.now()}.pem`;
|
||||
fs.writeFileSync(tempCaPath, cleanedCa);
|
||||
}
|
||||
|
||||
// Build Bun script that runs outside Vite's process (Vite interferes with TLS)
|
||||
const tlsConfig = config.tlsSkipVerify
|
||||
? `tls: { rejectUnauthorized: false }`
|
||||
: `tls: { ca: await Bun.file('${tempCaPath}').text() }`;
|
||||
// Pass config as base64-encoded JSON to avoid escaping issues
|
||||
const tlsConfig = {
|
||||
url: `https://${host}:${port}/info`,
|
||||
headers,
|
||||
tlsSkipVerify: config.tlsSkipVerify || false,
|
||||
ca: config.tlsCa && !config.tlsSkipVerify ? cleanPem(config.tlsCa) : null,
|
||||
cert: config.tlsCert ? cleanPem(config.tlsCert) : null,
|
||||
key: config.tlsKey ? cleanPem(config.tlsKey) : null,
|
||||
host
|
||||
};
|
||||
const configBase64 = Buffer.from(JSON.stringify(tlsConfig)).toString('base64');
|
||||
|
||||
// Inline script with config embedded (bun -e doesn't pass argv correctly)
|
||||
const scriptContent = `
|
||||
const response = await fetch('https://${host}:${port}/info', {
|
||||
headers: ${JSON.stringify(headers)},
|
||||
${tlsConfig}
|
||||
});
|
||||
const body = await response.text();
|
||||
console.log(JSON.stringify({ status: response.status, body }));
|
||||
const config = JSON.parse(Buffer.from('${configBase64}', 'base64').toString());
|
||||
try {
|
||||
const tls = {
|
||||
sessionTimeout: 0,
|
||||
servername: config.host,
|
||||
rejectUnauthorized: !config.tlsSkipVerify
|
||||
};
|
||||
if (config.ca) tls.ca = [config.ca];
|
||||
if (config.cert) tls.cert = [config.cert];
|
||||
if (config.key) tls.key = config.key;
|
||||
const response = await fetch(config.url, {
|
||||
headers: config.headers,
|
||||
tls,
|
||||
keepalive: false
|
||||
});
|
||||
const body = await response.text();
|
||||
console.log(JSON.stringify({ status: response.status, body }));
|
||||
} catch (e) {
|
||||
console.log(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
`;
|
||||
const scriptPath = `/tmp/dockhand-test-${Date.now()}.ts`;
|
||||
fs.writeFileSync(scriptPath, scriptContent);
|
||||
|
||||
const proc = Bun.spawn(['bun', scriptPath], { stdout: 'pipe', stderr: 'pipe' });
|
||||
const proc = Bun.spawn(['bun', '-e', scriptContent], { stdout: 'pipe', stderr: 'pipe' });
|
||||
const output = await new Response(proc.stdout).text();
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
|
||||
// Cleanup temp files
|
||||
if (tempCaPath) {
|
||||
try { fs.unlinkSync(tempCaPath); } catch {}
|
||||
}
|
||||
try { fs.unlinkSync(scriptPath); } catch {}
|
||||
|
||||
if (!output.trim()) {
|
||||
throw new Error(stderr || 'Empty response from TLS test subprocess');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user