@jeffusion/bungee 5.24.2 → 5.25.0
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/index.js +179 -67
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2146,7 +2146,7 @@ var {
|
|
|
2146
2146
|
} = exports_commander;
|
|
2147
2147
|
|
|
2148
2148
|
// src/commands/recover.ts
|
|
2149
|
-
import { resolve as resolve4, join as
|
|
2149
|
+
import { resolve as resolve4, join as join8, dirname as dirname3 } from "node:path";
|
|
2150
2150
|
import { spawn as spawn3 } from "node:child_process";
|
|
2151
2151
|
|
|
2152
2152
|
// src/config/paths.ts
|
|
@@ -2178,10 +2178,10 @@ class ConfigPaths {
|
|
|
2178
2178
|
// src/daemon/manager.ts
|
|
2179
2179
|
import fs2 from "node:fs";
|
|
2180
2180
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
2181
|
-
import { randomBytes as
|
|
2181
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID3 } from "node:crypto";
|
|
2182
2182
|
import { spawn as spawn2 } from "node:child_process";
|
|
2183
2183
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
2184
|
-
import { basename as basename2, isAbsolute as isAbsolute4, join as
|
|
2184
|
+
import { basename as basename2, isAbsolute as isAbsolute4, join as join7 } from "node:path";
|
|
2185
2185
|
import { isIP } from "node:net";
|
|
2186
2186
|
|
|
2187
2187
|
// ../types/dist/daemon-control.js
|
|
@@ -3249,6 +3249,10 @@ function defaultWindowsAclAdapter(deadlineMs = WINDOWS_ACL_DEADLINE_MS) {
|
|
|
3249
3249
|
}
|
|
3250
3250
|
};
|
|
3251
3251
|
}
|
|
3252
|
+
async function secureWindowsOwnerOnlyFileAcl(path, adapter = defaultWindowsAclAdapter()) {
|
|
3253
|
+
await ensureWindowsAcl(path, adapter, "file");
|
|
3254
|
+
await assertWindowsOwnerOnlyFileAcl(path, adapter);
|
|
3255
|
+
}
|
|
3252
3256
|
async function assertWindowsOwnerOnlyFileAcl(path, adapter = defaultWindowsAclAdapter()) {
|
|
3253
3257
|
const snapshot = await adapter.read(path);
|
|
3254
3258
|
if (!canonicalSid(snapshot.ownerSid) || snapshot.ownerSid !== snapshot.currentSid)
|
|
@@ -3388,16 +3392,117 @@ async function createLaunchingDaemonMetadataFile(path, metadata, options) {
|
|
|
3388
3392
|
}
|
|
3389
3393
|
}
|
|
3390
3394
|
|
|
3395
|
+
// src/config/plugin-secrets-key.ts
|
|
3396
|
+
import { randomBytes } from "node:crypto";
|
|
3397
|
+
import { constants } from "node:fs";
|
|
3398
|
+
import { lstat as lstat2, mkdir as mkdir2, open as open2, unlink as unlink2 } from "node:fs/promises";
|
|
3399
|
+
import { dirname, join as join2 } from "node:path";
|
|
3400
|
+
var UNSAFE_FILE = "Plugin secrets key must be an owner-only regular file containing a canonical Base64 key for 32 bytes";
|
|
3401
|
+
function pluginSecretsKeyPath(configDbPath) {
|
|
3402
|
+
return join2(dirname(configDbPath), "plugin-secrets.key");
|
|
3403
|
+
}
|
|
3404
|
+
function withPluginSecretsKey(environment, key) {
|
|
3405
|
+
return {
|
|
3406
|
+
...Object.fromEntries(Object.entries(environment).filter(([name]) => name.toLowerCase() !== "bungee_plugin_secrets_key")),
|
|
3407
|
+
BUNGEE_PLUGIN_SECRETS_KEY: key
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3410
|
+
function validateKey(value) {
|
|
3411
|
+
const bytes = Buffer.from(value, "base64");
|
|
3412
|
+
if (bytes.length !== 32 || bytes.toString("base64") !== value) {
|
|
3413
|
+
throw new Error("Plugin secrets key must be canonical Base64 for 32 bytes");
|
|
3414
|
+
}
|
|
3415
|
+
return value;
|
|
3416
|
+
}
|
|
3417
|
+
async function readKeyFile(path, options) {
|
|
3418
|
+
const platform = options.platform ?? process.platform;
|
|
3419
|
+
const before = await lstat2(path);
|
|
3420
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1)
|
|
3421
|
+
throw new Error(UNSAFE_FILE);
|
|
3422
|
+
const file = await open2(path, constants.O_RDONLY | (platform === "win32" ? 0 : constants.O_NOFOLLOW | constants.O_NONBLOCK));
|
|
3423
|
+
try {
|
|
3424
|
+
const opened = await file.stat();
|
|
3425
|
+
const verify = (item) => {
|
|
3426
|
+
if (!item.isFile() || item.nlink !== 1 || item.dev !== before.dev || item.ino !== before.ino || item.size !== 44 || platform !== "win32" && ((item.mode & 511) !== 384 || process.geteuid && item.uid !== process.geteuid()))
|
|
3427
|
+
throw new Error(UNSAFE_FILE);
|
|
3428
|
+
};
|
|
3429
|
+
verify(opened);
|
|
3430
|
+
if (platform === "win32")
|
|
3431
|
+
await assertWindowsOwnerOnlyFileAcl(path, options.windowsAcl);
|
|
3432
|
+
const value = await file.readFile("utf8");
|
|
3433
|
+
verify(await file.stat());
|
|
3434
|
+
const after = await lstat2(path);
|
|
3435
|
+
verify(after);
|
|
3436
|
+
return validateKey(value);
|
|
3437
|
+
} finally {
|
|
3438
|
+
await file.close();
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
async function readPluginSecretsKey(configDbPath, options = {}) {
|
|
3442
|
+
try {
|
|
3443
|
+
return await readKeyFile(pluginSecretsKeyPath(configDbPath), options);
|
|
3444
|
+
} catch (error) {
|
|
3445
|
+
if (error.code === "ENOENT")
|
|
3446
|
+
throw new Error("Plugin secrets key is missing. Run bungee init first.");
|
|
3447
|
+
throw error;
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
async function initializePluginSecretsKey(configDbPath, suppliedKey, options = {}) {
|
|
3451
|
+
if (suppliedKey !== undefined)
|
|
3452
|
+
validateKey(suppliedKey);
|
|
3453
|
+
const path = pluginSecretsKeyPath(configDbPath);
|
|
3454
|
+
try {
|
|
3455
|
+
const existing = await readKeyFile(path, options);
|
|
3456
|
+
if (suppliedKey !== undefined && suppliedKey !== existing)
|
|
3457
|
+
throw new Error("Plugin secrets key is already initialized with a different key");
|
|
3458
|
+
return existing;
|
|
3459
|
+
} catch (error) {
|
|
3460
|
+
if (error.code !== "ENOENT")
|
|
3461
|
+
throw error;
|
|
3462
|
+
}
|
|
3463
|
+
if (suppliedKey === undefined) {
|
|
3464
|
+
for (const database of [configDbPath, join2(dirname(configDbPath), "plugin-state.db")]) {
|
|
3465
|
+
try {
|
|
3466
|
+
await lstat2(database);
|
|
3467
|
+
throw new Error("An existing database requires its original plugin secrets key when running bungee init");
|
|
3468
|
+
} catch (error) {
|
|
3469
|
+
if (error.code !== "ENOENT")
|
|
3470
|
+
throw error;
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
const key = suppliedKey ?? randomBytes(32).toString("base64");
|
|
3475
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
3476
|
+
const file = await open2(path, "wx", 384);
|
|
3477
|
+
const created = await file.stat();
|
|
3478
|
+
try {
|
|
3479
|
+
if ((options.platform ?? process.platform) === "win32")
|
|
3480
|
+
await secureWindowsOwnerOnlyFileAcl(path, options.windowsAcl);
|
|
3481
|
+
else
|
|
3482
|
+
await file.chmod(384);
|
|
3483
|
+
await file.writeFile(key);
|
|
3484
|
+
await file.sync();
|
|
3485
|
+
} catch (error) {
|
|
3486
|
+
const current = await lstat2(path);
|
|
3487
|
+
if (current.dev === created.dev && current.ino === created.ino)
|
|
3488
|
+
await unlink2(path);
|
|
3489
|
+
throw error;
|
|
3490
|
+
} finally {
|
|
3491
|
+
await file.close();
|
|
3492
|
+
}
|
|
3493
|
+
return readKeyFile(path, options);
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3391
3496
|
// src/binary/manager.ts
|
|
3392
3497
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3393
3498
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, writeFileSync } from "node:fs";
|
|
3394
3499
|
import { homedir } from "node:os";
|
|
3395
|
-
import { join as
|
|
3500
|
+
import { join as join4 } from "node:path";
|
|
3396
3501
|
import { createInterface } from "node:readline";
|
|
3397
3502
|
// package.json
|
|
3398
3503
|
var package_default = {
|
|
3399
3504
|
name: "@jeffusion/bungee",
|
|
3400
|
-
version: "5.
|
|
3505
|
+
version: "5.25.0",
|
|
3401
3506
|
description: "High-performance reverse proxy server CLI",
|
|
3402
3507
|
type: "module",
|
|
3403
3508
|
bin: {
|
|
@@ -3453,7 +3558,7 @@ import {
|
|
|
3453
3558
|
renameSync,
|
|
3454
3559
|
rmSync
|
|
3455
3560
|
} from "node:fs";
|
|
3456
|
-
import { basename, join as
|
|
3561
|
+
import { basename, join as join3 } from "node:path";
|
|
3457
3562
|
function runTar(args) {
|
|
3458
3563
|
const result = spawnSync("tar", args, { encoding: "utf8" });
|
|
3459
3564
|
if (result.status !== 0) {
|
|
@@ -3483,8 +3588,8 @@ function validateListing(listing, binaryName) {
|
|
|
3483
3588
|
}
|
|
3484
3589
|
}
|
|
3485
3590
|
function validateExtractedTree(directory, binaryName) {
|
|
3486
|
-
const executable =
|
|
3487
|
-
const plugins =
|
|
3591
|
+
const executable = join3(directory, binaryName);
|
|
3592
|
+
const plugins = join3(directory, "plugins");
|
|
3488
3593
|
if (!lstatSync(executable).isFile() || !lstatSync(plugins).isDirectory()) {
|
|
3489
3594
|
throw new Error("Binary archive has an invalid root layout");
|
|
3490
3595
|
}
|
|
@@ -3495,7 +3600,7 @@ function validateExtractedTree(directory, binaryName) {
|
|
|
3495
3600
|
}
|
|
3496
3601
|
const visit = (current) => {
|
|
3497
3602
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
3498
|
-
const target =
|
|
3603
|
+
const target = join3(current, entry.name);
|
|
3499
3604
|
if (entry.isSymbolicLink())
|
|
3500
3605
|
throw new Error(`Binary archive contains a symlink: ${entry.name}`);
|
|
3501
3606
|
if (entry.isDirectory())
|
|
@@ -3506,8 +3611,8 @@ function validateExtractedTree(directory, binaryName) {
|
|
|
3506
3611
|
for (const plugin of readdirSync(plugins, { withFileTypes: true })) {
|
|
3507
3612
|
if (!plugin.isDirectory())
|
|
3508
3613
|
throw new Error(`Invalid plugin artifact: ${plugin.name}`);
|
|
3509
|
-
const pluginDirectory =
|
|
3510
|
-
if (!existsSync2(
|
|
3614
|
+
const pluginDirectory = join3(plugins, plugin.name);
|
|
3615
|
+
if (!existsSync2(join3(pluginDirectory, "manifest.json")) || !existsSync2(join3(pluginDirectory, "index.js"))) {
|
|
3511
3616
|
throw new Error(`Incomplete plugin artifact: ${plugin.name}`);
|
|
3512
3617
|
}
|
|
3513
3618
|
}
|
|
@@ -3517,15 +3622,15 @@ function installBinaryArchive(options) {
|
|
|
3517
3622
|
throw new Error("Invalid binary install identity");
|
|
3518
3623
|
}
|
|
3519
3624
|
mkdirSync(options.installRoot, { recursive: true });
|
|
3520
|
-
const target =
|
|
3521
|
-
const staging = mkdtempSync(
|
|
3625
|
+
const target = join3(options.installRoot, options.version);
|
|
3626
|
+
const staging = mkdtempSync(join3(options.installRoot, `${options.version}.staging-`));
|
|
3522
3627
|
const backup = `${target}.backup-${randomUUID()}`;
|
|
3523
3628
|
let backedUp = false;
|
|
3524
3629
|
try {
|
|
3525
3630
|
validateListing(runTar(["-tzf", options.archivePath]), options.binaryName);
|
|
3526
3631
|
runTar(["-xzf", options.archivePath, "-C", staging]);
|
|
3527
3632
|
validateExtractedTree(staging, options.binaryName);
|
|
3528
|
-
chmodSync(
|
|
3633
|
+
chmodSync(join3(staging, options.binaryName), 493);
|
|
3529
3634
|
if (existsSync2(target)) {
|
|
3530
3635
|
renameSync(target, backup);
|
|
3531
3636
|
backedUp = true;
|
|
@@ -3539,7 +3644,7 @@ function installBinaryArchive(options) {
|
|
|
3539
3644
|
}
|
|
3540
3645
|
if (backedUp)
|
|
3541
3646
|
rmSync(backup, { recursive: true, force: true });
|
|
3542
|
-
return
|
|
3647
|
+
return join3(target, options.binaryName);
|
|
3543
3648
|
} finally {
|
|
3544
3649
|
rmSync(staging, { recursive: true, force: true });
|
|
3545
3650
|
}
|
|
@@ -3573,8 +3678,8 @@ function getAssetName(binaryName = getBinaryName()) {
|
|
|
3573
3678
|
|
|
3574
3679
|
// src/binary/manager.ts
|
|
3575
3680
|
class BinaryManager {
|
|
3576
|
-
static BINARY_DIR =
|
|
3577
|
-
static VERSION_FILE =
|
|
3681
|
+
static BINARY_DIR = join4(homedir(), ".bungee", "bin");
|
|
3682
|
+
static VERSION_FILE = join4(homedir(), ".bungee", "version.txt");
|
|
3578
3683
|
static GITHUB_REPO = "jeffusion/bungee";
|
|
3579
3684
|
static getBinaryName() {
|
|
3580
3685
|
return getBinaryName();
|
|
@@ -3583,7 +3688,7 @@ class BinaryManager {
|
|
|
3583
3688
|
return getAssetName(this.getBinaryName());
|
|
3584
3689
|
}
|
|
3585
3690
|
static getLocalBinaryPath() {
|
|
3586
|
-
return
|
|
3691
|
+
return join4(this.BINARY_DIR, package_default.version, this.getBinaryName());
|
|
3587
3692
|
}
|
|
3588
3693
|
static isBinaryInstalled() {
|
|
3589
3694
|
return existsSync3(this.getLocalBinaryPath());
|
|
@@ -3596,7 +3701,7 @@ class BinaryManager {
|
|
|
3596
3701
|
}
|
|
3597
3702
|
}
|
|
3598
3703
|
static saveVersion(version) {
|
|
3599
|
-
mkdirSync2(
|
|
3704
|
+
mkdirSync2(join4(homedir(), ".bungee"), { recursive: true });
|
|
3600
3705
|
writeFileSync(this.VERSION_FILE, version);
|
|
3601
3706
|
}
|
|
3602
3707
|
static checkVersion() {
|
|
@@ -3617,7 +3722,7 @@ class BinaryManager {
|
|
|
3617
3722
|
static async downloadBinary(options = {}) {
|
|
3618
3723
|
const url = options.assetUrl ?? this.getDownloadUrl();
|
|
3619
3724
|
mkdirSync2(this.BINARY_DIR, { recursive: true });
|
|
3620
|
-
const archivePath =
|
|
3725
|
+
const archivePath = join4(this.BINARY_DIR, `.download-${randomUUID2()}.tar.gz`);
|
|
3621
3726
|
if (!options.silent) {
|
|
3622
3727
|
console.log(`Downloading Bungee v${package_default.version} from ${url}`);
|
|
3623
3728
|
}
|
|
@@ -3679,7 +3784,7 @@ class BinaryManager {
|
|
|
3679
3784
|
}
|
|
3680
3785
|
|
|
3681
3786
|
// src/daemon/runtime.ts
|
|
3682
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3787
|
+
import { isAbsolute as isAbsolute2, join as join5, resolve as resolve2 } from "node:path";
|
|
3683
3788
|
function createDaemonRuntime(options) {
|
|
3684
3789
|
if (!isAbsolute2(options.dataDirectory))
|
|
3685
3790
|
throw new Error("Daemon data directory must be absolute");
|
|
@@ -3696,8 +3801,8 @@ function createDaemonRuntime(options) {
|
|
|
3696
3801
|
cwd: options.dataDirectory,
|
|
3697
3802
|
env: {
|
|
3698
3803
|
...inheritedEnvironment,
|
|
3699
|
-
BUNGEE_CONFIG_DB_PATH: options.inheritedEnvironment?.BUNGEE_CONFIG_DB_PATH ? resolve2(options.inheritedEnvironment.BUNGEE_CONFIG_DB_PATH) :
|
|
3700
|
-
BUNGEE_ACCESS_DB_PATH:
|
|
3804
|
+
BUNGEE_CONFIG_DB_PATH: options.inheritedEnvironment?.BUNGEE_CONFIG_DB_PATH ? resolve2(options.inheritedEnvironment.BUNGEE_CONFIG_DB_PATH) : join5(options.dataDirectory, "bungee.db"),
|
|
3805
|
+
BUNGEE_ACCESS_DB_PATH: join5(options.logsDirectory, "access.db"),
|
|
3701
3806
|
WORKER_COUNT: options.workers ?? "2",
|
|
3702
3807
|
DAEMON_MODE: "true",
|
|
3703
3808
|
...options.port === undefined ? {} : { PORT: options.port }
|
|
@@ -3707,9 +3812,9 @@ function createDaemonRuntime(options) {
|
|
|
3707
3812
|
|
|
3708
3813
|
// src/daemon/pid-mirror.ts
|
|
3709
3814
|
import { constants as fsConstants2 } from "node:fs";
|
|
3710
|
-
import { lstat as
|
|
3711
|
-
import { dirname, isAbsolute as isAbsolute3, parse as parse2, resolve as resolve3 } from "node:path";
|
|
3712
|
-
import { randomBytes } from "node:crypto";
|
|
3815
|
+
import { lstat as lstat3, open as open3, rename as rename2, unlink as unlink3 } from "node:fs/promises";
|
|
3816
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, parse as parse2, resolve as resolve3 } from "node:path";
|
|
3817
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3713
3818
|
var MAX_PID_BYTES = 64;
|
|
3714
3819
|
var UNSUPPORTED_DIR_FSYNC = new Set(["EINVAL", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EPERM", "EISDIR"]);
|
|
3715
3820
|
function identity2(stat) {
|
|
@@ -3728,7 +3833,7 @@ async function rejectSymlinkComponents2(path) {
|
|
|
3728
3833
|
for (const part of absolute.slice(root.length).split(/[\\/]+/).filter(Boolean)) {
|
|
3729
3834
|
current = resolve3(current, part);
|
|
3730
3835
|
try {
|
|
3731
|
-
if ((await
|
|
3836
|
+
if ((await lstat3(current)).isSymbolicLink())
|
|
3732
3837
|
throw new Error("PID mirror path contains a symlink");
|
|
3733
3838
|
} catch (error) {
|
|
3734
3839
|
if (error.code === "ENOENT")
|
|
@@ -3742,7 +3847,7 @@ async function syncDirectory(directory, platform) {
|
|
|
3742
3847
|
return;
|
|
3743
3848
|
let handle;
|
|
3744
3849
|
try {
|
|
3745
|
-
handle = await
|
|
3850
|
+
handle = await open3(directory, fsConstants2.O_RDONLY | (fsConstants2.O_DIRECTORY ?? 0));
|
|
3746
3851
|
await handle.sync();
|
|
3747
3852
|
} catch (error) {
|
|
3748
3853
|
if (!UNSUPPORTED_DIR_FSYNC.has(error.code ?? ""))
|
|
@@ -3761,7 +3866,7 @@ async function syncDirectory(directory, platform) {
|
|
|
3761
3866
|
async function readLegacyPidFile(path) {
|
|
3762
3867
|
let listed;
|
|
3763
3868
|
try {
|
|
3764
|
-
listed = await
|
|
3869
|
+
listed = await lstat3(path);
|
|
3765
3870
|
} catch (error) {
|
|
3766
3871
|
const code = error.code;
|
|
3767
3872
|
return code === "ENOENT" ? { kind: "absent" } : { kind: "unknown" };
|
|
@@ -3772,7 +3877,7 @@ async function readLegacyPidFile(path) {
|
|
|
3772
3877
|
const noFollow = process.platform === "win32" ? 0 : fsConstants2.O_NOFOLLOW;
|
|
3773
3878
|
let handle;
|
|
3774
3879
|
try {
|
|
3775
|
-
handle = await
|
|
3880
|
+
handle = await open3(path, fsConstants2.O_RDONLY | noFollow);
|
|
3776
3881
|
const opened = await handle.stat();
|
|
3777
3882
|
if (!regular(opened) || !sameIdentity2(expected, identity2(opened)))
|
|
3778
3883
|
return { kind: "unsafe" };
|
|
@@ -3797,10 +3902,10 @@ async function readLegacyPidFile(path) {
|
|
|
3797
3902
|
}
|
|
3798
3903
|
async function deleteLegacyPidFile(path, expected) {
|
|
3799
3904
|
try {
|
|
3800
|
-
const current = await
|
|
3905
|
+
const current = await lstat3(path);
|
|
3801
3906
|
if (!regular(current) || current.isSymbolicLink() || !sameIdentity2(identity2(current), expected))
|
|
3802
3907
|
return false;
|
|
3803
|
-
await
|
|
3908
|
+
await unlink3(path);
|
|
3804
3909
|
return true;
|
|
3805
3910
|
} catch {
|
|
3806
3911
|
return false;
|
|
@@ -3810,10 +3915,10 @@ async function writeLegacyPidMirror(path, pid, options = {}) {
|
|
|
3810
3915
|
if (!Number.isSafeInteger(pid) || pid <= 0 || !isAbsolute3(path) || path.includes("\x00"))
|
|
3811
3916
|
throw new Error("invalid PID mirror target");
|
|
3812
3917
|
const target = resolve3(path);
|
|
3813
|
-
const directory =
|
|
3918
|
+
const directory = dirname2(target);
|
|
3814
3919
|
await rejectSymlinkComponents2(directory);
|
|
3815
|
-
const temporary = `${target}.${process.pid}.${
|
|
3816
|
-
const handle = await
|
|
3920
|
+
const temporary = `${target}.${process.pid}.${randomBytes2(16).toString("hex")}.tmp`;
|
|
3921
|
+
const handle = await open3(temporary, fsConstants2.O_WRONLY | fsConstants2.O_CREAT | fsConstants2.O_EXCL, 384);
|
|
3817
3922
|
try {
|
|
3818
3923
|
await handle.writeFile(String(pid));
|
|
3819
3924
|
await handle.chmod(384);
|
|
@@ -3829,7 +3934,7 @@ async function writeLegacyPidMirror(path, pid, options = {}) {
|
|
|
3829
3934
|
await handle.close();
|
|
3830
3935
|
} catch {}
|
|
3831
3936
|
try {
|
|
3832
|
-
await
|
|
3937
|
+
await unlink3(temporary);
|
|
3833
3938
|
} catch {}
|
|
3834
3939
|
throw error;
|
|
3835
3940
|
}
|
|
@@ -3837,7 +3942,7 @@ async function writeLegacyPidMirror(path, pid, options = {}) {
|
|
|
3837
3942
|
|
|
3838
3943
|
// src/daemon/process-identity.ts
|
|
3839
3944
|
import { existsSync as existsSync4 } from "node:fs";
|
|
3840
|
-
import { lstat as
|
|
3945
|
+
import { lstat as lstat4, realpath as realpath2 } from "node:fs/promises";
|
|
3841
3946
|
import { readdir, readFile } from "node:fs/promises";
|
|
3842
3947
|
import { execFile as nodeExecFile } from "node:child_process";
|
|
3843
3948
|
import { promisify } from "node:util";
|
|
@@ -4108,7 +4213,7 @@ async function probeProcessAlive(pid, options = {}) {
|
|
|
4108
4213
|
return "dead";
|
|
4109
4214
|
if ((options.platform ?? process.platform) === "linux") {
|
|
4110
4215
|
try {
|
|
4111
|
-
await
|
|
4216
|
+
await lstat4(`/proc/${pid}`);
|
|
4112
4217
|
return "alive";
|
|
4113
4218
|
} catch (error) {
|
|
4114
4219
|
return error.code === "ENOENT" ? "dead" : "unknown";
|
|
@@ -4229,7 +4334,7 @@ async function findExactDaemonProcessDetailed(bootNonce, expectedOrOptions, opti
|
|
|
4229
4334
|
// src/daemon/force-stop.ts
|
|
4230
4335
|
import { execFile as nodeExecFile3 } from "node:child_process";
|
|
4231
4336
|
import { realpathSync } from "node:fs";
|
|
4232
|
-
import { join as
|
|
4337
|
+
import { join as join6, win32 as win323 } from "node:path";
|
|
4233
4338
|
import { promisify as promisify3 } from "node:util";
|
|
4234
4339
|
|
|
4235
4340
|
// src/daemon/process-tree.ts
|
|
@@ -4540,7 +4645,7 @@ function canonicalTaskkillPath() {
|
|
|
4540
4645
|
throw new Error("Windows SystemRoot is unavailable");
|
|
4541
4646
|
}
|
|
4542
4647
|
})();
|
|
4543
|
-
const expected =
|
|
4648
|
+
const expected = join6(canonicalRoot, "System32", "taskkill.exe");
|
|
4544
4649
|
let actual;
|
|
4545
4650
|
try {
|
|
4546
4651
|
actual = realpathSync.native(expected);
|
|
@@ -5031,6 +5136,7 @@ class DaemonManager {
|
|
|
5031
5136
|
findProcessDetailed;
|
|
5032
5137
|
probePid;
|
|
5033
5138
|
injectedLaunch;
|
|
5139
|
+
readSecretsKey;
|
|
5034
5140
|
writePidMirror;
|
|
5035
5141
|
httpRequest;
|
|
5036
5142
|
forceStop;
|
|
@@ -5052,7 +5158,8 @@ class DaemonManager {
|
|
|
5052
5158
|
this.dataDirectory = dependencies.dataDirectory ?? ConfigPaths.DATA_DIR;
|
|
5053
5159
|
this.logsDirectory = dependencies.logsDirectory ?? ConfigPaths.LOGS_DIR;
|
|
5054
5160
|
this.inheritedEnvironment = dependencies.inheritedEnvironment ?? process.env;
|
|
5055
|
-
this.
|
|
5161
|
+
this.readSecretsKey = dependencies.readPluginSecretsKey ?? readPluginSecretsKey;
|
|
5162
|
+
this.metadataFile = join7(this.runtimeDirectory, "daemon.json");
|
|
5056
5163
|
this.now = dependencies.now ?? (() => performance.now());
|
|
5057
5164
|
this.sleep = dependencies.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
5058
5165
|
this.currentPid = dependencies.currentPid ?? (() => process.pid);
|
|
@@ -5315,9 +5422,17 @@ class DaemonManager {
|
|
|
5315
5422
|
}
|
|
5316
5423
|
async start(options = {}) {
|
|
5317
5424
|
await this.inspectExisting();
|
|
5425
|
+
const runtime = createDaemonRuntime({
|
|
5426
|
+
dataDirectory: this.dataDirectory,
|
|
5427
|
+
logsDirectory: this.logsDirectory,
|
|
5428
|
+
workers: options.workers,
|
|
5429
|
+
port: options.port,
|
|
5430
|
+
inheritedEnvironment: this.inheritedEnvironment
|
|
5431
|
+
});
|
|
5432
|
+
const key = await this.readSecretsKey(runtime.env.BUNGEE_CONFIG_DB_PATH);
|
|
5318
5433
|
const descriptor = await this.launchDescriptor(options);
|
|
5319
5434
|
const bootNonce = randomUUID3().toLowerCase();
|
|
5320
|
-
const shutdownSecret = encodeDaemonShutdownSecret(
|
|
5435
|
+
const shutdownSecret = encodeDaemonShutdownSecret(randomBytes3(32));
|
|
5321
5436
|
const launching = {
|
|
5322
5437
|
schema: "bungee-daemon-metadata-v1",
|
|
5323
5438
|
state: "launching",
|
|
@@ -5340,15 +5455,8 @@ class DaemonManager {
|
|
|
5340
5455
|
}
|
|
5341
5456
|
throw error;
|
|
5342
5457
|
}
|
|
5343
|
-
const runtime = createDaemonRuntime({
|
|
5344
|
-
dataDirectory: this.dataDirectory,
|
|
5345
|
-
logsDirectory: this.logsDirectory,
|
|
5346
|
-
workers: options.workers,
|
|
5347
|
-
port: options.port,
|
|
5348
|
-
inheritedEnvironment: this.inheritedEnvironment
|
|
5349
|
-
});
|
|
5350
5458
|
const env = {
|
|
5351
|
-
...runtime.env,
|
|
5459
|
+
...withPluginSecretsKey(runtime.env, key),
|
|
5352
5460
|
BUNGEE_ROLE: "master",
|
|
5353
5461
|
BUNGEE_DAEMON_METADATA_PATH: this.metadataFile,
|
|
5354
5462
|
BUNGEE_DAEMON_BOOT_NONCE: bootNonce,
|
|
@@ -5770,15 +5878,15 @@ class DaemonManager {
|
|
|
5770
5878
|
}
|
|
5771
5879
|
|
|
5772
5880
|
// src/commands/recovery-file.ts
|
|
5773
|
-
import { constants } from "node:fs";
|
|
5774
|
-
import { lstat as
|
|
5881
|
+
import { constants as constants2 } from "node:fs";
|
|
5882
|
+
import { lstat as lstat5, open as open4 } from "node:fs/promises";
|
|
5775
5883
|
var UNSAFE_INPUT = "Recovery input must be an owner-only regular file of at most 8192 bytes";
|
|
5776
5884
|
async function openRecoveryInputFile(path, options = {}) {
|
|
5777
5885
|
const platform = options.platform ?? process.platform;
|
|
5778
|
-
const before = await
|
|
5886
|
+
const before = await lstat5(path);
|
|
5779
5887
|
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1)
|
|
5780
5888
|
throw new Error(UNSAFE_INPUT);
|
|
5781
|
-
const file = await
|
|
5889
|
+
const file = await open4(path, constants2.O_RDONLY | (platform === "win32" ? 0 : constants2.O_NOFOLLOW | constants2.O_NONBLOCK));
|
|
5782
5890
|
try {
|
|
5783
5891
|
const opened = await file.stat();
|
|
5784
5892
|
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== before.dev || opened.ino !== before.ino || opened.size > 8192)
|
|
@@ -5787,7 +5895,7 @@ async function openRecoveryInputFile(path, options = {}) {
|
|
|
5787
5895
|
await assertWindowsOwnerOnlyFileAcl(path, options.windowsAcl);
|
|
5788
5896
|
else if ((opened.mode & 511) !== 384 || process.getuid && opened.uid !== process.getuid())
|
|
5789
5897
|
throw new Error(UNSAFE_INPUT);
|
|
5790
|
-
const after = await
|
|
5898
|
+
const after = await lstat5(path);
|
|
5791
5899
|
const final = await file.stat();
|
|
5792
5900
|
if (!after.isFile() || after.isSymbolicLink() || after.nlink !== 1 || after.dev !== opened.dev || after.ino !== opened.ino || final.nlink !== 1 || final.size > 8192 || final.size !== opened.size || platform !== "win32" && ((final.mode & 511) !== 384 || process.getuid && final.uid !== process.getuid()))
|
|
5793
5901
|
throw new Error(UNSAFE_INPUT);
|
|
@@ -5800,15 +5908,16 @@ async function openRecoveryInputFile(path, options = {}) {
|
|
|
5800
5908
|
|
|
5801
5909
|
// src/commands/recover.ts
|
|
5802
5910
|
async function recoverCommand(options = {}) {
|
|
5803
|
-
const
|
|
5804
|
-
const db = resolve4(process.env.BUNGEE_CONFIG_DB_PATH ?? join7(ConfigPaths.DATA_DIR, "bungee.db"));
|
|
5911
|
+
const db = resolve4(process.env.BUNGEE_CONFIG_DB_PATH ?? join8(ConfigPaths.DATA_DIR, "bungee.db"));
|
|
5805
5912
|
let file;
|
|
5806
5913
|
try {
|
|
5807
5914
|
if (options.file) {
|
|
5808
5915
|
file = await openRecoveryInputFile(options.file);
|
|
5809
5916
|
}
|
|
5917
|
+
const key = await readPluginSecretsKey(db);
|
|
5918
|
+
const launch = await resolveCoreLaunchDescriptor(options);
|
|
5810
5919
|
await new Promise((accept, reject) => {
|
|
5811
|
-
const child = spawn3(launch.executable, [...launch.entrypoint === null ? [] : [launch.entrypoint], "--recover", db], { cwd:
|
|
5920
|
+
const child = spawn3(launch.executable, [...launch.entrypoint === null ? [] : [launch.entrypoint], "--recover", db], { cwd: dirname3(db), stdio: [file?.fd ?? "inherit", "inherit", "inherit"], env: { ...withPluginSecretsKey(process.env, key), BUNGEE_ROLE: "master", BUNGEE_ACCESS_DB_PATH: process.env.BUNGEE_ACCESS_DB_PATH ?? join8(ConfigPaths.LOGS_DIR, "access.db") } });
|
|
5812
5921
|
child.once("error", reject);
|
|
5813
5922
|
child.once("exit", (code) => code === 0 ? accept() : reject(new Error("Offline recovery failed")));
|
|
5814
5923
|
});
|
|
@@ -5818,22 +5927,25 @@ async function recoverCommand(options = {}) {
|
|
|
5818
5927
|
}
|
|
5819
5928
|
|
|
5820
5929
|
// src/commands/init.ts
|
|
5821
|
-
import { resolve as resolve5, join as
|
|
5930
|
+
import { resolve as resolve5, join as join9, dirname as dirname4 } from "node:path";
|
|
5822
5931
|
import { spawn as spawn4 } from "node:child_process";
|
|
5932
|
+
import { mkdir as mkdir3 } from "node:fs/promises";
|
|
5823
5933
|
async function initCommand(options = {}) {
|
|
5824
|
-
ConfigPaths.
|
|
5825
|
-
ConfigPaths.
|
|
5826
|
-
const
|
|
5934
|
+
const configDbPath = resolve5(process.env.BUNGEE_CONFIG_DB_PATH ?? join9(ConfigPaths.DATA_DIR, "bungee.db"));
|
|
5935
|
+
const accessDbPath = resolve5(process.env.BUNGEE_ACCESS_DB_PATH ?? join9(ConfigPaths.LOGS_DIR, "access.db"));
|
|
5936
|
+
const key = await initializePluginSecretsKey(configDbPath, options.pluginSecretsKey ?? process.env.BUNGEE_PLUGIN_SECRETS_KEY);
|
|
5937
|
+
await mkdir3(dirname4(accessDbPath), { recursive: true });
|
|
5827
5938
|
const launch = await resolveCoreLaunchDescriptor(options);
|
|
5828
5939
|
await new Promise((accept, reject) => {
|
|
5829
|
-
const child = spawn4(launch.executable, [...launch.entrypoint === null ? [] : [launch.entrypoint], "--initialize-config", configDbPath], { cwd:
|
|
5940
|
+
const child = spawn4(launch.executable, [...launch.entrypoint === null ? [] : [launch.entrypoint], "--initialize-config", configDbPath], { cwd: dirname4(configDbPath), stdio: "inherit", env: { ...withPluginSecretsKey(process.env, key), BUNGEE_ROLE: "master", BUNGEE_ACCESS_DB_PATH: accessDbPath } });
|
|
5830
5941
|
child.once("error", reject);
|
|
5831
5942
|
child.once("exit", (code) => code === 0 ? accept() : reject(new Error(`Configuration initialization failed (${code})`)));
|
|
5832
5943
|
});
|
|
5833
|
-
console.log(`Bungee data directory initialized at: ${
|
|
5944
|
+
console.log(`Bungee data directory initialized at: ${dirname4(configDbPath)}`);
|
|
5834
5945
|
console.log(`Configuration database: ${configDbPath}`);
|
|
5835
|
-
console.log(`Plugin state database: ${
|
|
5836
|
-
console.log(`
|
|
5946
|
+
console.log(`Plugin state database: ${join9(dirname4(configDbPath), "plugin-state.db")}`);
|
|
5947
|
+
console.log(`Plugin secrets key file: ${pluginSecretsKeyPath(configDbPath)}`);
|
|
5948
|
+
console.log(`Telemetry database: ${accessDbPath}`);
|
|
5837
5949
|
console.log("Start Bungee with: bungee start");
|
|
5838
5950
|
}
|
|
5839
5951
|
|
|
@@ -6154,7 +6266,7 @@ function createProgram() {
|
|
|
6154
6266
|
process.exitCode = 1;
|
|
6155
6267
|
}
|
|
6156
6268
|
});
|
|
6157
|
-
program.command("init").description("Initialize the Bungee SQLite data directory").action(initCommand);
|
|
6269
|
+
program.command("init").description("Initialize the Bungee SQLite data directory and persistent plugin secrets key").option("--plugin-secrets-key <base64>", "Initialize with a canonical Base64 key for 32 bytes (otherwise use BUNGEE_PLUGIN_SECRETS_KEY or generate one)").action(initCommand);
|
|
6158
6270
|
program.command("recover").description("Recover a stopped instance using bounded JSON from stdin or a 0600 file").option("--file <path>", "Owner-only 0600 recovery JSON input file").addHelpText("after", `
|
|
6159
6271
|
Stop the instance first. Supply one JSON object via stdin or bungee recover --file recovery.json (chmod 600).
|
|
6160
6272
|
Administrator identity: {"kind":"identity","plugin":"local-accounts","payload":{"username":"admin","password":"<new password, 15-128 characters>","reason":"Lost credentials"}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffusion/bungee",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.25.0",
|
|
4
4
|
"description": "High-performance reverse proxy server CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"test": "bun test"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@jeffusion/bungee-core": "^5.
|
|
17
|
-
"@jeffusion/bungee-types": "^5.
|
|
16
|
+
"@jeffusion/bungee-core": "^5.25.0",
|
|
17
|
+
"@jeffusion/bungee-types": "^5.25.0",
|
|
18
18
|
"cli-progress": "^3.12.0",
|
|
19
19
|
"commander": "^14.0.1"
|
|
20
20
|
},
|