mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
feat(ql3): distribute copilot console via signed admin image
This commit is contained in:
@@ -18,8 +18,9 @@ const USAGE = [
|
||||
'Usage:',
|
||||
' ql3-copilot-console --config /absolute/client.json --credential /absolute/credential --session /absolute/session [--port=0..65535]',
|
||||
' ql3-copilot-console --check --config /absolute/client.json --credential /absolute/credential --session /absolute/session',
|
||||
' ql3-copilot-console --container-published-loopback --port=1024..65535 --config /absolute/client.json --credential /absolute/credential --session /absolute/session [--check]',
|
||||
'',
|
||||
'The Console binds only 127.0.0.1 and exposes inspect/output reads.',
|
||||
'Native mode binds 127.0.0.1. Container mode requires host-loopback port publication.',
|
||||
'The browser session key remains in a separate owner-private 0600 file.',
|
||||
].join('\n');
|
||||
|
||||
@@ -27,6 +28,9 @@ interface ClusterCopilotConsoleCliArguments {
|
||||
readonly check: boolean;
|
||||
readonly configFile: string;
|
||||
readonly credentialFile: string;
|
||||
readonly networkBoundary:
|
||||
| 'host-loopback'
|
||||
| 'container-published-loopback';
|
||||
readonly sessionFile: string;
|
||||
readonly port: number;
|
||||
}
|
||||
@@ -71,6 +75,7 @@ export function parseClusterCopilotConsoleCliArguments(
|
||||
let sessionFile: string | undefined;
|
||||
let port = 0;
|
||||
let portSeen = false;
|
||||
let containerPublishedLoopback = false;
|
||||
for (let index = 0; index < argv.length; ) {
|
||||
const current = argv[index];
|
||||
if (current === '--check' && !check) {
|
||||
@@ -78,6 +83,14 @@ export function parseClusterCopilotConsoleCliArguments(
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
current === '--container-published-loopback' &&
|
||||
!containerPublishedLoopback
|
||||
) {
|
||||
containerPublishedLoopback = true;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const config = argumentValue(argv, index, '--config');
|
||||
if (config) {
|
||||
if (configFile !== undefined) return usageFailure();
|
||||
@@ -121,7 +134,8 @@ export function parseClusterCopilotConsoleCliArguments(
|
||||
configFile === undefined ||
|
||||
credentialFile === undefined ||
|
||||
sessionFile === undefined ||
|
||||
(check && port !== 0)
|
||||
(containerPublishedLoopback && port === 0) ||
|
||||
(!containerPublishedLoopback && check && port !== 0)
|
||||
) {
|
||||
return usageFailure();
|
||||
}
|
||||
@@ -129,6 +143,9 @@ export function parseClusterCopilotConsoleCliArguments(
|
||||
check,
|
||||
configFile,
|
||||
credentialFile,
|
||||
networkBoundary: containerPublishedLoopback
|
||||
? 'container-published-loopback'
|
||||
: 'host-loopback',
|
||||
sessionFile,
|
||||
port,
|
||||
});
|
||||
@@ -178,7 +195,8 @@ async function main(): Promise<void> {
|
||||
component: 'qinglong3-cluster-copilot-console',
|
||||
event: 'preflight_checked',
|
||||
ready: readiness.ready,
|
||||
listenAddress: '127.0.0.1',
|
||||
networkBoundary: parsed.networkBoundary,
|
||||
publishedHostAddress: '127.0.0.1',
|
||||
browserCredential: 'forbidden',
|
||||
clusterCredential: 'server_only',
|
||||
operations: ['inspect', 'output'],
|
||||
@@ -203,6 +221,7 @@ async function main(): Promise<void> {
|
||||
});
|
||||
},
|
||||
}),
|
||||
networkBoundary: parsed.networkBoundary,
|
||||
port: parsed.port,
|
||||
sessionDigest,
|
||||
});
|
||||
@@ -213,7 +232,8 @@ async function main(): Promise<void> {
|
||||
component: 'qinglong3-cluster-copilot-console',
|
||||
event: 'started',
|
||||
origin: server.origin,
|
||||
listenAddress: '127.0.0.1',
|
||||
networkBoundary: parsed.networkBoundary,
|
||||
publishedHostAddress: '127.0.0.1',
|
||||
browserCredential: 'forbidden',
|
||||
clusterCredential: 'server_only',
|
||||
operations: ['inspect', 'output'],
|
||||
|
||||
@@ -39,10 +39,15 @@ export interface ClusterCopilotConsoleExecutor {
|
||||
export interface ClusterCopilotConsoleServerOptions {
|
||||
readonly assets: Readonly<ClusterCopilotConsoleAssets>;
|
||||
readonly executor: ClusterCopilotConsoleExecutor;
|
||||
readonly networkBoundary?: ClusterCopilotConsoleNetworkBoundary;
|
||||
readonly port: number;
|
||||
readonly sessionDigest: Buffer;
|
||||
}
|
||||
|
||||
export type ClusterCopilotConsoleNetworkBoundary =
|
||||
| 'host-loopback'
|
||||
| 'container-published-loopback';
|
||||
|
||||
export interface ClusterCopilotConsoleServer {
|
||||
readonly origin: string;
|
||||
close(): Promise<void>;
|
||||
@@ -297,13 +302,16 @@ function remoteFailure(
|
||||
export async function startClusterCopilotConsoleServer(
|
||||
options: ClusterCopilotConsoleServerOptions,
|
||||
): Promise<Readonly<ClusterCopilotConsoleServer>> {
|
||||
const record = exactObject(options, [
|
||||
'assets',
|
||||
'executor',
|
||||
'port',
|
||||
'sessionDigest',
|
||||
]);
|
||||
const optionKeys = ['assets', 'executor', 'port', 'sessionDigest'];
|
||||
if (Object.hasOwn(options, 'networkBoundary')) {
|
||||
optionKeys.push('networkBoundary');
|
||||
}
|
||||
const record = exactObject(options, optionKeys);
|
||||
const assets = exactObject(record.assets, ['css', 'html', 'javascript']);
|
||||
const networkBoundary =
|
||||
record.networkBoundary === undefined
|
||||
? 'host-loopback'
|
||||
: record.networkBoundary;
|
||||
if (
|
||||
typeof assets.html !== 'string' ||
|
||||
assets.html.length < 1 ||
|
||||
@@ -317,6 +325,10 @@ export async function startClusterCopilotConsoleServer(
|
||||
!Number.isSafeInteger(record.port) ||
|
||||
((record.port as number) !== 0 &&
|
||||
((record.port as number) < 1_024 || (record.port as number) > 65_535)) ||
|
||||
(networkBoundary !== 'host-loopback' &&
|
||||
networkBoundary !== 'container-published-loopback') ||
|
||||
(networkBoundary === 'container-published-loopback' &&
|
||||
(record.port as number) === 0) ||
|
||||
!Buffer.isBuffer(record.sessionDigest) ||
|
||||
(record.sessionDigest as Buffer).byteLength !== 32
|
||||
) {
|
||||
@@ -324,6 +336,8 @@ export async function startClusterCopilotConsoleServer(
|
||||
}
|
||||
const sessionDigest = Buffer.from(record.sessionDigest as Buffer);
|
||||
const executor = record.executor as ClusterCopilotConsoleExecutor;
|
||||
const listenAddress =
|
||||
networkBoundary === 'host-loopback' ? '127.0.0.1' : '0.0.0.0';
|
||||
let expectedOrigin = '';
|
||||
let inFlight = 0;
|
||||
let closed = false;
|
||||
@@ -461,7 +475,7 @@ export async function startClusterCopilotConsoleServer(
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(record.port as number, '127.0.0.1', () => {
|
||||
server.listen(record.port as number, listenAddress, () => {
|
||||
server.off('error', reject);
|
||||
resolve();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user