@jeffusion/bungee 5.4.6 → 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.
Files changed (2) hide show
  1. package/dist/index.js +110 -36
  2. 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.4.6",
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 resolve2 } from "node:path";
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 = resolve2(path);
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 = resolve2(current, part);
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 = resolve2(path);
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
- if (options.directLaunch !== undefined)
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, nextToken) {
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, options.nextToken), "content-type": "application/json" },
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.nextToken ?? options.token),
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").option("--next-token <token>", "Next management token when the imported config rotates auth").action(importCommand);
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.4.6",
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.4.6",
17
- "@jeffusion/bungee-types": "^5.4.6",
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
  },