@fiber-pay/cli 0.1.0-rc.3 → 0.1.0-rc.4
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/README.md +1 -1
- package/dist/cli.js +514 -109
- package/dist/cli.js.map +1 -1
- package/package.json +6 -4
package/dist/cli.js
CHANGED
|
@@ -965,13 +965,13 @@ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, wr
|
|
|
965
965
|
import { join as join3 } from "path";
|
|
966
966
|
|
|
967
967
|
// src/lib/config-templates.ts
|
|
968
|
-
var
|
|
968
|
+
var TESTNET_CONFIG_TEMPLATE_V071 = `# This configuration file only contains the necessary configurations for the testnet deployment.
|
|
969
969
|
# All options' descriptions can be found via \`fnn --help\` and be overridden by command line arguments or environment variables.
|
|
970
970
|
fiber:
|
|
971
|
-
listening_addr: "/ip4/
|
|
971
|
+
listening_addr: "/ip4/0.0.0.0/tcp/8228"
|
|
972
972
|
bootnode_addrs:
|
|
973
973
|
- "/ip4/54.179.226.154/tcp/8228/p2p/Qmes1EBD4yNo9Ywkfe6eRw9tG1nVNGLDmMud1xJMsoYFKy"
|
|
974
|
-
- "/ip4/
|
|
974
|
+
- "/ip4/16.163.7.105/tcp/8228/p2p/QmdyQWjPtbK4NWWsvy8s69NGJaQULwgeQDT5ZpNDrTNaeV"
|
|
975
975
|
announce_listening_addr: true
|
|
976
976
|
announced_addrs:
|
|
977
977
|
# If you want to announce your fiber node public address to the network, you need to add the address here, please change the ip to your public ip accordingly.
|
|
@@ -1037,7 +1037,7 @@ services:
|
|
|
1037
1037
|
- rpc
|
|
1038
1038
|
- ckb
|
|
1039
1039
|
`;
|
|
1040
|
-
var
|
|
1040
|
+
var MAINNET_CONFIG_TEMPLATE_V071 = `# This configuration file only contains the necessary configurations for the mainnet deployment.
|
|
1041
1041
|
# All options' descriptions can be found via \`fnn --help\` and be overridden by command line arguments or environment variables.
|
|
1042
1042
|
fiber:
|
|
1043
1043
|
listening_addr: "/ip4/0.0.0.0/tcp/8228"
|
|
@@ -1113,7 +1113,7 @@ services:
|
|
|
1113
1113
|
- ckb
|
|
1114
1114
|
`;
|
|
1115
1115
|
function getConfigTemplate(network) {
|
|
1116
|
-
return network === "mainnet" ?
|
|
1116
|
+
return network === "mainnet" ? MAINNET_CONFIG_TEMPLATE_V071 : TESTNET_CONFIG_TEMPLATE_V071;
|
|
1117
1117
|
}
|
|
1118
1118
|
|
|
1119
1119
|
// src/lib/config.ts
|
|
@@ -2683,11 +2683,122 @@ Following logs (interval: ${intervalMs}ms). Press Ctrl+C to stop.`);
|
|
|
2683
2683
|
import { nodeIdToPeerId as nodeIdToPeerId2, scriptToAddress as scriptToAddress2 } from "@fiber-pay/sdk";
|
|
2684
2684
|
import { Command as Command8 } from "commander";
|
|
2685
2685
|
|
|
2686
|
+
// src/lib/node-start.ts
|
|
2687
|
+
import { spawn } from "child_process";
|
|
2688
|
+
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
2689
|
+
import { join as join5 } from "path";
|
|
2690
|
+
import {
|
|
2691
|
+
createKeyManager,
|
|
2692
|
+
ensureFiberBinary,
|
|
2693
|
+
getDefaultBinaryPath,
|
|
2694
|
+
ProcessManager
|
|
2695
|
+
} from "@fiber-pay/node";
|
|
2696
|
+
import { startRuntimeService } from "@fiber-pay/runtime";
|
|
2697
|
+
|
|
2698
|
+
// src/lib/bootnode.ts
|
|
2699
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
2700
|
+
import { parse as parseYaml } from "yaml";
|
|
2701
|
+
function extractBootnodeAddrs(configFilePath) {
|
|
2702
|
+
if (!existsSync8(configFilePath)) return [];
|
|
2703
|
+
try {
|
|
2704
|
+
const content = readFileSync5(configFilePath, "utf-8");
|
|
2705
|
+
const doc = parseYaml(content);
|
|
2706
|
+
const addrs = doc?.fiber?.bootnode_addrs;
|
|
2707
|
+
if (!Array.isArray(addrs)) return [];
|
|
2708
|
+
return addrs.filter((a) => typeof a === "string" && a.startsWith("/ip"));
|
|
2709
|
+
} catch {
|
|
2710
|
+
return [];
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
async function autoConnectBootnodes(rpc, bootnodes) {
|
|
2714
|
+
if (bootnodes.length === 0) return;
|
|
2715
|
+
console.log(`\u{1F517} Connecting to ${bootnodes.length} bootnode(s)...`);
|
|
2716
|
+
for (const addr of bootnodes) {
|
|
2717
|
+
const shortId = addr.match(/\/p2p\/(.+)$/)?.[1]?.slice(0, 12) || addr.slice(-12);
|
|
2718
|
+
try {
|
|
2719
|
+
await rpc.connectPeer({ address: addr });
|
|
2720
|
+
console.log(` \u2705 Connected to ${shortId}...`);
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2723
|
+
if (msg.toLowerCase().includes("already")) {
|
|
2724
|
+
console.log(` \u2705 Already connected to ${shortId}...`);
|
|
2725
|
+
} else {
|
|
2726
|
+
console.error(` \u26A0\uFE0F Failed to connect to ${shortId}...: ${msg}`);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
// src/lib/node-migration.ts
|
|
2733
|
+
import { dirname } from "path";
|
|
2734
|
+
import { BinaryManager, MigrationManager } from "@fiber-pay/node";
|
|
2735
|
+
|
|
2736
|
+
// src/lib/migration-utils.ts
|
|
2737
|
+
function replaceRawMigrateHint(message) {
|
|
2738
|
+
return message.replace(
|
|
2739
|
+
/Fiber need to run some database migrations, please run `fnn-migrate[^`]*` to start migrations\.?/g,
|
|
2740
|
+
"Fiber database migration is required."
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
function normalizeMigrationCheck(check) {
|
|
2744
|
+
return {
|
|
2745
|
+
...check,
|
|
2746
|
+
message: replaceRawMigrateHint(check.message)
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
// src/lib/node-migration.ts
|
|
2751
|
+
async function runMigrationGuard(opts) {
|
|
2752
|
+
const { dataDir, binaryPath, json } = opts;
|
|
2753
|
+
if (!MigrationManager.storeExists(dataDir)) {
|
|
2754
|
+
return { checked: false, skippedReason: "store does not exist" };
|
|
2755
|
+
}
|
|
2756
|
+
const storePath = MigrationManager.resolveStorePath(dataDir);
|
|
2757
|
+
const binaryDir = dirname(binaryPath);
|
|
2758
|
+
const bm = new BinaryManager(binaryDir);
|
|
2759
|
+
const migrateBinPath = bm.getMigrateBinaryPath();
|
|
2760
|
+
let migrationCheck;
|
|
2761
|
+
try {
|
|
2762
|
+
const migrationManager = new MigrationManager(migrateBinPath);
|
|
2763
|
+
migrationCheck = await migrationManager.check(storePath);
|
|
2764
|
+
} catch {
|
|
2765
|
+
return { checked: false, skippedReason: "fnn-migrate binary not available" };
|
|
2766
|
+
}
|
|
2767
|
+
if (migrationCheck.needed) {
|
|
2768
|
+
const message = migrationCheck.valid ? "Database migration required. Run `fiber-pay node upgrade --force-migrate` before starting." : replaceRawMigrateHint(migrationCheck.message);
|
|
2769
|
+
if (json) {
|
|
2770
|
+
printJsonError({
|
|
2771
|
+
code: "MIGRATION_REQUIRED",
|
|
2772
|
+
message,
|
|
2773
|
+
recoverable: true,
|
|
2774
|
+
suggestion: `Back up your store first (directory: "${storePath}"). Then run \`fiber-pay node upgrade --force-migrate\`. If migration still fails, close channels on the old fnn version, remove the store, and restart with a fresh store. If backup exists, restore it to roll back.`,
|
|
2775
|
+
details: {
|
|
2776
|
+
storePath,
|
|
2777
|
+
migrationCheck: {
|
|
2778
|
+
...migrationCheck,
|
|
2779
|
+
message
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
} else {
|
|
2784
|
+
console.error(`\u274C ${message}`);
|
|
2785
|
+
console.error(` 1) Back up store directory: ${storePath}`);
|
|
2786
|
+
console.error(" 2) Run: fiber-pay node upgrade --force-migrate");
|
|
2787
|
+
console.error(
|
|
2788
|
+
" 3) If it still fails, close channels on old fnn, remove store, then restart."
|
|
2789
|
+
);
|
|
2790
|
+
console.error(" 4) If backup exists, restore it to roll back.");
|
|
2791
|
+
}
|
|
2792
|
+
process.exit(1);
|
|
2793
|
+
}
|
|
2794
|
+
return { checked: true, migrationCheck };
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2686
2797
|
// src/lib/node-runtime-daemon.ts
|
|
2687
2798
|
import { spawnSync } from "child_process";
|
|
2688
|
-
import { existsSync as
|
|
2799
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2689
2800
|
function getCustomBinaryState(binaryPath) {
|
|
2690
|
-
const exists =
|
|
2801
|
+
const exists = existsSync9(binaryPath);
|
|
2691
2802
|
if (!exists) {
|
|
2692
2803
|
return { path: binaryPath, ready: false, version: "unknown" };
|
|
2693
2804
|
}
|
|
@@ -2777,52 +2888,6 @@ function stopRuntimeDaemonFromNode(params) {
|
|
|
2777
2888
|
);
|
|
2778
2889
|
}
|
|
2779
2890
|
|
|
2780
|
-
// src/lib/node-start.ts
|
|
2781
|
-
import { spawn } from "child_process";
|
|
2782
|
-
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
2783
|
-
import { join as join5 } from "path";
|
|
2784
|
-
import {
|
|
2785
|
-
ensureFiberBinary,
|
|
2786
|
-
getDefaultBinaryPath,
|
|
2787
|
-
ProcessManager
|
|
2788
|
-
} from "@fiber-pay/node";
|
|
2789
|
-
import { startRuntimeService } from "@fiber-pay/runtime";
|
|
2790
|
-
import { createKeyManager } from "@fiber-pay/sdk";
|
|
2791
|
-
|
|
2792
|
-
// src/lib/bootnode.ts
|
|
2793
|
-
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
2794
|
-
import { parse as parseYaml } from "yaml";
|
|
2795
|
-
function extractBootnodeAddrs(configFilePath) {
|
|
2796
|
-
if (!existsSync9(configFilePath)) return [];
|
|
2797
|
-
try {
|
|
2798
|
-
const content = readFileSync5(configFilePath, "utf-8");
|
|
2799
|
-
const doc = parseYaml(content);
|
|
2800
|
-
const addrs = doc?.fiber?.bootnode_addrs;
|
|
2801
|
-
if (!Array.isArray(addrs)) return [];
|
|
2802
|
-
return addrs.filter((a) => typeof a === "string" && a.startsWith("/ip"));
|
|
2803
|
-
} catch {
|
|
2804
|
-
return [];
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
async function autoConnectBootnodes(rpc, bootnodes) {
|
|
2808
|
-
if (bootnodes.length === 0) return;
|
|
2809
|
-
console.log(`\u{1F517} Connecting to ${bootnodes.length} bootnode(s)...`);
|
|
2810
|
-
for (const addr of bootnodes) {
|
|
2811
|
-
const shortId = addr.match(/\/p2p\/(.+)$/)?.[1]?.slice(0, 12) || addr.slice(-12);
|
|
2812
|
-
try {
|
|
2813
|
-
await rpc.connectPeer({ address: addr });
|
|
2814
|
-
console.log(` \u2705 Connected to ${shortId}...`);
|
|
2815
|
-
} catch (err) {
|
|
2816
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2817
|
-
if (msg.toLowerCase().includes("already")) {
|
|
2818
|
-
console.log(` \u2705 Already connected to ${shortId}...`);
|
|
2819
|
-
} else {
|
|
2820
|
-
console.error(` \u26A0\uFE0F Failed to connect to ${shortId}...: ${msg}`);
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
2891
|
// src/lib/node-start.ts
|
|
2827
2892
|
async function runNodeStartCommand(config, options) {
|
|
2828
2893
|
const json = Boolean(options.json);
|
|
@@ -2951,6 +3016,19 @@ async function runNodeStartCommand(config, options) {
|
|
|
2951
3016
|
console.log(`\u{1F9E9} Binary: ${binaryPath}`);
|
|
2952
3017
|
console.log(`\u{1F9E9} Version: ${binaryVersion}`);
|
|
2953
3018
|
}
|
|
3019
|
+
const guardResult = await runMigrationGuard({ dataDir: config.dataDir, binaryPath, json });
|
|
3020
|
+
if (guardResult.checked) {
|
|
3021
|
+
emitStage("migration_check", "ok", {
|
|
3022
|
+
storePath: `${config.dataDir}/store`,
|
|
3023
|
+
needed: false
|
|
3024
|
+
});
|
|
3025
|
+
} else {
|
|
3026
|
+
emitStage("migration_check", "ok", {
|
|
3027
|
+
storePath: `${config.dataDir}/store`,
|
|
3028
|
+
skipped: true,
|
|
3029
|
+
reason: guardResult.skippedReason
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
2954
3032
|
const nodeConfig = {
|
|
2955
3033
|
binaryPath,
|
|
2956
3034
|
dataDir: config.dataDir,
|
|
@@ -3721,67 +3799,334 @@ async function runNodeReadyCommand(config, options) {
|
|
|
3721
3799
|
}
|
|
3722
3800
|
}
|
|
3723
3801
|
|
|
3724
|
-
// src/
|
|
3725
|
-
function
|
|
3726
|
-
const
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3802
|
+
// src/lib/node-stop.ts
|
|
3803
|
+
async function runNodeStopCommand(config, options) {
|
|
3804
|
+
const json = Boolean(options.json);
|
|
3805
|
+
const runtimeMeta = readRuntimeMeta(config.dataDir);
|
|
3806
|
+
const runtimePid = readRuntimePid(config.dataDir);
|
|
3807
|
+
if (runtimeMeta?.daemon && runtimePid && isProcessRunning(runtimePid)) {
|
|
3808
|
+
stopRuntimeDaemonFromNode({ dataDir: config.dataDir, rpcUrl: config.rpcUrl });
|
|
3809
|
+
}
|
|
3810
|
+
removeRuntimeFiles(config.dataDir);
|
|
3811
|
+
const pid = readPidFile(config.dataDir);
|
|
3812
|
+
if (!pid) {
|
|
3813
|
+
if (json) {
|
|
3814
|
+
printJsonError({
|
|
3815
|
+
code: "NODE_NOT_RUNNING",
|
|
3816
|
+
message: "No PID file found. Node may not be running.",
|
|
3817
|
+
recoverable: true,
|
|
3818
|
+
suggestion: "Run `fiber-pay node start` first if you intend to stop a node."
|
|
3819
|
+
});
|
|
3820
|
+
} else {
|
|
3821
|
+
console.log("\u274C No PID file found. Node may not be running.");
|
|
3736
3822
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
|
-
}
|
|
3750
|
-
process.exit(1);
|
|
3823
|
+
process.exit(1);
|
|
3824
|
+
}
|
|
3825
|
+
if (!isProcessRunning(pid)) {
|
|
3826
|
+
if (json) {
|
|
3827
|
+
printJsonError({
|
|
3828
|
+
code: "NODE_NOT_RUNNING",
|
|
3829
|
+
message: `Process ${pid} is not running. Cleaning up PID file.`,
|
|
3830
|
+
recoverable: true,
|
|
3831
|
+
suggestion: "Start the node again if needed; stale PID has been cleaned.",
|
|
3832
|
+
details: { pid, stalePidFileCleaned: true }
|
|
3833
|
+
});
|
|
3834
|
+
} else {
|
|
3835
|
+
console.log(`\u274C Process ${pid} is not running. Cleaning up PID file.`);
|
|
3751
3836
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3837
|
+
removePidFile(config.dataDir);
|
|
3838
|
+
process.exit(1);
|
|
3839
|
+
}
|
|
3840
|
+
if (!json) {
|
|
3841
|
+
console.log(`\u{1F6D1} Stopping node (PID: ${pid})...`);
|
|
3842
|
+
}
|
|
3843
|
+
process.kill(pid, "SIGTERM");
|
|
3844
|
+
let attempts = 0;
|
|
3845
|
+
while (isProcessRunning(pid) && attempts < 30) {
|
|
3846
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
3847
|
+
attempts++;
|
|
3848
|
+
}
|
|
3849
|
+
if (isProcessRunning(pid)) {
|
|
3850
|
+
process.kill(pid, "SIGKILL");
|
|
3851
|
+
}
|
|
3852
|
+
removePidFile(config.dataDir);
|
|
3853
|
+
if (json) {
|
|
3854
|
+
printJsonSuccess({ pid, stopped: true });
|
|
3855
|
+
} else {
|
|
3856
|
+
console.log("\u2705 Node stopped.");
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
// src/lib/node-upgrade.ts
|
|
3861
|
+
import { BinaryManager as BinaryManager2, MigrationManager as MigrationManager2 } from "@fiber-pay/node";
|
|
3862
|
+
async function runNodeUpgradeCommand(config, options) {
|
|
3863
|
+
const json = Boolean(options.json);
|
|
3864
|
+
const installDir = `${config.dataDir}/bin`;
|
|
3865
|
+
const binaryManager = new BinaryManager2(installDir);
|
|
3866
|
+
const pid = readPidFile(config.dataDir);
|
|
3867
|
+
if (pid && isProcessRunning(pid)) {
|
|
3868
|
+
const msg = "The Fiber node is currently running. Stop it before upgrading.";
|
|
3869
|
+
if (json) {
|
|
3870
|
+
printJsonError({
|
|
3871
|
+
code: "NODE_RUNNING",
|
|
3872
|
+
message: msg,
|
|
3873
|
+
recoverable: true,
|
|
3874
|
+
suggestion: "Run `fiber-pay node stop` first, then retry the upgrade."
|
|
3875
|
+
});
|
|
3876
|
+
} else {
|
|
3877
|
+
console.error(`\u274C ${msg}`);
|
|
3878
|
+
console.log(" Run: fiber-pay node stop");
|
|
3879
|
+
}
|
|
3880
|
+
process.exit(1);
|
|
3881
|
+
}
|
|
3882
|
+
let targetTag;
|
|
3883
|
+
if (options.version) {
|
|
3884
|
+
targetTag = binaryManager.normalizeTag(options.version);
|
|
3885
|
+
} else {
|
|
3886
|
+
if (!json) console.log("\u{1F50D} Resolving latest Fiber release...");
|
|
3887
|
+
targetTag = await binaryManager.getLatestTag();
|
|
3888
|
+
}
|
|
3889
|
+
if (!json) console.log(`\u{1F4E6} Target version: ${targetTag}`);
|
|
3890
|
+
const currentInfo = await binaryManager.getBinaryInfo();
|
|
3891
|
+
const targetVersion = targetTag.startsWith("v") ? targetTag.slice(1) : targetTag;
|
|
3892
|
+
const storePath = MigrationManager2.resolveStorePath(config.dataDir);
|
|
3893
|
+
const migrateBinaryPath = binaryManager.getMigrateBinaryPath();
|
|
3894
|
+
let migrationCheck = null;
|
|
3895
|
+
const storeExists = MigrationManager2.storeExists(config.dataDir);
|
|
3896
|
+
if (!json && storeExists) {
|
|
3897
|
+
console.log("\u{1F4C2} Existing store detected.");
|
|
3898
|
+
}
|
|
3899
|
+
if (currentInfo.ready && currentInfo.version === targetVersion && !options.forceMigrate) {
|
|
3900
|
+
if (storeExists) {
|
|
3901
|
+
migrationCheck = await runMigrationAndReport({
|
|
3902
|
+
migrateBinaryPath,
|
|
3903
|
+
storePath,
|
|
3904
|
+
json,
|
|
3905
|
+
checkOnly: Boolean(options.checkOnly),
|
|
3906
|
+
targetVersion,
|
|
3907
|
+
backup: options.backup !== false,
|
|
3908
|
+
forceMigrateAttempt: false
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
const msg = migrationCheck ? `Already installed ${targetTag}. Store compatibility checked.` : `Already installed ${targetTag}. Use --force-migrate to run migration flow anyway.`;
|
|
3912
|
+
if (json) {
|
|
3913
|
+
printJsonSuccess({
|
|
3914
|
+
action: "none",
|
|
3915
|
+
currentVersion: currentInfo.version,
|
|
3916
|
+
targetVersion,
|
|
3917
|
+
message: msg,
|
|
3918
|
+
migration: migrationCheck
|
|
3919
|
+
});
|
|
3920
|
+
} else {
|
|
3921
|
+
console.log(`\u2705 ${msg}`);
|
|
3922
|
+
}
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
const versionMatches = currentInfo.ready && currentInfo.version === targetVersion;
|
|
3926
|
+
const shouldDownload = !versionMatches;
|
|
3927
|
+
if (!json && currentInfo.ready) {
|
|
3928
|
+
console.log(` Current version: v${currentInfo.version}`);
|
|
3929
|
+
}
|
|
3930
|
+
if (shouldDownload) {
|
|
3931
|
+
if (!json && storeExists) {
|
|
3932
|
+
console.log("\u{1F4C2} Existing store detected, will check migration after download.");
|
|
3933
|
+
}
|
|
3934
|
+
if (!json) console.log("\u2B07\uFE0F Downloading new binary...");
|
|
3935
|
+
const showProgress2 = (progress) => {
|
|
3936
|
+
if (!json) {
|
|
3937
|
+
const percent = progress.percent !== void 0 ? ` (${progress.percent}%)` : "";
|
|
3938
|
+
process.stdout.write(`\r [${progress.phase}]${percent} ${progress.message}`.padEnd(80));
|
|
3939
|
+
if (progress.phase === "installing") console.log();
|
|
3763
3940
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3941
|
+
};
|
|
3942
|
+
await binaryManager.download({
|
|
3943
|
+
version: targetTag,
|
|
3944
|
+
force: true,
|
|
3945
|
+
onProgress: showProgress2
|
|
3946
|
+
});
|
|
3947
|
+
} else if (!json && options.forceMigrate) {
|
|
3948
|
+
console.log("\u23ED\uFE0F Skipping binary download: target version is already installed.");
|
|
3949
|
+
console.log("\u{1F501} --force-migrate enabled: attempting migration flow on existing binaries.");
|
|
3950
|
+
}
|
|
3951
|
+
if (storeExists) {
|
|
3952
|
+
migrationCheck = await runMigrationAndReport({
|
|
3953
|
+
migrateBinaryPath,
|
|
3954
|
+
storePath,
|
|
3955
|
+
json,
|
|
3956
|
+
checkOnly: Boolean(options.checkOnly),
|
|
3957
|
+
targetVersion,
|
|
3958
|
+
backup: options.backup !== false,
|
|
3959
|
+
forceMigrateAttempt: Boolean(options.forceMigrate)
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
const newInfo = await binaryManager.getBinaryInfo();
|
|
3963
|
+
if (json) {
|
|
3964
|
+
printJsonSuccess({
|
|
3965
|
+
action: "upgraded",
|
|
3966
|
+
previousVersion: currentInfo.ready ? currentInfo.version : null,
|
|
3967
|
+
currentVersion: newInfo.version,
|
|
3968
|
+
binaryPath: newInfo.path,
|
|
3969
|
+
migrateBinaryPath,
|
|
3970
|
+
migration: migrationCheck
|
|
3971
|
+
});
|
|
3972
|
+
} else {
|
|
3973
|
+
console.log("\n\u2705 Upgrade complete!");
|
|
3974
|
+
console.log(` Version: v${newInfo.version}`);
|
|
3975
|
+
console.log(` Binary: ${newInfo.path}`);
|
|
3976
|
+
console.log("\n Start the node with: fiber-pay node start");
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
async function runMigrationAndReport(opts) {
|
|
3980
|
+
const {
|
|
3981
|
+
migrateBinaryPath,
|
|
3982
|
+
storePath,
|
|
3983
|
+
json,
|
|
3984
|
+
checkOnly,
|
|
3985
|
+
targetVersion,
|
|
3986
|
+
backup,
|
|
3987
|
+
forceMigrateAttempt
|
|
3988
|
+
} = opts;
|
|
3989
|
+
let migrationManager;
|
|
3990
|
+
try {
|
|
3991
|
+
migrationManager = new MigrationManager2(migrateBinaryPath);
|
|
3992
|
+
} catch (err) {
|
|
3993
|
+
const msg = err instanceof Error ? err.message : "fnn-migrate binary not available";
|
|
3994
|
+
if (json) {
|
|
3995
|
+
printJsonError({
|
|
3996
|
+
code: "MIGRATION_TOOL_MISSING",
|
|
3997
|
+
message: msg,
|
|
3998
|
+
recoverable: true,
|
|
3999
|
+
suggestion: "Run `fiber-pay node upgrade` to reinstall binaries, then retry `fiber-pay node upgrade --force-migrate`."
|
|
4000
|
+
});
|
|
4001
|
+
} else {
|
|
4002
|
+
console.error(`
|
|
4003
|
+
\u26A0\uFE0F ${msg}`);
|
|
4004
|
+
console.log(
|
|
4005
|
+
" Run `fiber-pay node upgrade` to reinstall binaries, then retry `fiber-pay node upgrade --force-migrate`."
|
|
4006
|
+
);
|
|
3766
4007
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
4008
|
+
process.exit(1);
|
|
4009
|
+
}
|
|
4010
|
+
if (!json) console.log("\u{1F50D} Checking store compatibility...");
|
|
4011
|
+
let migrationCheck;
|
|
4012
|
+
try {
|
|
4013
|
+
migrationCheck = await migrationManager.check(storePath);
|
|
4014
|
+
} catch (checkErr) {
|
|
4015
|
+
const msg = checkErr instanceof Error ? checkErr.message : String(checkErr);
|
|
4016
|
+
if (json) {
|
|
4017
|
+
printJsonError({
|
|
4018
|
+
code: "MIGRATION_TOOL_MISSING",
|
|
4019
|
+
message: `Migration check failed: ${msg}`,
|
|
4020
|
+
recoverable: true,
|
|
4021
|
+
suggestion: "Run `fiber-pay node upgrade` to reinstall binaries, then retry `fiber-pay node upgrade --force-migrate`."
|
|
4022
|
+
});
|
|
4023
|
+
} else {
|
|
4024
|
+
console.error(`
|
|
4025
|
+
\u26A0\uFE0F Migration check failed: ${msg}`);
|
|
4026
|
+
console.log(
|
|
4027
|
+
" Run `fiber-pay node upgrade` to reinstall binaries, then retry `fiber-pay node upgrade --force-migrate`."
|
|
4028
|
+
);
|
|
3769
4029
|
}
|
|
3770
|
-
process.
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
4030
|
+
process.exit(1);
|
|
4031
|
+
}
|
|
4032
|
+
if (checkOnly) {
|
|
4033
|
+
const normalizedCheck = normalizeMigrationCheck(migrationCheck);
|
|
4034
|
+
if (json) {
|
|
4035
|
+
printJsonSuccess({
|
|
4036
|
+
action: "check-only",
|
|
4037
|
+
targetVersion,
|
|
4038
|
+
migration: normalizedCheck
|
|
4039
|
+
});
|
|
4040
|
+
} else {
|
|
4041
|
+
console.log(`
|
|
4042
|
+
\u{1F4CB} Migration status: ${normalizedCheck.message}`);
|
|
3775
4043
|
}
|
|
3776
|
-
|
|
3777
|
-
|
|
4044
|
+
process.exit(0);
|
|
4045
|
+
}
|
|
4046
|
+
if (!migrationCheck.needed) {
|
|
4047
|
+
if (!json) console.log(" Store is compatible, no migration needed.");
|
|
4048
|
+
return normalizeMigrationCheck(migrationCheck);
|
|
4049
|
+
}
|
|
4050
|
+
if (!migrationCheck.valid && !forceMigrateAttempt) {
|
|
4051
|
+
const normalizedMessage = replaceRawMigrateHint(migrationCheck.message);
|
|
4052
|
+
if (json) {
|
|
4053
|
+
printJsonError({
|
|
4054
|
+
code: "MIGRATION_INCOMPATIBLE",
|
|
4055
|
+
message: normalizedMessage,
|
|
4056
|
+
recoverable: false,
|
|
4057
|
+
suggestion: `Back up your store first (directory: "${storePath}"). Then run \`fiber-pay node upgrade --force-migrate\`. If it still fails, close all channels with the old fnn version, remove the store, and restart with a fresh store. If you attempted migration with backup enabled, you can roll back by restoring the backup directory.`,
|
|
4058
|
+
details: {
|
|
4059
|
+
storePath,
|
|
4060
|
+
migrationCheck: {
|
|
4061
|
+
...migrationCheck,
|
|
4062
|
+
message: normalizedMessage
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
});
|
|
4066
|
+
} else {
|
|
4067
|
+
console.error("\n\u274C Store migration is not possible automatically.");
|
|
4068
|
+
console.log(normalizedMessage);
|
|
4069
|
+
console.log(` 1) Back up store directory: ${storePath}`);
|
|
4070
|
+
console.log(" 2) Try: fiber-pay node upgrade --force-migrate");
|
|
4071
|
+
console.log(
|
|
4072
|
+
" 3) If it still fails, close channels on old fnn, remove store, then restart."
|
|
4073
|
+
);
|
|
4074
|
+
console.log(" 4) If migration created a backup, you can roll back by restoring it.");
|
|
3778
4075
|
}
|
|
3779
|
-
|
|
4076
|
+
process.exit(1);
|
|
4077
|
+
}
|
|
4078
|
+
if (!migrationCheck.valid && !json) {
|
|
4079
|
+
console.log("\u26A0\uFE0F Store check reported incompatibility, but --force-migrate is set.");
|
|
4080
|
+
console.log(" Attempting migration anyway with backup enabled (unless --no-backup).");
|
|
4081
|
+
}
|
|
4082
|
+
if (!json) console.log("\u{1F504} Running database migration...");
|
|
4083
|
+
const result = await migrationManager.migrate({
|
|
4084
|
+
storePath,
|
|
4085
|
+
backup,
|
|
4086
|
+
force: forceMigrateAttempt
|
|
4087
|
+
});
|
|
4088
|
+
if (!result.success) {
|
|
3780
4089
|
if (json) {
|
|
3781
|
-
|
|
4090
|
+
printJsonError({
|
|
4091
|
+
code: "MIGRATION_FAILED",
|
|
4092
|
+
message: result.message,
|
|
4093
|
+
recoverable: !!result.backupPath,
|
|
4094
|
+
suggestion: result.backupPath ? `To roll back, delete the current store at "${storePath}" and restore the backup from "${result.backupPath}".` : "Re-download the previous version or start fresh.",
|
|
4095
|
+
details: { output: result.output, backupPath: result.backupPath }
|
|
4096
|
+
});
|
|
3782
4097
|
} else {
|
|
3783
|
-
console.
|
|
4098
|
+
console.error("\n\u274C Migration failed.");
|
|
4099
|
+
console.log(result.message);
|
|
3784
4100
|
}
|
|
4101
|
+
process.exit(1);
|
|
4102
|
+
}
|
|
4103
|
+
if (!json) {
|
|
4104
|
+
console.log(`\u2705 ${result.message}`);
|
|
4105
|
+
if (result.backupPath) {
|
|
4106
|
+
console.log(` Backup: ${result.backupPath}`);
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
try {
|
|
4110
|
+
const postCheck = await migrationManager.check(storePath);
|
|
4111
|
+
return normalizeMigrationCheck(postCheck);
|
|
4112
|
+
} catch (err) {
|
|
4113
|
+
if (!json) {
|
|
4114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4115
|
+
console.error("\u26A0\uFE0F Post-migration check failed; final migration status may be stale.");
|
|
4116
|
+
console.error(` ${message}`);
|
|
4117
|
+
}
|
|
4118
|
+
return normalizeMigrationCheck(migrationCheck);
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
// src/commands/node.ts
|
|
4123
|
+
function createNodeCommand(config) {
|
|
4124
|
+
const node = new Command8("node").description("Node management");
|
|
4125
|
+
node.command("start").option("--daemon", "Start node in detached background mode (node + runtime)").option("--runtime-proxy-listen <host:port>", "Runtime monitor proxy listen address").option("--event-stream <format>", "Event stream format for --json mode (jsonl)", "jsonl").option("--quiet-fnn", "Do not mirror fnn stdout/stderr to console; keep file persistence").option("--json").action(async (options) => {
|
|
4126
|
+
await runNodeStartCommand(config, options);
|
|
4127
|
+
});
|
|
4128
|
+
node.command("stop").option("--json").action(async (options) => {
|
|
4129
|
+
await runNodeStopCommand(config, options);
|
|
3785
4130
|
});
|
|
3786
4131
|
node.command("status").option("--json").action(async (options) => {
|
|
3787
4132
|
await runNodeStatusCommand(config, options);
|
|
@@ -3812,6 +4157,12 @@ function createNodeCommand(config) {
|
|
|
3812
4157
|
printNodeInfoHuman(output);
|
|
3813
4158
|
}
|
|
3814
4159
|
});
|
|
4160
|
+
node.command("upgrade").description("Upgrade the Fiber node binary and migrate the database if needed").option("--version <version>", "Target Fiber version (default: latest)").option("--no-backup", "Skip creating a store backup before migration").option("--check-only", "Only check if migration is needed, do not migrate").option(
|
|
4161
|
+
"--force-migrate",
|
|
4162
|
+
"Force migration attempt even when compatibility check reports incompatible data"
|
|
4163
|
+
).option("--json").action(async (options) => {
|
|
4164
|
+
await runNodeUpgradeCommand(config, options);
|
|
4165
|
+
});
|
|
3815
4166
|
return node;
|
|
3816
4167
|
}
|
|
3817
4168
|
|
|
@@ -4575,8 +4926,8 @@ function createRuntimeCommand(config) {
|
|
|
4575
4926
|
import { Command as Command12 } from "commander";
|
|
4576
4927
|
|
|
4577
4928
|
// src/lib/build-info.ts
|
|
4578
|
-
var CLI_VERSION = "0.1.0-rc.
|
|
4579
|
-
var CLI_COMMIT = "
|
|
4929
|
+
var CLI_VERSION = "0.1.0-rc.4";
|
|
4930
|
+
var CLI_COMMIT = "20dc82d290856d411382a4ec30f912b913b4f956";
|
|
4580
4931
|
|
|
4581
4932
|
// src/commands/version.ts
|
|
4582
4933
|
function createVersionCommand() {
|
|
@@ -4594,6 +4945,55 @@ function createVersionCommand() {
|
|
|
4594
4945
|
});
|
|
4595
4946
|
}
|
|
4596
4947
|
|
|
4948
|
+
// src/lib/argv.ts
|
|
4949
|
+
var GLOBAL_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
4950
|
+
"--profile",
|
|
4951
|
+
"--data-dir",
|
|
4952
|
+
"--rpc-url",
|
|
4953
|
+
"--network",
|
|
4954
|
+
"--key-password",
|
|
4955
|
+
"--binary-path"
|
|
4956
|
+
]);
|
|
4957
|
+
function isOptionToken(token) {
|
|
4958
|
+
return token.startsWith("-") && token !== "-";
|
|
4959
|
+
}
|
|
4960
|
+
function hasInlineValue(token) {
|
|
4961
|
+
return token.includes("=");
|
|
4962
|
+
}
|
|
4963
|
+
function getFirstPositional(argv) {
|
|
4964
|
+
for (let index = 2; index < argv.length; index++) {
|
|
4965
|
+
const token = argv[index];
|
|
4966
|
+
if (token === "--") {
|
|
4967
|
+
return argv[index + 1];
|
|
4968
|
+
}
|
|
4969
|
+
if (!isOptionToken(token)) {
|
|
4970
|
+
return token;
|
|
4971
|
+
}
|
|
4972
|
+
if (GLOBAL_OPTIONS_WITH_VALUE.has(token) && !hasInlineValue(token)) {
|
|
4973
|
+
const next = argv[index + 1];
|
|
4974
|
+
if (next && !isOptionToken(next)) {
|
|
4975
|
+
index++;
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
return void 0;
|
|
4980
|
+
}
|
|
4981
|
+
function hasTopLevelVersionFlag(argv) {
|
|
4982
|
+
for (let index = 2; index < argv.length; index++) {
|
|
4983
|
+
const token = argv[index];
|
|
4984
|
+
if (token === "--") {
|
|
4985
|
+
return false;
|
|
4986
|
+
}
|
|
4987
|
+
if (token === "--version" || token === "-v") {
|
|
4988
|
+
return true;
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
return false;
|
|
4992
|
+
}
|
|
4993
|
+
function isTopLevelVersionRequest(argv) {
|
|
4994
|
+
return !getFirstPositional(argv) && hasTopLevelVersionFlag(argv);
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4597
4997
|
// src/index.ts
|
|
4598
4998
|
function shouldOutputJson() {
|
|
4599
4999
|
return process.argv.includes("--json");
|
|
@@ -4695,10 +5095,15 @@ function printFatal(error) {
|
|
|
4695
5095
|
}
|
|
4696
5096
|
}
|
|
4697
5097
|
async function main() {
|
|
4698
|
-
|
|
5098
|
+
const argv = process.argv;
|
|
5099
|
+
if (isTopLevelVersionRequest(argv)) {
|
|
5100
|
+
console.log(`${CLI_VERSION} (${CLI_COMMIT})`);
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
applyGlobalOverrides(argv);
|
|
4699
5104
|
const config = getEffectiveConfig(explicitFlags).config;
|
|
4700
5105
|
const program = new Command13();
|
|
4701
|
-
program.name("fiber-pay").description("AI Agent Payment SDK for CKB Lightning Network").
|
|
5106
|
+
program.name("fiber-pay").description("AI Agent Payment SDK for CKB Lightning Network").option("--profile <name>", "Use profile at ~/.fiber-pay/profiles/<name>").option("--data-dir <path>", "Override data directory for all commands").option("--rpc-url <url>", "Override RPC URL for all commands").option("--network <network>", "Override network for all commands (testnet|mainnet)").option("--key-password <password>", "Override key password for all commands").option("--binary-path <path>", "Override fiber binary path for all commands").showHelpAfterError().showSuggestionAfterError();
|
|
4702
5107
|
program.exitOverride();
|
|
4703
5108
|
program.configureOutput({
|
|
4704
5109
|
writeOut: (str) => process.stdout.write(str),
|
|
@@ -4720,7 +5125,7 @@ async function main() {
|
|
|
4720
5125
|
program.addCommand(createConfigCommand(config));
|
|
4721
5126
|
program.addCommand(createRuntimeCommand(config));
|
|
4722
5127
|
program.addCommand(createVersionCommand());
|
|
4723
|
-
await program.parseAsync(
|
|
5128
|
+
await program.parseAsync(argv);
|
|
4724
5129
|
}
|
|
4725
5130
|
main().catch((error) => {
|
|
4726
5131
|
const commanderCode = error && typeof error === "object" && "code" in error ? String(error.code) : void 0;
|