bunqueue 2.8.7 → 2.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/client.d.ts +3 -0
- package/dist/cli/client.js +13 -2
- package/dist/cli/commands/server.js +26 -2
- package/dist/cli/help.js +5 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +53 -1
- package/dist/client/queue/queue.js +2 -0
- package/dist/client/tcp/client.js +1 -0
- package/dist/client/tcp/connection.d.ts +8 -1
- package/dist/client/tcp/connection.js +27 -1
- package/dist/client/tcp/index.d.ts +1 -1
- package/dist/client/tcp/shared.d.ts +6 -4
- package/dist/client/tcp/shared.js +27 -11
- package/dist/client/tcp/types.d.ts +13 -0
- package/dist/client/tcp/types.js +1 -0
- package/dist/client/tcpPool.js +11 -1
- package/dist/client/types.d.ts +8 -0
- package/dist/client/worker/worker.js +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/resolve.d.ts +14 -0
- package/dist/config/resolve.js +19 -0
- package/dist/config/types.d.ts +4 -0
- package/dist/infrastructure/server/http.d.ts +3 -0
- package/dist/infrastructure/server/http.js +10 -2
- package/dist/infrastructure/server/tcp.d.ts +6 -0
- package/dist/infrastructure/server/tcp.js +5 -1
- package/dist/infrastructure/server/tls.d.ts +21 -0
- package/dist/infrastructure/server/tls.js +19 -0
- package/dist/main.js +13 -1
- package/package.json +1 -1
package/dist/cli/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* CLI TCP Client
|
|
3
3
|
* Connects to bunQ server and executes commands (msgpack binary protocol)
|
|
4
4
|
*/
|
|
5
|
+
import type { ClientTlsOptions } from '../client/tcp/types';
|
|
5
6
|
/** Client options */
|
|
6
7
|
export interface ClientOptions {
|
|
7
8
|
/** Server host */
|
|
@@ -10,6 +11,8 @@ export interface ClientOptions {
|
|
|
10
11
|
port: number;
|
|
11
12
|
/** Auth token */
|
|
12
13
|
token?: string;
|
|
14
|
+
/** TLS to the server (true = system CAs, object = custom CA / no-verify) */
|
|
15
|
+
tls?: boolean | ClientTlsOptions;
|
|
13
16
|
/** Output as JSON */
|
|
14
17
|
json: boolean;
|
|
15
18
|
}
|
package/dist/cli/client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { formatOutput, formatError } from './output';
|
|
|
6
6
|
import { pack, unpack } from 'msgpackr';
|
|
7
7
|
import { FrameParser, FrameSizeError } from '../infrastructure/server/protocol';
|
|
8
8
|
import { CommandError } from './commands/types';
|
|
9
|
+
import { buildClientTls } from '../client/tcp/connection';
|
|
9
10
|
/** Send a command and wait for response */
|
|
10
11
|
async function sendCommand(socket, command) {
|
|
11
12
|
return new Promise((resolve, reject) => {
|
|
@@ -45,7 +46,10 @@ async function connect(options) {
|
|
|
45
46
|
catch (err) {
|
|
46
47
|
if (err instanceof FrameSizeError) {
|
|
47
48
|
if (socketData.reject) {
|
|
48
|
-
|
|
49
|
+
// A plaintext client reading a TLS handshake sees garbage that
|
|
50
|
+
// parses as an absurd frame size — hint at the likely cause.
|
|
51
|
+
socketData.reject(new Error(`Frame too large: ${err.requestedSize} bytes exceeds maximum ${err.maxSize}` +
|
|
52
|
+
' (is the server running with TLS? try --tls, --tls-ca or --tls-no-verify)'));
|
|
49
53
|
socketData.resolve = null;
|
|
50
54
|
socketData.reject = null;
|
|
51
55
|
}
|
|
@@ -95,11 +99,18 @@ async function connect(options) {
|
|
|
95
99
|
reject(new Error(`Failed to connect to ${targetDesc}: ${error.message}`));
|
|
96
100
|
},
|
|
97
101
|
};
|
|
98
|
-
// Connect via TCP
|
|
102
|
+
// Connect via TCP (optionally wrapped in TLS)
|
|
103
|
+
const tlsValue = buildClientTls(options.tls);
|
|
99
104
|
void Bun.connect({
|
|
100
105
|
hostname: options.host,
|
|
101
106
|
port: options.port,
|
|
107
|
+
...(tlsValue !== undefined && { tls: tlsValue }),
|
|
102
108
|
socket: socketHandlers,
|
|
109
|
+
}).catch((err) => {
|
|
110
|
+
// Bun.connect can reject directly (e.g. TLS handshake refused) instead of
|
|
111
|
+
// firing connectError — surface it instead of waiting out the timeout.
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
reject(new Error(`Failed to connect to ${targetDesc}: ${message}`));
|
|
103
114
|
});
|
|
104
115
|
// Handle connection timeout
|
|
105
116
|
const connectionTimeoutId = setTimeout(() => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { printServerHelp } from '../help';
|
|
7
7
|
import { VERSION } from '../../shared/version';
|
|
8
|
-
import { loadConfigFile, resolveServerConfig, resolveCloudConfig } from '../../config';
|
|
8
|
+
import { loadConfigFile, resolveServerConfig, resolveCloudConfig, resolveTlsServerOptions, } from '../../config';
|
|
9
9
|
/** Validate port number */
|
|
10
10
|
function validatePort(value, name, defaultPort) {
|
|
11
11
|
const port = parseInt(value, 10);
|
|
@@ -25,6 +25,8 @@ function parseCliFlags(args) {
|
|
|
25
25
|
host: { type: 'string' },
|
|
26
26
|
'data-path': { type: 'string' },
|
|
27
27
|
'auth-tokens': { type: 'string' },
|
|
28
|
+
'tls-cert': { type: 'string' },
|
|
29
|
+
'tls-key': { type: 'string' },
|
|
28
30
|
config: { type: 'string', short: 'c' },
|
|
29
31
|
},
|
|
30
32
|
allowPositionals: false,
|
|
@@ -46,6 +48,12 @@ function parseCliFlags(args) {
|
|
|
46
48
|
if (values['auth-tokens']) {
|
|
47
49
|
flags.authTokens = values['auth-tokens'].split(',').filter(Boolean);
|
|
48
50
|
}
|
|
51
|
+
if (values['tls-cert']) {
|
|
52
|
+
flags.tlsCertFile = values['tls-cert'];
|
|
53
|
+
}
|
|
54
|
+
if (values['tls-key']) {
|
|
55
|
+
flags.tlsKeyFile = values['tls-key'];
|
|
56
|
+
}
|
|
49
57
|
if (values.config) {
|
|
50
58
|
flags.configPath = values.config;
|
|
51
59
|
}
|
|
@@ -58,7 +66,9 @@ function applyCliFlags(fileConfig, flags) {
|
|
|
58
66
|
flags.httpPort !== undefined ||
|
|
59
67
|
flags.host !== undefined ||
|
|
60
68
|
flags.dataPath !== undefined ||
|
|
61
|
-
flags.authTokens !== undefined
|
|
69
|
+
flags.authTokens !== undefined ||
|
|
70
|
+
flags.tlsCertFile !== undefined ||
|
|
71
|
+
flags.tlsKeyFile !== undefined;
|
|
62
72
|
if (!hasFlags && !fileConfig)
|
|
63
73
|
return null;
|
|
64
74
|
const base = fileConfig ?? {};
|
|
@@ -69,6 +79,8 @@ function applyCliFlags(fileConfig, flags) {
|
|
|
69
79
|
...(flags.tcpPort !== undefined && { tcpPort: flags.tcpPort }),
|
|
70
80
|
...(flags.httpPort !== undefined && { httpPort: flags.httpPort }),
|
|
71
81
|
...(flags.host !== undefined && { host: flags.host }),
|
|
82
|
+
...(flags.tlsCertFile !== undefined && { tlsCertFile: flags.tlsCertFile }),
|
|
83
|
+
...(flags.tlsKeyFile !== undefined && { tlsKeyFile: flags.tlsKeyFile }),
|
|
72
84
|
},
|
|
73
85
|
storage: {
|
|
74
86
|
...base.storage,
|
|
@@ -92,6 +104,15 @@ export async function runServer(args, showHelp) {
|
|
|
92
104
|
const mergedConfig = applyCliFlags(fileConfig, flags);
|
|
93
105
|
const config = resolveServerConfig(mergedConfig);
|
|
94
106
|
const cloudConfig = resolveCloudConfig(mergedConfig, config.dataPath);
|
|
107
|
+
// Resolve TLS — fail fast on partial cert/key before binding anything
|
|
108
|
+
let tlsConfig;
|
|
109
|
+
try {
|
|
110
|
+
tlsConfig = resolveTlsServerOptions(config);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
95
116
|
// Import and start the server components
|
|
96
117
|
const { QueueManager } = await import('../../application/queueManager');
|
|
97
118
|
const { createTcpServer } = await import('../../infrastructure/server/tcp');
|
|
@@ -110,11 +131,13 @@ export async function runServer(args, showHelp) {
|
|
|
110
131
|
port: config.tcpPort,
|
|
111
132
|
hostname: config.hostname,
|
|
112
133
|
authTokens,
|
|
134
|
+
...(tlsConfig && { tls: tlsConfig }),
|
|
113
135
|
});
|
|
114
136
|
httpServer = createHttpServer(qm, {
|
|
115
137
|
port: config.httpPort,
|
|
116
138
|
hostname: config.hostname,
|
|
117
139
|
authTokens,
|
|
140
|
+
...(tlsConfig && { tls: tlsConfig }),
|
|
118
141
|
});
|
|
119
142
|
}
|
|
120
143
|
catch (err) {
|
|
@@ -159,6 +182,7 @@ ${dim}────────────────────────
|
|
|
159
182
|
${green}●${reset} TCP ${tcpDisplay}
|
|
160
183
|
${green}●${reset} HTTP ${httpDisplay}
|
|
161
184
|
${yellow}●${reset} Data ${config.dataPath ?? 'in-memory'}
|
|
185
|
+
${yellow}●${reset} TLS ${tlsConfig ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
162
186
|
${yellow}●${reset} Auth ${authTokens ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
163
187
|
${yellow}●${reset} Cloud ${cloudConfig ? `${green}enabled${reset} ${dim}→ ${cloudConfig.url}${reset}` : `${dim}disabled${reset}`}
|
|
164
188
|
|
package/dist/cli/help.js
CHANGED
|
@@ -98,6 +98,9 @@ GLOBAL OPTIONS:
|
|
|
98
98
|
-H, --host <host> Server host (default: localhost)
|
|
99
99
|
-p, --port <port> TCP port (default: 6789)
|
|
100
100
|
-t, --token <token> Authentication token (env: BQ_TOKEN, BUNQUEUE_TOKEN)
|
|
101
|
+
--tls Connect with TLS (verify with system CAs)
|
|
102
|
+
--tls-ca <file> Trust a custom CA cert (implies --tls)
|
|
103
|
+
--tls-no-verify TLS without cert verification (self-signed, dev only)
|
|
101
104
|
--json Output as JSON
|
|
102
105
|
--help Show help
|
|
103
106
|
--version Show version
|
|
@@ -125,6 +128,8 @@ Options:
|
|
|
125
128
|
--host <host> Bind address (default: 0.0.0.0, env: HOST)
|
|
126
129
|
--data-path <path> SQLite database path (env: DATA_PATH)
|
|
127
130
|
--auth-tokens <list> Comma-separated auth tokens (env: AUTH_TOKENS)
|
|
131
|
+
--tls-cert <file> PEM certificate for native TLS (env: TLS_CERT_FILE)
|
|
132
|
+
--tls-key <file> PEM private key for native TLS (env: TLS_KEY_FILE)
|
|
128
133
|
--help Show this help
|
|
129
134
|
|
|
130
135
|
Examples:
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ interface GlobalOptions {
|
|
|
8
8
|
host: string;
|
|
9
9
|
port: number;
|
|
10
10
|
token?: string;
|
|
11
|
+
/** TLS to the server: true = verify with system CAs, object = custom CA / no-verify */
|
|
12
|
+
tls?: boolean | {
|
|
13
|
+
rejectUnauthorized?: boolean;
|
|
14
|
+
caFile?: string;
|
|
15
|
+
};
|
|
11
16
|
json: boolean;
|
|
12
17
|
help: boolean;
|
|
13
18
|
version: boolean;
|
package/dist/cli/index.js
CHANGED
|
@@ -35,6 +35,53 @@ function resolveEnvPort(currentPort) {
|
|
|
35
35
|
function resolveEnvHost(currentHost) {
|
|
36
36
|
return Bun.env.HOST ?? Bun.env.BUNQUEUE_HOST ?? Bun.env.BQ_HOST ?? currentHost;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Handle a `--tls*` global client flag and return the index to continue
|
|
40
|
+
* scanning from. Flags that are NOT client TLS flags (e.g. the server's
|
|
41
|
+
* --tls-cert/--tls-key) are passed through to `commandArgs`.
|
|
42
|
+
*/
|
|
43
|
+
function applyTlsFlag(arg, allArgs, i, state, commandArgs) {
|
|
44
|
+
if (arg === '--tls') {
|
|
45
|
+
state.enabled = true;
|
|
46
|
+
return i;
|
|
47
|
+
}
|
|
48
|
+
if (arg === '--tls-no-verify') {
|
|
49
|
+
state.noVerify = true;
|
|
50
|
+
return i;
|
|
51
|
+
}
|
|
52
|
+
if (arg === '--tls-ca') {
|
|
53
|
+
const nextArg = allArgs[i + 1];
|
|
54
|
+
// A following flag is not a path: don't swallow it (--tls-ca --json ...)
|
|
55
|
+
if (nextArg === undefined || nextArg.startsWith('-')) {
|
|
56
|
+
console.warn('Warning: --tls-ca requires a file path. Option ignored.');
|
|
57
|
+
return i;
|
|
58
|
+
}
|
|
59
|
+
state.caFile = nextArg;
|
|
60
|
+
return i + 1;
|
|
61
|
+
}
|
|
62
|
+
if (arg.startsWith('--tls-ca=')) {
|
|
63
|
+
const val = arg.slice(9);
|
|
64
|
+
if (!val) {
|
|
65
|
+
console.warn('Warning: --tls-ca= requires a file path. Option ignored.');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
state.caFile = val;
|
|
69
|
+
}
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
commandArgs.push(arg); // --tls-cert / --tls-key etc. → server flags, pass through
|
|
73
|
+
return i;
|
|
74
|
+
}
|
|
75
|
+
/** Build the GlobalOptions.tls value: --tls-ca / --tls-no-verify imply TLS */
|
|
76
|
+
function buildTlsOption(state) {
|
|
77
|
+
if (state.noVerify || state.caFile !== undefined) {
|
|
78
|
+
return {
|
|
79
|
+
...(state.noVerify && { rejectUnauthorized: false }),
|
|
80
|
+
...(state.caFile !== undefined && { caFile: state.caFile }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return state.enabled ? true : undefined;
|
|
84
|
+
}
|
|
38
85
|
/** Parse global options from process.argv */
|
|
39
86
|
export function parseGlobalOptions() {
|
|
40
87
|
const allArgs = process.argv.slice(2);
|
|
@@ -47,6 +94,7 @@ export function parseGlobalOptions() {
|
|
|
47
94
|
let version = false;
|
|
48
95
|
let hostExplicit = false;
|
|
49
96
|
let portExplicit = false;
|
|
97
|
+
const tlsState = { enabled: false, noVerify: false };
|
|
50
98
|
const commandArgs = [];
|
|
51
99
|
let i = 0;
|
|
52
100
|
while (i < allArgs.length) {
|
|
@@ -76,6 +124,9 @@ export function parseGlobalOptions() {
|
|
|
76
124
|
token = allArgs[++i];
|
|
77
125
|
}
|
|
78
126
|
}
|
|
127
|
+
else if (arg.startsWith('--tls')) {
|
|
128
|
+
i = applyTlsFlag(arg, allArgs, i, tlsState, commandArgs);
|
|
129
|
+
}
|
|
79
130
|
else if (arg === '--json') {
|
|
80
131
|
json = true;
|
|
81
132
|
}
|
|
@@ -137,7 +188,7 @@ export function parseGlobalOptions() {
|
|
|
137
188
|
if (!hostExplicit)
|
|
138
189
|
host = resolveEnvHost(host);
|
|
139
190
|
return {
|
|
140
|
-
options: { host, port, token, json, help, version },
|
|
191
|
+
options: { host, port, token, tls: buildTlsOption(tlsState), json, help, version },
|
|
141
192
|
commandArgs,
|
|
142
193
|
};
|
|
143
194
|
}
|
|
@@ -231,6 +282,7 @@ export async function main() {
|
|
|
231
282
|
host: options.host,
|
|
232
283
|
port: options.port,
|
|
233
284
|
token: options.token,
|
|
285
|
+
tls: options.tls,
|
|
234
286
|
json: options.json,
|
|
235
287
|
});
|
|
236
288
|
}
|
|
@@ -60,6 +60,7 @@ export class Queue {
|
|
|
60
60
|
this.tcpPool = getSharedPool({
|
|
61
61
|
host: connOpts.host,
|
|
62
62
|
port: connOpts.port,
|
|
63
|
+
tls: connOpts.tls,
|
|
63
64
|
poolSize,
|
|
64
65
|
pingInterval: connOpts.pingInterval,
|
|
65
66
|
commandTimeout: connOpts.commandTimeout,
|
|
@@ -74,6 +75,7 @@ export class Queue {
|
|
|
74
75
|
host: connOpts.host ?? 'localhost',
|
|
75
76
|
port: connOpts.port ?? 6789,
|
|
76
77
|
token,
|
|
78
|
+
tls: connOpts.tls,
|
|
77
79
|
poolSize,
|
|
78
80
|
pingInterval: connOpts.pingInterval,
|
|
79
81
|
commandTimeout: connOpts.commandTimeout,
|
|
@@ -137,6 +137,7 @@ export class TcpClient extends EventEmitter {
|
|
|
137
137
|
const { socket } = await createConnection({
|
|
138
138
|
host: this.options.host,
|
|
139
139
|
port: this.options.port,
|
|
140
|
+
tls: this.options.tls,
|
|
140
141
|
}, this.options.connectTimeout, {
|
|
141
142
|
onData: (frame) => {
|
|
142
143
|
this.handleData(frame);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* TCP Connection Handler
|
|
3
3
|
* Manages low-level socket connection and data handling (msgpack binary protocol)
|
|
4
4
|
*/
|
|
5
|
-
import type { SocketWrapper, PendingCommand } from './types';
|
|
5
|
+
import type { SocketWrapper, PendingCommand, ClientTlsOptions } from './types';
|
|
6
6
|
/** Connection events */
|
|
7
7
|
export interface ConnectionEvents {
|
|
8
8
|
onData: (frame: Uint8Array) => void;
|
|
@@ -20,7 +20,14 @@ export interface ConnectionTarget {
|
|
|
20
20
|
host?: string;
|
|
21
21
|
/** TCP port */
|
|
22
22
|
port?: number;
|
|
23
|
+
/** Enable TLS: true (system CAs) or per-connection TLS options */
|
|
24
|
+
tls?: boolean | ClientTlsOptions;
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Map client TLS options to the `tls` value accepted by Bun.connect.
|
|
28
|
+
* Returns undefined when TLS is disabled (plaintext, the default).
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildClientTls(tls: boolean | ClientTlsOptions | undefined): true | Record<string, unknown> | undefined;
|
|
24
31
|
/**
|
|
25
32
|
* Establish TCP connection to server
|
|
26
33
|
*/
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
* Manages low-level socket connection and data handling (msgpack binary protocol)
|
|
4
4
|
*/
|
|
5
5
|
import { FrameParser, FrameSizeError } from '../../infrastructure/server/protocol';
|
|
6
|
+
/**
|
|
7
|
+
* Map client TLS options to the `tls` value accepted by Bun.connect.
|
|
8
|
+
* Returns undefined when TLS is disabled (plaintext, the default).
|
|
9
|
+
*/
|
|
10
|
+
export function buildClientTls(tls) {
|
|
11
|
+
if (!tls)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (tls === true)
|
|
14
|
+
return true;
|
|
15
|
+
return {
|
|
16
|
+
...(tls.rejectUnauthorized !== undefined && { rejectUnauthorized: tls.rejectUnauthorized }),
|
|
17
|
+
...(tls.caFile !== undefined && { ca: Bun.file(tls.caFile) }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
6
20
|
/**
|
|
7
21
|
* Establish TCP connection to server
|
|
8
22
|
*/
|
|
@@ -82,11 +96,23 @@ export async function createConnection(target, connectTimeout, events) {
|
|
|
82
96
|
}
|
|
83
97
|
},
|
|
84
98
|
};
|
|
85
|
-
// Connect via TCP
|
|
99
|
+
// Connect via TCP (optionally wrapped in TLS — protocol is unchanged)
|
|
100
|
+
const tlsValue = buildClientTls(target.tls);
|
|
86
101
|
void Bun.connect({
|
|
87
102
|
hostname: target.host ?? 'localhost',
|
|
88
103
|
port: target.port ?? 6789,
|
|
104
|
+
...(tlsValue !== undefined && { tls: tlsValue }),
|
|
89
105
|
socket: socketHandlers,
|
|
106
|
+
}).catch((error) => {
|
|
107
|
+
// Bun.connect rejects (instead of firing connectError) for some failure
|
|
108
|
+
// modes, e.g. a TLS handshake refused synchronously. Route it through the
|
|
109
|
+
// same rejection path so callers never hang until the connect timeout.
|
|
110
|
+
if (!connectionResolved) {
|
|
111
|
+
connectionResolved = true;
|
|
112
|
+
cleanup();
|
|
113
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
+
reject(new Error(`Failed to connect to ${targetDesc}: ${message}`));
|
|
115
|
+
}
|
|
90
116
|
});
|
|
91
117
|
timeoutId = setTimeout(() => {
|
|
92
118
|
if (!connectionResolved) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* TCP Client Module
|
|
3
3
|
* Re-exports all TCP client components
|
|
4
4
|
*/
|
|
5
|
-
export type { ConnectionOptions, ConnectionHealth, PendingCommand, SocketWrapper } from './types';
|
|
5
|
+
export type { ConnectionOptions, ConnectionHealth, PendingCommand, SocketWrapper, ClientTlsOptions, } from './types';
|
|
6
6
|
export { DEFAULT_CONNECTION } from './types';
|
|
7
7
|
export { HealthTracker, type HealthConfig } from './health';
|
|
8
8
|
export { ReconnectManager, type ReconnectConfig } from './reconnect';
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared TCP Client
|
|
3
|
-
*
|
|
2
|
+
* Shared TCP Client Instances
|
|
3
|
+
* One shared client per distinct connection target. Keyed by
|
|
4
|
+
* host/port/token/tls so callers with different configs (notably TLS vs
|
|
5
|
+
* plaintext to the same server) never receive each other's connection.
|
|
4
6
|
*/
|
|
5
7
|
import type { ConnectionOptions } from './types';
|
|
6
8
|
import { TcpClient } from './client';
|
|
7
|
-
/** Get shared TCP client */
|
|
9
|
+
/** Get shared TCP client for the given connection target */
|
|
8
10
|
export declare function getSharedTcpClient(options?: Partial<ConnectionOptions>): TcpClient;
|
|
9
|
-
/** Close shared
|
|
11
|
+
/** Close all shared clients */
|
|
10
12
|
export declare function closeSharedTcpClient(): void;
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared TCP Client
|
|
3
|
-
*
|
|
2
|
+
* Shared TCP Client Instances
|
|
3
|
+
* One shared client per distinct connection target. Keyed by
|
|
4
|
+
* host/port/token/tls so callers with different configs (notably TLS vs
|
|
5
|
+
* plaintext to the same server) never receive each other's connection.
|
|
4
6
|
*/
|
|
5
7
|
import { TcpClient } from './client';
|
|
6
|
-
/** Shared
|
|
7
|
-
|
|
8
|
-
/**
|
|
8
|
+
/** Shared clients keyed by connection target */
|
|
9
|
+
const sharedClients = new Map();
|
|
10
|
+
/** Build the sharing key from the connection-identity options */
|
|
11
|
+
function getClientKey(options) {
|
|
12
|
+
const host = options?.host ?? 'localhost';
|
|
13
|
+
const port = options?.port ?? 6789;
|
|
14
|
+
const token = options?.token ?? '';
|
|
15
|
+
const tokenHash = token ? String(Number(Bun.hash(token)) & 0xffff) : '0';
|
|
16
|
+
const tlsKey = options?.tls ? JSON.stringify(options.tls) : '0';
|
|
17
|
+
return `${host}:${port}:${tokenHash}:${tlsKey}`;
|
|
18
|
+
}
|
|
19
|
+
/** Get shared TCP client for the given connection target */
|
|
9
20
|
export function getSharedTcpClient(options) {
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
const key = getClientKey(options);
|
|
22
|
+
let client = sharedClients.get(key);
|
|
23
|
+
if (!client) {
|
|
24
|
+
client = new TcpClient(options);
|
|
25
|
+
sharedClients.set(key, client);
|
|
26
|
+
}
|
|
27
|
+
return client;
|
|
12
28
|
}
|
|
13
|
-
/** Close shared
|
|
29
|
+
/** Close all shared clients */
|
|
14
30
|
export function closeSharedTcpClient() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
sharedClient = null;
|
|
31
|
+
for (const client of sharedClients.values()) {
|
|
32
|
+
client.close();
|
|
18
33
|
}
|
|
34
|
+
sharedClients.clear();
|
|
19
35
|
}
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
* TCP Client Types
|
|
3
3
|
* Type definitions for TCP connection management
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* TLS options for client connections.
|
|
7
|
+
* `true` enables TLS with system CA verification; the object form allows
|
|
8
|
+
* trusting a custom CA (self-signed server cert) or disabling verification.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClientTlsOptions {
|
|
11
|
+
/** Verify the server certificate (default: true). Set false for self-signed in dev. */
|
|
12
|
+
rejectUnauthorized?: boolean;
|
|
13
|
+
/** Path to a PEM CA certificate to trust (e.g. the self-signed server cert) */
|
|
14
|
+
caFile?: string;
|
|
15
|
+
}
|
|
5
16
|
/** Connection options */
|
|
6
17
|
export interface ConnectionOptions {
|
|
7
18
|
/** Server host */
|
|
@@ -10,6 +21,8 @@ export interface ConnectionOptions {
|
|
|
10
21
|
port: number;
|
|
11
22
|
/** Auth token */
|
|
12
23
|
token?: string;
|
|
24
|
+
/** Enable TLS: true (system CAs) or per-connection TLS options (default: false) */
|
|
25
|
+
tls?: boolean | ClientTlsOptions;
|
|
13
26
|
/** Max reconnection attempts (default: Infinity) */
|
|
14
27
|
maxReconnectAttempts?: number;
|
|
15
28
|
/** Initial reconnect delay in ms (default: 100) */
|
package/dist/client/tcp/types.js
CHANGED
package/dist/client/tcpPool.js
CHANGED
|
@@ -20,6 +20,7 @@ export class TcpConnectionPool {
|
|
|
20
20
|
host: options.host ?? 'localhost',
|
|
21
21
|
port: options.port ?? 6789,
|
|
22
22
|
token: options.token ?? '',
|
|
23
|
+
tls: options.tls ?? false,
|
|
23
24
|
poolSize,
|
|
24
25
|
maxReconnectAttempts: options.maxReconnectAttempts ?? Infinity,
|
|
25
26
|
reconnectDelay: options.reconnectDelay ?? 100,
|
|
@@ -39,6 +40,7 @@ export class TcpConnectionPool {
|
|
|
39
40
|
host: this.options.host,
|
|
40
41
|
port: this.options.port,
|
|
41
42
|
token: this.options.token,
|
|
43
|
+
tls: this.options.tls,
|
|
42
44
|
maxReconnectAttempts: this.options.maxReconnectAttempts,
|
|
43
45
|
reconnectDelay: this.options.reconnectDelay,
|
|
44
46
|
maxReconnectDelay: this.options.maxReconnectDelay,
|
|
@@ -49,6 +51,11 @@ export class TcpConnectionPool {
|
|
|
49
51
|
maxPingFailures: this.options.maxPingFailures,
|
|
50
52
|
maxCommandTimeouts: this.options.maxCommandTimeouts,
|
|
51
53
|
});
|
|
54
|
+
// Socket-level errors (e.g. TLS handshake failures, protocol garbage) are
|
|
55
|
+
// emitted as 'error' events; without a listener EventEmitter would throw
|
|
56
|
+
// and crash the process. Commands are still settled via the close/timeout
|
|
57
|
+
// paths, so observing the error here is enough.
|
|
58
|
+
client.on('error', () => { });
|
|
52
59
|
this.clients.push(client);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -169,7 +176,10 @@ function getPoolKey(options) {
|
|
|
169
176
|
const token = options?.token ?? '';
|
|
170
177
|
// Include poolSize and token hash to prevent sharing pools with different configs
|
|
171
178
|
const tokenHash = token ? String(Number(Bun.hash(token)) & 0xffff) : '0';
|
|
172
|
-
|
|
179
|
+
// TLS config must differentiate pools too: a TLS pool and a plaintext pool
|
|
180
|
+
// to the same host:port are NOT interchangeable.
|
|
181
|
+
const tlsKey = options?.tls ? JSON.stringify(options.tls) : '0';
|
|
182
|
+
return `${host}:${port}:${poolSize}:${tokenHash}:${tlsKey}`;
|
|
173
183
|
}
|
|
174
184
|
/** Get or create shared connection pool */
|
|
175
185
|
export function getSharedPool(options) {
|
package/dist/client/types.d.ts
CHANGED
|
@@ -360,6 +360,12 @@ export interface JobOptions {
|
|
|
360
360
|
/** Debounce configuration */
|
|
361
361
|
debounce?: DebounceOptions;
|
|
362
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* TLS options for client connections — single definition lives in tcp/types
|
|
365
|
+
* to prevent drift between the public and internal ConnectionOptions.
|
|
366
|
+
*/
|
|
367
|
+
export type { ClientTlsOptions } from './tcp/types';
|
|
368
|
+
import type { ClientTlsOptions } from './tcp/types';
|
|
363
369
|
/** Connection options for TCP mode */
|
|
364
370
|
export interface ConnectionOptions {
|
|
365
371
|
/** Server host (default: localhost, ignored if socketPath is set) */
|
|
@@ -368,6 +374,8 @@ export interface ConnectionOptions {
|
|
|
368
374
|
port?: number;
|
|
369
375
|
/** Unix socket path (takes priority over host/port) */
|
|
370
376
|
socketPath?: string;
|
|
377
|
+
/** Enable TLS to the server: true (system CAs) or custom options (default: off) */
|
|
378
|
+
tls?: boolean | ClientTlsOptions;
|
|
371
379
|
/** Auth token */
|
|
372
380
|
token?: string;
|
|
373
381
|
/** Connection pool size for parallel operations (default: 1, set >1 to enable pooling) */
|
package/dist/config/index.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
export { defineConfig } from './types';
|
|
6
6
|
export type { BunqueueConfig } from './types';
|
|
7
7
|
export { loadConfigFile } from './loader';
|
|
8
|
-
export { resolveServerConfig, resolveCloudConfig, resolveBackupConfig } from './resolve';
|
|
8
|
+
export { resolveServerConfig, resolveCloudConfig, resolveBackupConfig, resolveTlsServerOptions, } from './resolve';
|
|
9
9
|
export type { ResolvedConfig } from './resolve';
|
package/dist/config/index.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { defineConfig } from './types';
|
|
6
6
|
export { loadConfigFile } from './loader';
|
|
7
|
-
export { resolveServerConfig, resolveCloudConfig, resolveBackupConfig } from './resolve';
|
|
7
|
+
export { resolveServerConfig, resolveCloudConfig, resolveBackupConfig, resolveTlsServerOptions, } from './resolve';
|
package/dist/config/resolve.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ResolvedConfig {
|
|
|
12
12
|
hostname: string;
|
|
13
13
|
tcpSocketPath: string | undefined;
|
|
14
14
|
httpSocketPath: string | undefined;
|
|
15
|
+
tlsCertFile: string | undefined;
|
|
16
|
+
tlsKeyFile: string | undefined;
|
|
15
17
|
authTokens: string[];
|
|
16
18
|
dataPath: string | undefined;
|
|
17
19
|
corsOrigins: string[];
|
|
@@ -22,6 +24,18 @@ export interface ResolvedConfig {
|
|
|
22
24
|
}
|
|
23
25
|
/** Resolve server config: config file > env vars > defaults */
|
|
24
26
|
export declare function resolveServerConfig(fileConfig: BunqueueConfig | null): ResolvedConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve server TLS options from resolved config. Returns null when TLS is
|
|
29
|
+
* not configured; throws when only one of cert/key is set (fail fast at
|
|
30
|
+
* startup rather than serving plaintext when the operator expected TLS).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveTlsServerOptions(config: {
|
|
33
|
+
tlsCertFile?: string;
|
|
34
|
+
tlsKeyFile?: string;
|
|
35
|
+
}): {
|
|
36
|
+
certFile: string;
|
|
37
|
+
keyFile: string;
|
|
38
|
+
} | null;
|
|
25
39
|
/** Resolve cloud config: config file > env vars. Returns null if disabled. */
|
|
26
40
|
export declare function resolveCloudConfig(fileConfig: BunqueueConfig | null, dataPath?: string): CloudConfig | null;
|
|
27
41
|
/** Resolve S3 backup config: config file > env vars */
|
package/dist/config/resolve.js
CHANGED
|
@@ -14,6 +14,8 @@ export function resolveServerConfig(fileConfig) {
|
|
|
14
14
|
hostname: fc?.server?.host ?? Bun.env.HOST ?? '0.0.0.0',
|
|
15
15
|
tcpSocketPath: fc?.server?.tcpSocketPath ?? Bun.env.TCP_SOCKET_PATH,
|
|
16
16
|
httpSocketPath: fc?.server?.httpSocketPath ?? Bun.env.HTTP_SOCKET_PATH,
|
|
17
|
+
tlsCertFile: fc?.server?.tlsCertFile ?? Bun.env.TLS_CERT_FILE,
|
|
18
|
+
tlsKeyFile: fc?.server?.tlsKeyFile ?? Bun.env.TLS_KEY_FILE,
|
|
17
19
|
authTokens: fc?.auth?.tokens ?? Bun.env.AUTH_TOKENS?.split(',').filter(Boolean) ?? [],
|
|
18
20
|
dataPath: fc?.storage?.dataPath ??
|
|
19
21
|
Bun.env.BUNQUEUE_DATA_PATH ??
|
|
@@ -28,6 +30,23 @@ export function resolveServerConfig(fileConfig) {
|
|
|
28
30
|
statsIntervalMs: fc?.timeouts?.stats ?? parseInt(Bun.env.STATS_INTERVAL_MS ?? '300000', 10),
|
|
29
31
|
};
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve server TLS options from resolved config. Returns null when TLS is
|
|
35
|
+
* not configured; throws when only one of cert/key is set (fail fast at
|
|
36
|
+
* startup rather than serving plaintext when the operator expected TLS).
|
|
37
|
+
*/
|
|
38
|
+
export function resolveTlsServerOptions(config) {
|
|
39
|
+
const { tlsCertFile, tlsKeyFile } = config;
|
|
40
|
+
if (!tlsCertFile && !tlsKeyFile)
|
|
41
|
+
return null;
|
|
42
|
+
if (!tlsKeyFile) {
|
|
43
|
+
throw new Error('TLS misconfigured: tlsCertFile is set but tlsKeyFile (TLS_KEY_FILE) is missing');
|
|
44
|
+
}
|
|
45
|
+
if (!tlsCertFile) {
|
|
46
|
+
throw new Error('TLS misconfigured: tlsKeyFile is set but tlsCertFile (TLS_CERT_FILE) is missing');
|
|
47
|
+
}
|
|
48
|
+
return { certFile: tlsCertFile, keyFile: tlsKeyFile };
|
|
49
|
+
}
|
|
31
50
|
/** Resolve cloud config: config file > env vars. Returns null if disabled. */
|
|
32
51
|
export function resolveCloudConfig(fileConfig, dataPath) {
|
|
33
52
|
const fc = fileConfig?.cloud;
|
package/dist/config/types.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface BunqueueConfig {
|
|
|
10
10
|
host?: string;
|
|
11
11
|
tcpSocketPath?: string;
|
|
12
12
|
httpSocketPath?: string;
|
|
13
|
+
/** Path to PEM certificate file — enables native TLS on TCP + HTTP (with tlsKeyFile) */
|
|
14
|
+
tlsCertFile?: string;
|
|
15
|
+
/** Path to PEM private key file — enables native TLS on TCP + HTTP (with tlsCertFile) */
|
|
16
|
+
tlsKeyFile?: string;
|
|
13
17
|
};
|
|
14
18
|
auth?: {
|
|
15
19
|
tokens?: string[];
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { Server, ServerWebSocket } from 'bun';
|
|
6
6
|
import type { QueueManager } from '../../application/queueManager';
|
|
7
7
|
import { type WsData } from './wsHandler';
|
|
8
|
+
import { type TlsServerOptions } from './tls';
|
|
8
9
|
/** HTTP Server configuration */
|
|
9
10
|
export interface HttpServerConfig {
|
|
10
11
|
port?: number;
|
|
@@ -13,6 +14,8 @@ export interface HttpServerConfig {
|
|
|
13
14
|
authTokens?: string[];
|
|
14
15
|
corsOrigins?: string[];
|
|
15
16
|
requireAuthForMetrics?: boolean;
|
|
17
|
+
/** Native TLS termination (https/wss). Protocol and routes are unchanged. */
|
|
18
|
+
tls?: TlsServerOptions;
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
18
21
|
* Create and start HTTP server
|
|
@@ -10,6 +10,7 @@ import { SseHandler } from './sseHandler';
|
|
|
10
10
|
import { WsHandler } from './wsHandler';
|
|
11
11
|
import { jsonResponse, corsResponse, healthEndpoint, gcEndpoint, heapStatsEndpoint, statsEndpoint, metricsEndpoint, dashboardOverviewEndpoint, dashboardQueuesEndpoint, dashboardQueueDetailEndpoint, } from './httpEndpoints';
|
|
12
12
|
import { routeJobRoutes } from './httpRouteJobs';
|
|
13
|
+
import { loadTlsOptions } from './tls';
|
|
13
14
|
import { routeQueueRoutes } from './httpRouteQueues';
|
|
14
15
|
import { routeQueueConfigRoutes } from './httpRouteQueueConfig';
|
|
15
16
|
import { routeResourceRoutes } from './httpRouteResources';
|
|
@@ -202,15 +203,22 @@ export function createHttpServer(queueManager, config) {
|
|
|
202
203
|
});
|
|
203
204
|
},
|
|
204
205
|
};
|
|
205
|
-
// Create server
|
|
206
|
+
// Create server (validate TLS files BEFORE binding, fail fast on bad paths)
|
|
207
|
+
const tlsOptions = config.tls ? loadTlsOptions(config.tls) : undefined;
|
|
206
208
|
let server;
|
|
207
209
|
if (config.socketPath) {
|
|
208
|
-
server = Bun.serve({
|
|
210
|
+
server = Bun.serve({
|
|
211
|
+
unix: config.socketPath,
|
|
212
|
+
...(tlsOptions && { tls: tlsOptions }),
|
|
213
|
+
fetch,
|
|
214
|
+
websocket,
|
|
215
|
+
});
|
|
209
216
|
}
|
|
210
217
|
else {
|
|
211
218
|
server = Bun.serve({
|
|
212
219
|
hostname: config.hostname ?? '0.0.0.0',
|
|
213
220
|
port: config.port ?? 6790,
|
|
221
|
+
...(tlsOptions && { tls: tlsOptions }),
|
|
214
222
|
fetch,
|
|
215
223
|
websocket,
|
|
216
224
|
});
|
|
@@ -9,6 +9,7 @@ import { type HandlerContext } from './handler';
|
|
|
9
9
|
import { FrameParser, type ConnectionState } from './protocol';
|
|
10
10
|
import { Semaphore } from '../../shared/semaphore';
|
|
11
11
|
import { SocketWriteQueue } from './socketWriteQueue';
|
|
12
|
+
import { type TlsServerOptions } from './tls';
|
|
12
13
|
/** TCP Server configuration */
|
|
13
14
|
export interface TcpServerConfig {
|
|
14
15
|
/** TCP port */
|
|
@@ -29,6 +30,11 @@ export interface TcpServerConfig {
|
|
|
29
30
|
* Mainly for tests.
|
|
30
31
|
*/
|
|
31
32
|
maxWriteQueueBytes?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Native TLS termination. When set, the server only accepts TLS clients —
|
|
35
|
+
* the msgpack protocol is unchanged, only the transport is wrapped.
|
|
36
|
+
*/
|
|
37
|
+
tls?: TlsServerOptions;
|
|
32
38
|
}
|
|
33
39
|
/** Per-connection data */
|
|
34
40
|
interface ConnectionData {
|
|
@@ -11,6 +11,7 @@ import { getRateLimiter } from './rateLimiter';
|
|
|
11
11
|
import { pack, unpack } from 'msgpackr';
|
|
12
12
|
import { Semaphore, withSemaphore } from '../../shared/semaphore';
|
|
13
13
|
import { SocketWriteQueue } from './socketWriteQueue';
|
|
14
|
+
import { loadTlsOptions } from './tls';
|
|
14
15
|
/** Max concurrent commands per connection for pipelining */
|
|
15
16
|
const MAX_CONCURRENT_PER_CONNECTION = 50;
|
|
16
17
|
/**
|
|
@@ -236,10 +237,13 @@ export function createTcpServer(queueManager, config) {
|
|
|
236
237
|
socket.data.writeQueue.flush(socket);
|
|
237
238
|
},
|
|
238
239
|
};
|
|
239
|
-
// Create TCP server
|
|
240
|
+
// Create TCP server (validate TLS files BEFORE binding the port, so a bad
|
|
241
|
+
// path doesn't leave a half-started listener behind)
|
|
242
|
+
const tlsOptions = config.tls ? loadTlsOptions(config.tls) : undefined;
|
|
240
243
|
const server = Bun.listen({
|
|
241
244
|
hostname: config.hostname ?? '0.0.0.0',
|
|
242
245
|
port: config.port ?? 6789,
|
|
246
|
+
...(tlsOptions && { tls: tlsOptions }),
|
|
243
247
|
socket: socketHandlers,
|
|
244
248
|
});
|
|
245
249
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server TLS options
|
|
3
|
+
* Shared by the TCP (Bun.listen) and HTTP (Bun.serve) servers.
|
|
4
|
+
*/
|
|
5
|
+
import type { BunFile } from 'bun';
|
|
6
|
+
/** TLS configuration for a server (paths to PEM files) */
|
|
7
|
+
export interface TlsServerOptions {
|
|
8
|
+
/** Path to the PEM certificate (or full chain) file */
|
|
9
|
+
certFile: string;
|
|
10
|
+
/** Path to the PEM private key file */
|
|
11
|
+
keyFile: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate cert/key paths and build the `tls` option object for
|
|
15
|
+
* Bun.listen/Bun.serve. Fails fast with a descriptive error so a typo in a
|
|
16
|
+
* path surfaces at startup instead of as an opaque handshake failure.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadTlsOptions(tls: TlsServerOptions): {
|
|
19
|
+
cert: BunFile;
|
|
20
|
+
key: BunFile;
|
|
21
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server TLS options
|
|
3
|
+
* Shared by the TCP (Bun.listen) and HTTP (Bun.serve) servers.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
/**
|
|
7
|
+
* Validate cert/key paths and build the `tls` option object for
|
|
8
|
+
* Bun.listen/Bun.serve. Fails fast with a descriptive error so a typo in a
|
|
9
|
+
* path surfaces at startup instead of as an opaque handshake failure.
|
|
10
|
+
*/
|
|
11
|
+
export function loadTlsOptions(tls) {
|
|
12
|
+
if (!existsSync(tls.certFile)) {
|
|
13
|
+
throw new Error(`TLS cert file not found: ${tls.certFile}`);
|
|
14
|
+
}
|
|
15
|
+
if (!existsSync(tls.keyFile)) {
|
|
16
|
+
throw new Error(`TLS key file not found: ${tls.keyFile}`);
|
|
17
|
+
}
|
|
18
|
+
return { cert: Bun.file(tls.certFile), key: Bun.file(tls.keyFile) };
|
|
19
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -48,7 +48,7 @@ import { VERSION } from './shared/version';
|
|
|
48
48
|
import { S3BackupManager } from './infrastructure/backup';
|
|
49
49
|
import { CloudAgent } from './infrastructure/cloud';
|
|
50
50
|
import { SHARD_COUNT } from './shared/hash';
|
|
51
|
-
import { loadConfigFile, resolveServerConfig, resolveCloudConfig, resolveBackupConfig, } from './config';
|
|
51
|
+
import { loadConfigFile, resolveServerConfig, resolveCloudConfig, resolveBackupConfig, resolveTlsServerOptions, } from './config';
|
|
52
52
|
export { defineConfig } from './config';
|
|
53
53
|
/** Print startup banner */
|
|
54
54
|
function printBanner(config, cloudUrl) {
|
|
@@ -82,6 +82,7 @@ ${dim}────────────────────────
|
|
|
82
82
|
${green}●${reset} HTTP ${httpDisplay}
|
|
83
83
|
${yellow}●${reset} Socket ${socketDisplay}
|
|
84
84
|
${yellow}●${reset} Data ${config.dataPath ?? 'in-memory'}
|
|
85
|
+
${yellow}●${reset} TLS ${config.tlsCertFile ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
85
86
|
${yellow}●${reset} Auth ${config.authTokens.length > 0 ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
86
87
|
${yellow}●${reset} S3 Backup ${config.s3BackupEnabled ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
87
88
|
${yellow}●${reset} Cloud ${cloudUrl ? `${green}enabled${reset} ${dim}→ ${cloudUrl}${reset}` : `${dim}disabled${reset}`}
|
|
@@ -108,6 +109,15 @@ async function startServer() {
|
|
|
108
109
|
}
|
|
109
110
|
// Resolve cloud config
|
|
110
111
|
const cloudConfig = resolveCloudConfig(fileConfig, config.dataPath);
|
|
112
|
+
// Resolve TLS config — fail fast on partial cert/key before binding anything
|
|
113
|
+
let tlsConfig;
|
|
114
|
+
try {
|
|
115
|
+
tlsConfig = resolveTlsServerOptions(config);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
serverLog.error(err instanceof Error ? err.message : String(err));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
111
121
|
printBanner(config, cloudConfig?.url);
|
|
112
122
|
// Create queue manager
|
|
113
123
|
const queueManager = new QueueManager({
|
|
@@ -118,6 +128,7 @@ async function startServer() {
|
|
|
118
128
|
port: config.tcpPort,
|
|
119
129
|
hostname: config.hostname,
|
|
120
130
|
authTokens: config.authTokens,
|
|
131
|
+
...(tlsConfig && { tls: tlsConfig }),
|
|
121
132
|
});
|
|
122
133
|
// Start HTTP server
|
|
123
134
|
const httpServer = createHttpServer(queueManager, {
|
|
@@ -126,6 +137,7 @@ async function startServer() {
|
|
|
126
137
|
authTokens: config.authTokens,
|
|
127
138
|
corsOrigins: config.corsOrigins,
|
|
128
139
|
requireAuthForMetrics: config.requireAuthForMetrics,
|
|
140
|
+
...(tlsConfig && { tls: tlsConfig }),
|
|
129
141
|
});
|
|
130
142
|
// Initialize S3 backup manager
|
|
131
143
|
let backupManager = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunqueue",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8",
|
|
4
4
|
"description": "High-performance job queue for Bun & AI agents. SQLite persistence, cron scheduling, priorities, retries, DLQ, webhooks, native MCP server. Zero external dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|