mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
import fs from 'fs';
|
|
import { Tunnel, bin, install } from 'cloudflared';
|
|
|
|
import { logError } from '@/utils/node/logging';
|
|
import { sendKoboldOutput, sendToRenderer } from '../window';
|
|
import { PROXY } from '@/constants/proxy';
|
|
import { SILLYTAVERN, OPENWEBUI } from '@/constants';
|
|
import type { FrontendPreference } from '@/types';
|
|
|
|
let activeTunnel: Tunnel | null = null;
|
|
let tunnelUrl: string | null = null;
|
|
|
|
const getTunnelTarget = (frontendPreference: FrontendPreference) => {
|
|
switch (frontendPreference) {
|
|
case 'sillytavern':
|
|
return `http://${SILLYTAVERN.HOST}:${SILLYTAVERN.PROXY_PORT}`;
|
|
case 'openwebui':
|
|
return `http://${OPENWEBUI.HOST}:${OPENWEBUI.PORT}`;
|
|
default:
|
|
return `http://${PROXY.HOST}:${PROXY.PORT}`;
|
|
}
|
|
};
|
|
|
|
export const startTunnel = async (
|
|
frontendPreference: FrontendPreference = 'koboldcpp'
|
|
) => {
|
|
if (activeTunnel) {
|
|
return tunnelUrl;
|
|
}
|
|
|
|
try {
|
|
sendKoboldOutput('Starting Cloudflare tunnel...');
|
|
|
|
if (!fs.existsSync(bin)) {
|
|
sendKoboldOutput('Installing cloudflared binary...');
|
|
await install(bin);
|
|
sendKoboldOutput('cloudflared binary installed');
|
|
}
|
|
|
|
const tunnelTarget = getTunnelTarget(frontendPreference);
|
|
const tunnel = Tunnel.quick(tunnelTarget, { '--no-autoupdate': true });
|
|
activeTunnel = tunnel;
|
|
|
|
let rateLimited = false;
|
|
|
|
tunnel.on('stderr', (data: string) => {
|
|
if (data.includes('429') || data.includes('Too Many Requests')) {
|
|
rateLimited = true;
|
|
}
|
|
});
|
|
|
|
const url = await new Promise<string>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
const message = rateLimited
|
|
? 'Cloudflare rate limit exceeded. Please wait a few minutes and try again.'
|
|
: 'Tunnel connection timed out';
|
|
reject(new Error(message));
|
|
}, 30000);
|
|
|
|
tunnel.once('url', (url) => {
|
|
clearTimeout(timeout);
|
|
resolve(url);
|
|
});
|
|
|
|
tunnel.once('error', (error) => {
|
|
clearTimeout(timeout);
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
tunnelUrl = url;
|
|
sendKoboldOutput(`Tunnel ready at ${tunnelUrl}`);
|
|
sendToRenderer('tunnel-url-changed', tunnelUrl);
|
|
|
|
tunnel.on('error', (error: Error) => {
|
|
logError(`Tunnel error: ${error.message}`, error);
|
|
sendKoboldOutput(`Tunnel error: ${error.message}`);
|
|
});
|
|
|
|
tunnel.on('exit', (code, signal) => {
|
|
sendKoboldOutput(
|
|
`Tunnel process exited (code: ${code}, signal: ${signal})`
|
|
);
|
|
activeTunnel = null;
|
|
tunnelUrl = null;
|
|
sendToRenderer('tunnel-url-changed', null);
|
|
});
|
|
|
|
return tunnelUrl;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
logError(`Failed to start tunnel: ${errorMessage}`, error as Error);
|
|
sendKoboldOutput(`Failed to start tunnel: ${errorMessage}`);
|
|
activeTunnel = null;
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const stopTunnel = () => {
|
|
if (!activeTunnel) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
sendKoboldOutput('Stopping Cloudflare tunnel...');
|
|
activeTunnel.stop();
|
|
activeTunnel = null;
|
|
tunnelUrl = null;
|
|
sendToRenderer('tunnel-url-changed', null);
|
|
sendKoboldOutput('Tunnel stopped');
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
logError(`Failed to stop tunnel: ${errorMessage}`, error as Error);
|
|
activeTunnel = null;
|
|
tunnelUrl = null;
|
|
}
|
|
};
|
|
|
|
export const getTunnelUrl = () => tunnelUrl;
|
|
|
|
export const isTunnelActive = () => activeTunnel !== null;
|