@jeffusion/bungee 5.4.5 → 5.5.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 +110 -36
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2145,6 +2145,10 @@ var {
|
|
|
2145
2145
|
Help: Help2
|
|
2146
2146
|
} = exports_commander;
|
|
2147
2147
|
|
|
2148
|
+
// src/commands/recover.ts
|
|
2149
|
+
import { resolve as resolve4, join as join7 } from "node:path";
|
|
2150
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2151
|
+
|
|
2148
2152
|
// src/config/paths.ts
|
|
2149
2153
|
import path from "path";
|
|
2150
2154
|
import os from "os";
|
|
@@ -2171,16 +2175,6 @@ class ConfigPaths {
|
|
|
2171
2175
|
}
|
|
2172
2176
|
}
|
|
2173
2177
|
|
|
2174
|
-
// src/commands/init.ts
|
|
2175
|
-
async function initCommand() {
|
|
2176
|
-
ConfigPaths.ensureDataDir();
|
|
2177
|
-
ConfigPaths.ensureLogsDir();
|
|
2178
|
-
console.log(`Bungee data directory initialized at: ${ConfigPaths.DATA_DIR}`);
|
|
2179
|
-
console.log(`Configuration database: ${ConfigPaths.DATA_DIR}/bungee.db`);
|
|
2180
|
-
console.log(`Telemetry database: ${ConfigPaths.LOGS_DIR}/access.db`);
|
|
2181
|
-
console.log("Start Bungee with: bungee start");
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
2178
|
// src/daemon/manager.ts
|
|
2185
2179
|
import fs2 from "node:fs";
|
|
2186
2180
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
@@ -3195,7 +3189,7 @@ function aclEnvironment(path, sid, kind) {
|
|
|
3195
3189
|
function defaultWindowsAclAdapter(deadlineMs = WINDOWS_ACL_DEADLINE_MS) {
|
|
3196
3190
|
const phase = (value) => `[Console]::Error.WriteLine('${WINDOWS_ACL_PHASE_PREFIX}${value}');`;
|
|
3197
3191
|
const moduleBootstrap = '$env:PSModulePath=[System.IO.Path]::Combine($PSHOME,"Modules");' + "Import-Module Microsoft.PowerShell.Security -ErrorAction Stop;";
|
|
3198
|
-
const readScript = phase("started") + moduleBootstrap + phase("before_get_acl") + "$item=Get-Item -LiteralPath $env:BUNGEE_DAEMON_ACL_PATH -Force;" + "$a=if($item -is [System.IO.DirectoryInfo]){[System.IO.FileSystemAclExtensions]::GetAccessControl([System.IO.DirectoryInfo]$item,[System.Security.AccessControl.AccessControlSections]::Access)}" + "elseif($item -is [System.IO.FileInfo]){[System.IO.FileSystemAclExtensions]::GetAccessControl([System.IO.FileInfo]$item,[System.Security.AccessControl.AccessControlSections]::Access)}" + 'else{throw "Windows ACL target is not a file or directory"};' + phase("after_get_acl") + "$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value;" + "$e=@($a.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])|ForEach-Object { @{sid=$_.IdentityReference.Value;" + 'access=$(if([int]$_.AccessControlType -eq 0){"allow"}else{"deny"});rights=[int]$_.FileSystemRights;' + "inheritance=[int]$_.InheritanceFlags;propagation=[int]$_.PropagationFlags;inherited=[bool]$_.IsInherited} });" + "[Console]::Out.Write((ConvertTo-Json -Compress -Depth 4 @{currentSid=$sid;entries=$e}))";
|
|
3192
|
+
const readScript = phase("started") + moduleBootstrap + phase("before_get_acl") + "$item=Get-Item -LiteralPath $env:BUNGEE_DAEMON_ACL_PATH -Force;" + "$a=if($item -is [System.IO.DirectoryInfo]){[System.IO.FileSystemAclExtensions]::GetAccessControl([System.IO.DirectoryInfo]$item,([System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner))}" + "elseif($item -is [System.IO.FileInfo]){[System.IO.FileSystemAclExtensions]::GetAccessControl([System.IO.FileInfo]$item,([System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner))}" + 'else{throw "Windows ACL target is not a file or directory"};' + phase("after_get_acl") + "$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value;" + "$e=@($a.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])|ForEach-Object { @{sid=$_.IdentityReference.Value;" + 'access=$(if([int]$_.AccessControlType -eq 0){"allow"}else{"deny"});rights=[int]$_.FileSystemRights;' + "inheritance=[int]$_.InheritanceFlags;propagation=[int]$_.PropagationFlags;inherited=[bool]$_.IsInherited} });" + "[Console]::Out.Write((ConvertTo-Json -Compress -Depth 4 @{currentSid=$sid;ownerSid=$a.GetOwner([System.Security.Principal.SecurityIdentifier]).Value;entries=$e}))";
|
|
3199
3193
|
const setScript = phase("started") + moduleBootstrap + "$p=$env:BUNGEE_DAEMON_ACL_PATH;$u=$env:BUNGEE_DAEMON_ACL_SID;" + '$k=$env:BUNGEE_DAEMON_ACL_KIND;$a=if($k -eq "directory"){[System.Security.AccessControl.DirectorySecurity]::new()}else{[System.Security.AccessControl.FileSecurity]::new()};' + '$a.SetAccessRuleProtection($true,$false);$i=if($k -eq "directory"){[System.Security.AccessControl.InheritanceFlags]::ObjectInherit -bor [System.Security.AccessControl.InheritanceFlags]::ContainerInherit}else{[System.Security.AccessControl.InheritanceFlags]::None};' + '@($u,"S-1-5-18","S-1-5-32-544")|ForEach-Object {$a.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.SecurityIdentifier]::new($_),[System.Security.AccessControl.FileSystemRights]::FullControl,$i,[System.Security.AccessControl.PropagationFlags]::None,[System.Security.AccessControl.AccessControlType]::Allow))};' + phase("before_set_acl") + 'if($k -eq "directory"){[System.IO.FileSystemAclExtensions]::SetAccessControl([System.IO.DirectoryInfo]::new($p),$a)}else{[System.IO.FileSystemAclExtensions]::SetAccessControl([System.IO.FileInfo]::new($p),$a)};' + phase("after_set_acl");
|
|
3200
3194
|
const readBlock = phase("before_get_acl") + "$item=Get-Item -LiteralPath $p -Force;" + "$a=if($item -is [System.IO.DirectoryInfo]){[System.IO.FileSystemAclExtensions]::GetAccessControl([System.IO.DirectoryInfo]$item,[System.Security.AccessControl.AccessControlSections]::Access)}" + "elseif($item -is [System.IO.FileInfo]){[System.IO.FileSystemAclExtensions]::GetAccessControl([System.IO.FileInfo]$item,[System.Security.AccessControl.AccessControlSections]::Access)}" + 'else{throw "Windows ACL target is not a file or directory"};' + "$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value;" + "$e=@($a.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])|ForEach-Object { @{sid=$_.IdentityReference.Value;" + 'access=$(if([int]$_.AccessControlType -eq 0){"allow"}else{"deny"});rights=[int]$_.FileSystemRights;' + "inheritance=[int]$_.InheritanceFlags;propagation=[int]$_.PropagationFlags;inherited=[bool]$_.IsInherited} });" + phase("after_get_acl") + "@{currentSid=$sid;entries=$e}";
|
|
3201
3195
|
const ensureScript = "$ErrorActionPreference='Stop';" + phase("started") + moduleBootstrap + "$p=$env:BUNGEE_DAEMON_ACL_PATH;$k=$env:BUNGEE_DAEMON_ACL_KIND;" + "$readAcl={ " + readBlock + " };$before=&$readAcl;$sid=$before.currentSid;" + `$parts=$sid -split "-";$validSid=($sid -cmatch '^S-[0-9]+(?:-[0-9]+)+$') -and (@($parts|Select-Object -Skip 1|Where-Object { $_.Length -gt 1 -and $_.StartsWith("0") }).Count -eq 0);` + '$allowed=@($sid,"S-1-5-18","S-1-5-32-544");$inheritance=if($k -eq "directory"){3}else{0};$missing=@{};$repair=$false;' + "if($validSid){foreach($name in $allowed){$missing[$name]=$true};foreach($r in @($before.entries)){" + "if($allowed -notcontains $r.sid){$repair=$true;continue};$missing.Remove($r.sid);" + 'if($r.access -ne "allow" -or [int]$r.rights -ne 2032127 -or [int]$r.inheritance -ne $inheritance -or [int]$r.propagation -ne 0 -or [bool]$r.inherited){$repair=$true}};' + "if($missing.Count -ne 0){$repair=$true}};" + 'if($repair){$a=if($k -eq "directory"){[System.Security.AccessControl.DirectorySecurity]::new()}else{[System.Security.AccessControl.FileSecurity]::new()};' + '$a.SetAccessRuleProtection($true,$false);$i=if($k -eq "directory"){[System.Security.AccessControl.InheritanceFlags]::ObjectInherit -bor [System.Security.AccessControl.InheritanceFlags]::ContainerInherit}else{[System.Security.AccessControl.InheritanceFlags]::None};' + '@($sid,"S-1-5-18","S-1-5-32-544")|ForEach-Object {$a.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.SecurityIdentifier]::new($_),[System.Security.AccessControl.FileSystemRights]::FullControl,$i,[System.Security.AccessControl.PropagationFlags]::None,[System.Security.AccessControl.AccessControlType]::Allow))};' + phase("before_set_acl") + 'if($k -eq "directory"){[System.IO.FileSystemAclExtensions]::SetAccessControl([System.IO.DirectoryInfo]::new($p),$a)}else{[System.IO.FileSystemAclExtensions]::SetAccessControl([System.IO.FileInfo]::new($p),$a)};' + phase("after_set_acl") + "};" + "$after=&$readAcl;[Console]::Out.Write((ConvertTo-Json -Compress -Depth 6 @{before=$before;after=$after;repaired=$repair}))";
|
|
@@ -3255,6 +3249,14 @@ function defaultWindowsAclAdapter(deadlineMs = WINDOWS_ACL_DEADLINE_MS) {
|
|
|
3255
3249
|
}
|
|
3256
3250
|
};
|
|
3257
3251
|
}
|
|
3252
|
+
async function assertWindowsOwnerOnlyFileAcl(path, adapter = defaultWindowsAclAdapter()) {
|
|
3253
|
+
const snapshot = await adapter.read(path);
|
|
3254
|
+
if (!canonicalSid(snapshot.ownerSid) || snapshot.ownerSid !== snapshot.currentSid)
|
|
3255
|
+
fail2("acl", "Recovery input must be owned by the current user");
|
|
3256
|
+
const validation = windowsAclSecure(snapshot, "file");
|
|
3257
|
+
if (validation !== null)
|
|
3258
|
+
throw new WindowsAclValidationError(validation);
|
|
3259
|
+
}
|
|
3258
3260
|
async function fsyncDirectory(root, platform, options) {
|
|
3259
3261
|
const unsupported = platform === "win32" ? WINDOWS_UNSUPPORTED_FSYNC : POSIX_UNSUPPORTED_FSYNC;
|
|
3260
3262
|
let handle;
|
|
@@ -3395,7 +3397,7 @@ import { createInterface } from "node:readline";
|
|
|
3395
3397
|
// package.json
|
|
3396
3398
|
var package_default = {
|
|
3397
3399
|
name: "@jeffusion/bungee",
|
|
3398
|
-
version: "5.
|
|
3400
|
+
version: "5.5.0",
|
|
3399
3401
|
description: "High-performance reverse proxy server CLI",
|
|
3400
3402
|
type: "module",
|
|
3401
3403
|
bin: {
|
|
@@ -3677,7 +3679,7 @@ class BinaryManager {
|
|
|
3677
3679
|
}
|
|
3678
3680
|
|
|
3679
3681
|
// src/daemon/runtime.ts
|
|
3680
|
-
import { isAbsolute as isAbsolute2, join as join4 } from "node:path";
|
|
3682
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
|
|
3681
3683
|
function createDaemonRuntime(options) {
|
|
3682
3684
|
if (!isAbsolute2(options.dataDirectory))
|
|
3683
3685
|
throw new Error("Daemon data directory must be absolute");
|
|
@@ -3694,7 +3696,7 @@ function createDaemonRuntime(options) {
|
|
|
3694
3696
|
cwd: options.dataDirectory,
|
|
3695
3697
|
env: {
|
|
3696
3698
|
...inheritedEnvironment,
|
|
3697
|
-
BUNGEE_CONFIG_DB_PATH: join4(options.dataDirectory, "bungee.db"),
|
|
3699
|
+
BUNGEE_CONFIG_DB_PATH: options.inheritedEnvironment?.BUNGEE_CONFIG_DB_PATH ? resolve2(options.inheritedEnvironment.BUNGEE_CONFIG_DB_PATH) : join4(options.dataDirectory, "bungee.db"),
|
|
3698
3700
|
BUNGEE_ACCESS_DB_PATH: join4(options.logsDirectory, "access.db"),
|
|
3699
3701
|
WORKER_COUNT: options.workers ?? "2",
|
|
3700
3702
|
DAEMON_MODE: "true",
|
|
@@ -3706,7 +3708,7 @@ function createDaemonRuntime(options) {
|
|
|
3706
3708
|
// src/daemon/pid-mirror.ts
|
|
3707
3709
|
import { constants as fsConstants2 } from "node:fs";
|
|
3708
3710
|
import { lstat as lstat2, open as open2, rename as rename2, unlink as unlink2 } from "node:fs/promises";
|
|
3709
|
-
import { dirname, isAbsolute as isAbsolute3, parse as parse2, resolve as
|
|
3711
|
+
import { dirname, isAbsolute as isAbsolute3, parse as parse2, resolve as resolve3 } from "node:path";
|
|
3710
3712
|
import { randomBytes } from "node:crypto";
|
|
3711
3713
|
var MAX_PID_BYTES = 64;
|
|
3712
3714
|
var UNSUPPORTED_DIR_FSYNC = new Set(["EINVAL", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EPERM", "EISDIR"]);
|
|
@@ -3720,11 +3722,11 @@ function regular(stat) {
|
|
|
3720
3722
|
return stat.isFile() && stat.nlink === 1;
|
|
3721
3723
|
}
|
|
3722
3724
|
async function rejectSymlinkComponents2(path) {
|
|
3723
|
-
const absolute =
|
|
3725
|
+
const absolute = resolve3(path);
|
|
3724
3726
|
const root = parse2(absolute).root;
|
|
3725
3727
|
let current = root;
|
|
3726
3728
|
for (const part of absolute.slice(root.length).split(/[\\/]+/).filter(Boolean)) {
|
|
3727
|
-
current =
|
|
3729
|
+
current = resolve3(current, part);
|
|
3728
3730
|
try {
|
|
3729
3731
|
if ((await lstat2(current)).isSymbolicLink())
|
|
3730
3732
|
throw new Error("PID mirror path contains a symlink");
|
|
@@ -3807,7 +3809,7 @@ async function deleteLegacyPidFile(path, expected) {
|
|
|
3807
3809
|
async function writeLegacyPidMirror(path, pid, options = {}) {
|
|
3808
3810
|
if (!Number.isSafeInteger(pid) || pid <= 0 || !isAbsolute3(path) || path.includes("\x00"))
|
|
3809
3811
|
throw new Error("invalid PID mirror target");
|
|
3810
|
-
const target =
|
|
3812
|
+
const target = resolve3(path);
|
|
3811
3813
|
const directory = dirname(target);
|
|
3812
3814
|
await rejectSymlinkComponents2(directory);
|
|
3813
3815
|
const temporary = `${target}.${process.pid}.${randomBytes(16).toString("hex")}.tmp`;
|
|
@@ -4990,6 +4992,17 @@ function canonicalLaunchDescriptor(descriptor) {
|
|
|
4990
4992
|
throw new Error(`Daemon entrypoint cannot be canonicalized: ${errorText(error)}`);
|
|
4991
4993
|
}
|
|
4992
4994
|
}
|
|
4995
|
+
async function resolveCoreLaunchDescriptor(options = {}) {
|
|
4996
|
+
const direct = options.directLaunch ?? options.launchDescriptor;
|
|
4997
|
+
if (direct !== undefined)
|
|
4998
|
+
return canonicalLaunchDescriptor(direct);
|
|
4999
|
+
const binaryPath = await BinaryManager.ensureBinary({ autoUpgrade: options.autoUpgrade });
|
|
5000
|
+
try {
|
|
5001
|
+
return { executable: realpathSync2.native(binaryPath), entrypoint: null };
|
|
5002
|
+
} catch (error) {
|
|
5003
|
+
throw new Error(`Daemon executable cannot be canonicalized: ${errorText(error)}`);
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
4993
5006
|
|
|
4994
5007
|
class DaemonManager {
|
|
4995
5008
|
spawnDaemon;
|
|
@@ -5171,18 +5184,7 @@ class DaemonManager {
|
|
|
5171
5184
|
}, this.fileOptions());
|
|
5172
5185
|
}
|
|
5173
5186
|
async launchDescriptor(options) {
|
|
5174
|
-
|
|
5175
|
-
return canonicalLaunchDescriptor(options.directLaunch);
|
|
5176
|
-
if (options.launchDescriptor !== undefined)
|
|
5177
|
-
return canonicalLaunchDescriptor(options.launchDescriptor);
|
|
5178
|
-
if (this.injectedLaunch !== undefined)
|
|
5179
|
-
return canonicalLaunchDescriptor(this.injectedLaunch);
|
|
5180
|
-
const binaryPath = await BinaryManager.ensureBinary({ autoUpgrade: options.autoUpgrade });
|
|
5181
|
-
try {
|
|
5182
|
-
return { executable: realpathSync2.native(binaryPath), entrypoint: null };
|
|
5183
|
-
} catch (error) {
|
|
5184
|
-
throw new Error(`Daemon executable cannot be canonicalized: ${errorText(error)}`);
|
|
5185
|
-
}
|
|
5187
|
+
return resolveCoreLaunchDescriptor({ ...options, launchDescriptor: options.launchDescriptor ?? this.injectedLaunch });
|
|
5186
5188
|
}
|
|
5187
5189
|
async cleanupAfterChildExit(state, childDead = false) {
|
|
5188
5190
|
if (state.state === "launching") {
|
|
@@ -5761,6 +5763,73 @@ class DaemonManager {
|
|
|
5761
5763
|
}
|
|
5762
5764
|
}
|
|
5763
5765
|
|
|
5766
|
+
// src/commands/recovery-file.ts
|
|
5767
|
+
import { constants } from "node:fs";
|
|
5768
|
+
import { lstat as lstat4, open as open3 } from "node:fs/promises";
|
|
5769
|
+
var UNSAFE_INPUT = "Recovery input must be an owner-only regular file of at most 8192 bytes";
|
|
5770
|
+
async function openRecoveryInputFile(path, options = {}) {
|
|
5771
|
+
const platform = options.platform ?? process.platform;
|
|
5772
|
+
const before = await lstat4(path);
|
|
5773
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1)
|
|
5774
|
+
throw new Error(UNSAFE_INPUT);
|
|
5775
|
+
const file = await open3(path, constants.O_RDONLY | (platform === "win32" ? 0 : constants.O_NOFOLLOW | constants.O_NONBLOCK));
|
|
5776
|
+
try {
|
|
5777
|
+
const opened = await file.stat();
|
|
5778
|
+
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== before.dev || opened.ino !== before.ino || opened.size > 8192)
|
|
5779
|
+
throw new Error(UNSAFE_INPUT);
|
|
5780
|
+
if (platform === "win32")
|
|
5781
|
+
await assertWindowsOwnerOnlyFileAcl(path, options.windowsAcl);
|
|
5782
|
+
else if ((opened.mode & 511) !== 384 || process.getuid && opened.uid !== process.getuid())
|
|
5783
|
+
throw new Error(UNSAFE_INPUT);
|
|
5784
|
+
const after = await lstat4(path);
|
|
5785
|
+
const final = await file.stat();
|
|
5786
|
+
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()))
|
|
5787
|
+
throw new Error(UNSAFE_INPUT);
|
|
5788
|
+
return file;
|
|
5789
|
+
} catch (error) {
|
|
5790
|
+
await file.close();
|
|
5791
|
+
throw error;
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5795
|
+
// src/commands/recover.ts
|
|
5796
|
+
async function recoverCommand(options = {}) {
|
|
5797
|
+
const launch = await resolveCoreLaunchDescriptor(options);
|
|
5798
|
+
const db = resolve4(process.env.BUNGEE_CONFIG_DB_PATH ?? join7(ConfigPaths.DATA_DIR, "bungee.db"));
|
|
5799
|
+
let file;
|
|
5800
|
+
try {
|
|
5801
|
+
if (options.file) {
|
|
5802
|
+
file = await openRecoveryInputFile(options.file);
|
|
5803
|
+
}
|
|
5804
|
+
await new Promise((accept, reject) => {
|
|
5805
|
+
const child = spawn3(launch.executable, [...launch.entrypoint === null ? [] : [launch.entrypoint], "--recover", db], { cwd: ConfigPaths.DATA_DIR, stdio: [file?.fd ?? "inherit", "inherit", "inherit"], env: { ...process.env, BUNGEE_ROLE: "master" } });
|
|
5806
|
+
child.once("error", reject);
|
|
5807
|
+
child.once("exit", (code) => code === 0 ? accept() : reject(new Error("Offline recovery failed")));
|
|
5808
|
+
});
|
|
5809
|
+
} finally {
|
|
5810
|
+
await file?.close();
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
|
|
5814
|
+
// src/commands/init.ts
|
|
5815
|
+
import { resolve as resolve5, join as join8 } from "node:path";
|
|
5816
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
5817
|
+
async function initCommand(options = {}) {
|
|
5818
|
+
ConfigPaths.ensureDataDir();
|
|
5819
|
+
ConfigPaths.ensureLogsDir();
|
|
5820
|
+
const configDbPath = resolve5(process.env.BUNGEE_CONFIG_DB_PATH ?? join8(ConfigPaths.DATA_DIR, "bungee.db"));
|
|
5821
|
+
const launch = await resolveCoreLaunchDescriptor(options);
|
|
5822
|
+
await new Promise((accept, reject) => {
|
|
5823
|
+
const child = spawn4(launch.executable, [...launch.entrypoint === null ? [] : [launch.entrypoint], "--initialize-config", configDbPath], { cwd: ConfigPaths.DATA_DIR, stdio: "inherit", env: { ...process.env, BUNGEE_ROLE: "master" } });
|
|
5824
|
+
child.once("error", reject);
|
|
5825
|
+
child.once("exit", (code) => code === 0 ? accept() : reject(new Error(`Configuration initialization failed (${code})`)));
|
|
5826
|
+
});
|
|
5827
|
+
console.log(`Bungee data directory initialized at: ${ConfigPaths.DATA_DIR}`);
|
|
5828
|
+
console.log(`Configuration database: ${configDbPath}`);
|
|
5829
|
+
console.log(`Telemetry database: ${ConfigPaths.LOGS_DIR}/access.db`);
|
|
5830
|
+
console.log("Start Bungee with: bungee start");
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5764
5833
|
// src/commands/start.ts
|
|
5765
5834
|
async function startCommand(options = {}) {
|
|
5766
5835
|
const daemonManager = new DaemonManager;
|
|
@@ -5916,10 +5985,9 @@ function endpoint(options, path) {
|
|
|
5916
5985
|
async function writeFile(path, content) {
|
|
5917
5986
|
await Bun.write(path, content);
|
|
5918
5987
|
}
|
|
5919
|
-
function headers(token
|
|
5988
|
+
function headers(token) {
|
|
5920
5989
|
return {
|
|
5921
|
-
...token === undefined ? {} : { authorization: `Bearer ${token}` }
|
|
5922
|
-
...nextToken === undefined ? {} : { "x-bungee-next-authorization": `Bearer ${nextToken}` }
|
|
5990
|
+
...token === undefined ? {} : { authorization: `Bearer ${token}` }
|
|
5923
5991
|
};
|
|
5924
5992
|
}
|
|
5925
5993
|
async function exportCommand(options) {
|
|
@@ -5937,7 +6005,7 @@ async function importCommand(options) {
|
|
|
5937
6005
|
const snapshot = await Bun.file(options.file).text();
|
|
5938
6006
|
const response = await fetch(endpoint(options, "import"), {
|
|
5939
6007
|
method: "POST",
|
|
5940
|
-
headers: { ...headers(options.token
|
|
6008
|
+
headers: { ...headers(options.token), "content-type": "application/json" },
|
|
5941
6009
|
body: snapshot
|
|
5942
6010
|
});
|
|
5943
6011
|
if (!response.ok) {
|
|
@@ -5964,7 +6032,7 @@ async function importCommand(options) {
|
|
|
5964
6032
|
throw new Error(`Import failed: operation ${accepted.operation_id} timed out after ${OPERATION_TIMEOUT_MS}ms`);
|
|
5965
6033
|
}
|
|
5966
6034
|
const poll = await fetch(endpoint(options, `operations/${accepted.operation_id}`), {
|
|
5967
|
-
headers: headers(options.
|
|
6035
|
+
headers: headers(options.token),
|
|
5968
6036
|
signal: AbortSignal.timeout(remainingMs)
|
|
5969
6037
|
});
|
|
5970
6038
|
if (!poll.ok) {
|
|
@@ -5982,6 +6050,12 @@ async function importCommand(options) {
|
|
|
5982
6050
|
function createProgram() {
|
|
5983
6051
|
const program = new Command2().name("bungee").description("High-performance reverse proxy server built with Bun and TypeScript").version(package_default.version);
|
|
5984
6052
|
program.command("init").description("Initialize the Bungee SQLite data directory").action(initCommand);
|
|
6053
|
+
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", `
|
|
6054
|
+
Stop the instance first. Supply one JSON object via stdin or bungee recover --file recovery.json (chmod 600).
|
|
6055
|
+
Administrator identity: {"kind":"identity","plugin":"local-accounts","payload":{"username":"admin","password":"<new password, 15-128 characters>","reason":"Lost credentials"}}
|
|
6056
|
+
Unresolved usage: {"kind":"plugin-state","plugin":"token-budget","payload":{"keyId":"KEY_ID","requestId":"REQUEST_ID","attemptId":"ATTEMPT_ID","inputTokens":100,"outputTokens":200,"reason":"Verified usage evidence"}}
|
|
6057
|
+
Usage counts are replacement totals for the specified attempt, applied to its original period.
|
|
6058
|
+
`).action(recoverCommand);
|
|
5985
6059
|
program.command("start").description("Start proxy server as daemon").option("-p, --port <port>", "Override default port").option("-w, --workers <count>", "Number of worker processes", "2").option("-d, --detach", "Run as daemon (default)", true).option("--auto-upgrade", "Automatically upgrade binary if version mismatch").action(startCommand);
|
|
5986
6060
|
program.command("stop").description("Stop proxy server daemon").action(stopCommand);
|
|
5987
6061
|
program.command("restart").description("Restart proxy server daemon").option("-p, --port <port>", "Override default port").option("-w, --workers <count>", "Number of worker processes", "2").option("--auto-upgrade", "Automatically upgrade binary if version mismatch").action(restartCommand);
|
|
@@ -5989,7 +6063,7 @@ function createProgram() {
|
|
|
5989
6063
|
program.command("logs").description("Show daemon logs").option("-f, --follow", "Follow log output").option("-n, --lines <number>", "Number of lines to show", "50").action(logsCommand);
|
|
5990
6064
|
program.command("ui").description("Open web dashboard from the Management server").option("-p, --port <port>", "Management server port", DEFAULT_MANAGEMENT_PORT).option("-H, --host <host>", "Management server host", "localhost").action(uiCommand);
|
|
5991
6065
|
program.command("export").description("Export current configuration from the Management server as a versioned snapshot JSON").requiredOption("-o, --file <path>", "Output file path").option("-p, --port <port>", "Management server port", DEFAULT_MANAGEMENT_PORT).option("-H, --host <host>", "Management server host", "localhost").option("-t, --token <token>", "Management auth token").action(exportCommand);
|
|
5992
|
-
program.command("import").description("Restore configuration on the Management server from a versioned snapshot JSON").requiredOption("-f, --file <path>", "Snapshot file path").option("-p, --port <port>", "Management server port", DEFAULT_MANAGEMENT_PORT).option("-H, --host <host>", "Management server host", "localhost").option("-t, --token <token>", "Management auth token").
|
|
6066
|
+
program.command("import").description("Restore configuration on the Management server from a versioned snapshot JSON").requiredOption("-f, --file <path>", "Snapshot file path").option("-p, --port <port>", "Management server port", DEFAULT_MANAGEMENT_PORT).option("-H, --host <host>", "Management server host", "localhost").option("-t, --token <token>", "Management auth token").action(importCommand);
|
|
5993
6067
|
program.command("upgrade").description("Upgrade Bungee binary to the latest version").option("-f, --force", "Force re-download even if already up to date").action(upgradeCommand);
|
|
5994
6068
|
return program;
|
|
5995
6069
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffusion/bungee",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.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.5.0",
|
|
17
|
+
"@jeffusion/bungee-types": "^5.5.0",
|
|
18
18
|
"cli-progress": "^3.12.0",
|
|
19
19
|
"commander": "^14.0.1"
|
|
20
20
|
},
|