@1sat/cli 0.0.83 → 0.0.85
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/package.json +48 -47
- package/src/commands/serve.ts +147 -36
- package/src/config.ts +59 -0
- package/src/help.ts +3 -8
- package/src/commands/serve-messagebox.ts +0 -221
package/package.json
CHANGED
|
@@ -1,49 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
2
|
+
"name": "@1sat/cli",
|
|
3
|
+
"version": "0.0.85",
|
|
4
|
+
"description": "CLI for 1Sat Ordinals SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/cli.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"1sat": "./src/cli.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"scripts"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"preinstall": "node scripts/check-bun.cjs",
|
|
16
|
+
"build": "bun build ./src/cli.ts --compile --outfile=bin/1sat --external pg --external pg-native --external pg-query-stream --external tedious --external oracledb --external mysql --external mariadb --external 'mariadb/*'",
|
|
17
|
+
"dev": "bun run src/cli.ts",
|
|
18
|
+
"lint": "biome check src",
|
|
19
|
+
"test": "bun test"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"1sat",
|
|
23
|
+
"bsv",
|
|
24
|
+
"ordinals",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@1sat/actions": "0.0.189",
|
|
30
|
+
"@1sat/client": "0.0.45",
|
|
31
|
+
"@1sat/types": "0.0.36",
|
|
32
|
+
"@1sat/wallet-node": "0.0.62",
|
|
33
|
+
"@1sat/wallet-server": "0.0.31",
|
|
34
|
+
"@bsv/sdk": "^2.1.6",
|
|
35
|
+
"@bsv/wallet-toolbox": "2.1.24",
|
|
36
|
+
"chalk": "^5.0.0",
|
|
37
|
+
"@clack/prompts": "^0.8.0",
|
|
38
|
+
"bitcoin-backup": "^0.0.11",
|
|
39
|
+
"dotenv": "^17.0.0",
|
|
40
|
+
"evlog": "^2.10.0",
|
|
41
|
+
"knex": "^3.1.0",
|
|
42
|
+
"@bopen-io/messagebox-server": "^1.3.2",
|
|
43
|
+
"pg": "^8.11.3",
|
|
44
|
+
"sqlite3": "^6.0.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/bun": "^1.3.9",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
}
|
|
49
50
|
}
|
package/src/commands/serve.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `1sat serve` command — launch
|
|
2
|
+
* `1sat serve` command — launch the unified host server and/or monitor.
|
|
3
3
|
*
|
|
4
|
-
* 1sat serve
|
|
5
|
-
* 1sat serve wallet Wallet server only
|
|
4
|
+
* 1sat serve Host server (storage + hosting + paymail + messagebox) + monitor
|
|
5
|
+
* 1sat serve wallet Wallet storage server only
|
|
6
6
|
* 1sat serve monitor Monitor daemon only
|
|
7
|
-
* 1sat serve messagebox BSV message-box server
|
|
8
7
|
*
|
|
9
8
|
* The server wraps the same wallet instance the CLI uses. Storage, active
|
|
10
9
|
* remote, and backups all come from `~/.1sat/cli/config.json` via the same
|
|
@@ -14,7 +13,9 @@
|
|
|
14
13
|
* 1sat config set server.port 8100
|
|
15
14
|
* 1sat config set server.host 0.0.0.0
|
|
16
15
|
* 1sat config set server.accounts.enabled true
|
|
17
|
-
* 1sat config set server.
|
|
16
|
+
* 1sat config set server.hosting.enabled true
|
|
17
|
+
* 1sat config set server.paymail.baseUrl https://1sat.app
|
|
18
|
+
* 1sat config set server.monitor.enabled false
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import { join } from 'node:path'
|
|
@@ -23,12 +24,18 @@ import {
|
|
|
23
24
|
type NodeWalletStorageConfig,
|
|
24
25
|
createNodeWallet,
|
|
25
26
|
} from '@1sat/wallet-node'
|
|
26
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
KnexPendingStore,
|
|
29
|
+
createHostServer,
|
|
30
|
+
createWalletServer,
|
|
31
|
+
} from '@1sat/wallet-server'
|
|
27
32
|
import type { PrivateKey } from '@bsv/sdk'
|
|
28
33
|
import { initLogger } from 'evlog'
|
|
34
|
+
import knexLib from 'knex'
|
|
29
35
|
import type { GlobalFlags } from '../args'
|
|
30
36
|
import {
|
|
31
37
|
type ServerAccountsConfig,
|
|
38
|
+
type ServerMessageboxConfig,
|
|
32
39
|
type ServerStorageConfig,
|
|
33
40
|
loadConfig,
|
|
34
41
|
setConfigPath,
|
|
@@ -43,7 +50,6 @@ import {
|
|
|
43
50
|
createAccountsConfigLoader,
|
|
44
51
|
resolveRateProvider,
|
|
45
52
|
} from '../repricer'
|
|
46
|
-
import { startMessagebox } from './serve-messagebox'
|
|
47
53
|
|
|
48
54
|
const DEFAULT_HOST = '127.0.0.1'
|
|
49
55
|
const DEFAULT_PORT = 8100
|
|
@@ -54,7 +60,7 @@ const DEFAULT_SATS_PER_UNIT = 1_000_000
|
|
|
54
60
|
const DEFAULT_DURATION_BLOCKS = 4383
|
|
55
61
|
const DEFAULT_STORAGE_IDENTITY_KEY = '1sat-cli-default'
|
|
56
62
|
|
|
57
|
-
type ServeMode = 'all' | 'wallet' | 'monitor'
|
|
63
|
+
type ServeMode = 'all' | 'wallet' | 'monitor'
|
|
58
64
|
|
|
59
65
|
interface ResolvedServe {
|
|
60
66
|
chain: 'main' | 'test'
|
|
@@ -68,6 +74,7 @@ interface ResolvedServe {
|
|
|
68
74
|
activeRemote?: string
|
|
69
75
|
backups?: string[]
|
|
70
76
|
accounts: ResolvedAccounts
|
|
77
|
+
monitorEnabled: boolean
|
|
71
78
|
privateKey: PrivateKey
|
|
72
79
|
}
|
|
73
80
|
|
|
@@ -103,20 +110,6 @@ export async function handleServeCommand(
|
|
|
103
110
|
|
|
104
111
|
initLogger({ env: { service: `1sat-cli-serve-${mode}` } })
|
|
105
112
|
|
|
106
|
-
if (mode === 'messagebox') {
|
|
107
|
-
const handle = await startMessagebox(opts)
|
|
108
|
-
try {
|
|
109
|
-
await waitForShutdown()
|
|
110
|
-
} finally {
|
|
111
|
-
try {
|
|
112
|
-
await handle.stop()
|
|
113
|
-
} catch (err) {
|
|
114
|
-
console.error(`Error during shutdown: ${(err as Error).message}`)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
113
|
const resolved = await resolveServe(opts)
|
|
121
114
|
const handles: Stoppable[] = []
|
|
122
115
|
|
|
@@ -139,7 +132,6 @@ function resolveMode(subcommand: string | undefined): ServeMode | null {
|
|
|
139
132
|
switch (subcommand) {
|
|
140
133
|
case 'wallet':
|
|
141
134
|
case 'monitor':
|
|
142
|
-
case 'messagebox':
|
|
143
135
|
return subcommand
|
|
144
136
|
default:
|
|
145
137
|
return null
|
|
@@ -187,6 +179,7 @@ async function resolveServe(opts: GlobalFlags): Promise<ResolvedServe> {
|
|
|
187
179
|
activeRemote: config.activeRemote,
|
|
188
180
|
backups: config.backups,
|
|
189
181
|
accounts: resolveAccounts(server.accounts),
|
|
182
|
+
monitorEnabled: server.monitor?.enabled !== false,
|
|
190
183
|
privateKey,
|
|
191
184
|
}
|
|
192
185
|
}
|
|
@@ -258,12 +251,17 @@ async function runWithStorage(
|
|
|
258
251
|
storage,
|
|
259
252
|
activeRemote: resolved.activeRemote,
|
|
260
253
|
backups: resolved.backups,
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// initial runOnce is redundant in all three cases.
|
|
254
|
+
// Each serve mode manages monitor work explicitly (see runMonitor
|
|
255
|
+
// below); the factory's initial runOnce is redundant in every case.
|
|
264
256
|
skipInitialMonitor: true,
|
|
265
257
|
})
|
|
266
258
|
|
|
259
|
+
// One active monitor per deployment: wallet mode never runs it, monitor
|
|
260
|
+
// mode always does, all mode runs it unless server.monitor.enabled=false
|
|
261
|
+
// (set on redundant host instances that share a dedicated serve monitor).
|
|
262
|
+
const runMonitor =
|
|
263
|
+
mode === 'monitor' || (mode === 'all' && resolved.monitorEnabled)
|
|
264
|
+
|
|
267
265
|
const accounts =
|
|
268
266
|
mode === 'monitor'
|
|
269
267
|
? undefined
|
|
@@ -272,9 +270,9 @@ async function runWithStorage(
|
|
|
272
270
|
const serverHandle =
|
|
273
271
|
mode === 'monitor'
|
|
274
272
|
? undefined
|
|
275
|
-
: await startWalletServer(resolved, walletResult, accounts)
|
|
273
|
+
: await startWalletServer(resolved, walletResult, accounts, mode)
|
|
276
274
|
|
|
277
|
-
if (
|
|
275
|
+
if (runMonitor) {
|
|
278
276
|
const accountsCfg = resolved.accounts
|
|
279
277
|
const r = accountsCfg.repricer
|
|
280
278
|
if (
|
|
@@ -310,7 +308,7 @@ async function runWithStorage(
|
|
|
310
308
|
}
|
|
311
309
|
}
|
|
312
310
|
|
|
313
|
-
if (
|
|
311
|
+
if (runMonitor) {
|
|
314
312
|
// startTasks loops until stopTasks flips its flag. Fire without
|
|
315
313
|
// awaiting so the caller can install shutdown handlers and write
|
|
316
314
|
// the monitor pid file.
|
|
@@ -323,7 +321,7 @@ async function runWithStorage(
|
|
|
323
321
|
|
|
324
322
|
return {
|
|
325
323
|
async stop() {
|
|
326
|
-
if (
|
|
324
|
+
if (runMonitor) {
|
|
327
325
|
walletResult.monitor.stopTasks()
|
|
328
326
|
clearMonitorPid(resolved.dataDir, process.pid)
|
|
329
327
|
}
|
|
@@ -339,20 +337,133 @@ async function startWalletServer(
|
|
|
339
337
|
resolved: ResolvedServe,
|
|
340
338
|
walletResult: NodeWalletResult,
|
|
341
339
|
accounts: AccountsRuntime | undefined,
|
|
340
|
+
mode: ServeMode,
|
|
342
341
|
): Promise<{ stop(): Promise<void> }> {
|
|
343
|
-
const
|
|
342
|
+
const config = loadConfig()
|
|
343
|
+
const hostingCfg = config.server?.hosting
|
|
344
|
+
const hostingEnabled = hostingCfg?.enabled === true
|
|
345
|
+
const hosting = hostingEnabled
|
|
346
|
+
? {
|
|
347
|
+
getConfig: () => {
|
|
348
|
+
const h = loadConfig().server?.hosting
|
|
349
|
+
return {
|
|
350
|
+
enabled: h?.enabled === true,
|
|
351
|
+
priceSats: h?.priceSats ?? 10_000,
|
|
352
|
+
periodSeconds: h?.periodSeconds ?? 2_592_000,
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
: undefined
|
|
357
|
+
|
|
358
|
+
if (mode === 'wallet') {
|
|
359
|
+
const handle = createWalletServer({
|
|
360
|
+
wallet: walletResult.wallet,
|
|
361
|
+
storage: walletResult.getActiveStorage(),
|
|
362
|
+
serverIdentityKey: walletResult.wallet.identityKey,
|
|
363
|
+
listen: { port: resolved.port, host: resolved.host },
|
|
364
|
+
publicPath: '/',
|
|
365
|
+
internalPath: null,
|
|
366
|
+
accounts: accounts?.walletServerAccounts,
|
|
367
|
+
hosting,
|
|
368
|
+
})
|
|
369
|
+
const port = await handle.start()
|
|
370
|
+
const notes = [
|
|
371
|
+
resolved.accounts.enabled ? 'accounts: on' : '',
|
|
372
|
+
hostingEnabled ? 'hosting: on' : '',
|
|
373
|
+
]
|
|
374
|
+
.filter(Boolean)
|
|
375
|
+
.join(', ')
|
|
376
|
+
console.log(
|
|
377
|
+
`[wallet] listening on ${resolved.host}:${port}${notes ? ` (${notes})` : ''}`,
|
|
378
|
+
)
|
|
379
|
+
return { stop: () => handle.stop() }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Unified host: storage + messagebox + paymail + hosting
|
|
383
|
+
const serverCfg = config.server ?? {}
|
|
384
|
+
const paymailCfg = serverCfg.paymail
|
|
385
|
+
const messageboxCfg = serverCfg.messagebox
|
|
386
|
+
|
|
387
|
+
const knex = createMessageboxKnex(
|
|
388
|
+
serverCfg.storage ?? { provider: 'bun-sqlite' },
|
|
389
|
+
messageboxCfg,
|
|
390
|
+
resolved,
|
|
391
|
+
)
|
|
392
|
+
const pendingStore = new KnexPendingStore(knex)
|
|
393
|
+
await pendingStore.init()
|
|
394
|
+
|
|
395
|
+
const handle = await createHostServer({
|
|
344
396
|
wallet: walletResult.wallet,
|
|
345
397
|
storage: walletResult.getActiveStorage(),
|
|
346
398
|
serverIdentityKey: walletResult.wallet.identityKey,
|
|
347
399
|
listen: { port: resolved.port, host: resolved.host },
|
|
348
|
-
publicPath: '/',
|
|
349
|
-
internalPath: null,
|
|
350
400
|
accounts: accounts?.walletServerAccounts,
|
|
401
|
+
hosting,
|
|
402
|
+
paymail: {
|
|
403
|
+
baseUrl: paymailCfg?.baseUrl ?? `http://${resolved.host}:${resolved.port}`,
|
|
404
|
+
stackUrl: paymailCfg?.stackUrl ?? 'https://api.1sat.app',
|
|
405
|
+
pendingStore,
|
|
406
|
+
hostWallet: hostingEnabled ? walletResult.wallet : undefined,
|
|
407
|
+
requireEntitlement: hostingEnabled,
|
|
408
|
+
messageboxUrl: `http://127.0.0.1:${resolved.port}/`,
|
|
409
|
+
hostPrivateKey: resolved.privateKey,
|
|
410
|
+
},
|
|
411
|
+
messagebox: {
|
|
412
|
+
knex,
|
|
413
|
+
websockets: messageboxCfg?.websockets !== false,
|
|
414
|
+
},
|
|
351
415
|
})
|
|
352
416
|
const port = await handle.start()
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
417
|
+
const notes = [
|
|
418
|
+
resolved.accounts.enabled ? 'accounts: on' : '',
|
|
419
|
+
hostingEnabled ? 'hosting: on' : '',
|
|
420
|
+
]
|
|
421
|
+
.filter(Boolean)
|
|
422
|
+
.join(', ')
|
|
423
|
+
console.log(
|
|
424
|
+
`[host] listening on ${resolved.host}:${port}${notes ? ` (${notes})` : ''}`,
|
|
425
|
+
)
|
|
426
|
+
return {
|
|
427
|
+
stop: async () => {
|
|
428
|
+
await handle.stop()
|
|
429
|
+
if (knex) await knex.destroy()
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const DEFAULT_PG_SCHEMA = 'messagebox'
|
|
435
|
+
|
|
436
|
+
function createMessageboxKnex(
|
|
437
|
+
storage: ServerStorageConfig,
|
|
438
|
+
messagebox: ServerMessageboxConfig | undefined,
|
|
439
|
+
resolved: ResolvedServe,
|
|
440
|
+
) {
|
|
441
|
+
if (storage.provider === 'bun-sqlite') {
|
|
442
|
+
const filename =
|
|
443
|
+
messagebox?.dbPath ?? join(resolved.dataDir, `messagebox-${resolved.chain}.db`)
|
|
444
|
+
return (knexLib as any)({
|
|
445
|
+
client: 'sqlite3',
|
|
446
|
+
connection: { filename },
|
|
447
|
+
useNullAsDefault: true,
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
if (storage.provider === 'pg') {
|
|
451
|
+
if (messagebox?.dbUrl) {
|
|
452
|
+
return (knexLib as any)({
|
|
453
|
+
client: 'pg',
|
|
454
|
+
connection: { connectionString: messagebox.dbUrl },
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
const schema = messagebox?.pgSchema ?? DEFAULT_PG_SCHEMA
|
|
458
|
+
return (knexLib as any)({
|
|
459
|
+
client: 'pg',
|
|
460
|
+
connection: { connectionString: storage.dbUrl },
|
|
461
|
+
searchPath: [schema],
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
fatal(
|
|
465
|
+
`server.storage.provider '${(storage as { provider: string }).provider}' is not supported for messagebox storage.`,
|
|
466
|
+
)
|
|
356
467
|
}
|
|
357
468
|
|
|
358
469
|
interface AccountsRuntime {
|
package/src/config.ts
CHANGED
|
@@ -85,6 +85,59 @@ export interface ServerMessageboxConfig {
|
|
|
85
85
|
walletStorageUrl?: string
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export interface ServerPaymailConfig {
|
|
89
|
+
/** Port to bind. Defaults to `8466` or ONESAT_PAYMAIL_PORT. */
|
|
90
|
+
port?: number
|
|
91
|
+
/**
|
|
92
|
+
* Public base URL used in /.well-known/bsvalias capability links
|
|
93
|
+
* (e.g. https://1sat.app). Required for serve paymail.
|
|
94
|
+
*/
|
|
95
|
+
baseUrl?: string
|
|
96
|
+
/** Stack API root (OpNS, beef, tx). Defaults to https://api.1sat.app */
|
|
97
|
+
stackUrl?: string
|
|
98
|
+
/**
|
|
99
|
+
* SQLite path for in-flight P2P payment references (Go paymail port).
|
|
100
|
+
* Defaults to `<dataDir>/paymail-pending-{chain}.db`.
|
|
101
|
+
*/
|
|
102
|
+
pendingDbPath?: string
|
|
103
|
+
/**
|
|
104
|
+
* Messagebox URL for payment_inbox delivery after receive.
|
|
105
|
+
* Defaults to https://messagebox.1sat.app when unset is OK to skip —
|
|
106
|
+
* set explicitly to enable (same as Go messagebox_url).
|
|
107
|
+
*/
|
|
108
|
+
messageboxUrl?: string
|
|
109
|
+
/**
|
|
110
|
+
* When true, open host wallet storage and require active HOSTING_BASKET
|
|
111
|
+
* receipt for the bound identity. Default false until subscribe path ships.
|
|
112
|
+
*/
|
|
113
|
+
requireEntitlement?: boolean
|
|
114
|
+
/**
|
|
115
|
+
* Wallet storage URL for entitlement listOutputs (and optional later
|
|
116
|
+
* subscribe). Defaults to local wallet serve URL like messagebox.
|
|
117
|
+
*/
|
|
118
|
+
walletStorageUrl?: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface ServerHostingConfig {
|
|
122
|
+
/** Master toggle. Defaults to false. */
|
|
123
|
+
enabled?: boolean
|
|
124
|
+
/** Sats per subscription period. */
|
|
125
|
+
priceSats?: number
|
|
126
|
+
/** Period length in seconds (e.g. 2592000 ≈ 30 days). */
|
|
127
|
+
periodSeconds?: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ServerMonitorConfig {
|
|
131
|
+
/**
|
|
132
|
+
* Run the background monitor task loop (post waiting txs, check for
|
|
133
|
+
* proofs, account repricing). Defaults to true. Set false on redundant
|
|
134
|
+
* host instances that run alongside a dedicated `serve monitor`, so a
|
|
135
|
+
* single active monitor runs per deployment. `serve monitor` always runs
|
|
136
|
+
* the loop regardless of this flag.
|
|
137
|
+
*/
|
|
138
|
+
enabled?: boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
88
141
|
export interface ServerConfig {
|
|
89
142
|
/** Hostname to bind. Defaults to `127.0.0.1`. */
|
|
90
143
|
host?: string
|
|
@@ -96,6 +149,12 @@ export interface ServerConfig {
|
|
|
96
149
|
accounts?: ServerAccountsConfig
|
|
97
150
|
/** Messagebox subcommand settings. */
|
|
98
151
|
messagebox?: ServerMessageboxConfig
|
|
152
|
+
/** Paymail subcommand settings. */
|
|
153
|
+
paymail?: ServerPaymailConfig
|
|
154
|
+
/** Hosted paymail subscription (wallet serve /hosting/*). */
|
|
155
|
+
hosting?: ServerHostingConfig
|
|
156
|
+
/** Background monitor task loop. Defaults to enabled. */
|
|
157
|
+
monitor?: ServerMonitorConfig
|
|
99
158
|
}
|
|
100
159
|
|
|
101
160
|
export interface OneSatCliConfig {
|
package/src/help.ts
CHANGED
|
@@ -560,19 +560,14 @@ export const COMMANDS: CommandSpec[] = [
|
|
|
560
560
|
group: 'Server',
|
|
561
561
|
name: 'serve',
|
|
562
562
|
description:
|
|
563
|
-
'Run
|
|
563
|
+
'Run unified host server (storage + hosting + paymail + messagebox) and/or monitor (config under server.* in config.json)',
|
|
564
564
|
subcommands: [
|
|
565
565
|
{
|
|
566
566
|
name: '(no subcommand)',
|
|
567
|
-
description: '
|
|
567
|
+
description: 'Host server + monitor daemon',
|
|
568
568
|
},
|
|
569
|
-
{ name: 'wallet', description: 'Wallet server only (BRC-100 HTTP)' },
|
|
569
|
+
{ name: 'wallet', description: 'Wallet storage server only (BRC-100 HTTP)' },
|
|
570
570
|
{ name: 'monitor', description: 'Monitor daemon only' },
|
|
571
|
-
{
|
|
572
|
-
name: 'messagebox',
|
|
573
|
-
description:
|
|
574
|
-
'BSV message-box server (port 8771 default; uses wallet identity)',
|
|
575
|
-
},
|
|
576
571
|
],
|
|
577
572
|
},
|
|
578
573
|
{
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `1sat serve messagebox` — launch the BSV message-box server using the
|
|
3
|
-
* CLI's wallet identity.
|
|
4
|
-
*
|
|
5
|
-
* messagebox-server boots itself as a side effect of `import` once env vars
|
|
6
|
-
* are populated, so this handler:
|
|
7
|
-
* 1. Reads CLI config + private key
|
|
8
|
-
* 2. Composes env (SERVER_PRIVATE_KEY, PORT, KNEX_DB_*, WALLET_STORAGE_URL, …)
|
|
9
|
-
* 3. Switches cwd to the messagebox-server install dir so its knexfile's
|
|
10
|
-
* relative `./out/src/migrations` path resolves correctly
|
|
11
|
-
* 4. Dynamically imports `@bopen-io/messagebox-server`
|
|
12
|
-
* 5. Awaits SIGINT/SIGTERM, then closes the HTTP and WebSocket servers
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { dirname, join } from 'node:path'
|
|
16
|
-
import { fileURLToPath } from 'node:url'
|
|
17
|
-
import type { PrivateKey } from '@bsv/sdk'
|
|
18
|
-
import type { GlobalFlags } from '../args'
|
|
19
|
-
import {
|
|
20
|
-
type ServerMessageboxConfig,
|
|
21
|
-
type ServerStorageConfig,
|
|
22
|
-
ensureDataDir,
|
|
23
|
-
loadConfig,
|
|
24
|
-
} from '../config'
|
|
25
|
-
import { loadKey } from '../keys'
|
|
26
|
-
import { fatal } from '../output'
|
|
27
|
-
|
|
28
|
-
const DEFAULT_MESSAGEBOX_PORT = 8771
|
|
29
|
-
const DEFAULT_WALLET_HOST = '127.0.0.1'
|
|
30
|
-
const DEFAULT_WALLET_PORT = 8100
|
|
31
|
-
const DEFAULT_PG_SCHEMA = 'messagebox'
|
|
32
|
-
|
|
33
|
-
interface ResolvedMessagebox {
|
|
34
|
-
chain: 'main' | 'test'
|
|
35
|
-
port: number
|
|
36
|
-
websockets: boolean
|
|
37
|
-
walletStorageUrl: string
|
|
38
|
-
knexClient: string
|
|
39
|
-
knexConnection: Record<string, unknown>
|
|
40
|
-
identityHex: string
|
|
41
|
-
identityPub: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface MessageboxModule {
|
|
45
|
-
http: { close(cb?: (err?: Error) => void): void }
|
|
46
|
-
io: { close(cb?: () => void): void } | null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface MessageboxStoppable {
|
|
50
|
-
stop(): Promise<void>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function startMessagebox(
|
|
54
|
-
opts: GlobalFlags,
|
|
55
|
-
): Promise<MessageboxStoppable> {
|
|
56
|
-
const resolved = await resolveMessagebox(opts)
|
|
57
|
-
applyEnv(resolved)
|
|
58
|
-
|
|
59
|
-
console.log(`[messagebox] starting on 0.0.0.0:${resolved.port}`)
|
|
60
|
-
console.log(`[messagebox] identity: ${resolved.identityPub.slice(0, 16)}…`)
|
|
61
|
-
console.log(`[messagebox] storage: ${resolved.knexClient}`)
|
|
62
|
-
console.log(`[messagebox] wallet: ${resolved.walletStorageUrl}`)
|
|
63
|
-
console.log(
|
|
64
|
-
`[messagebox] websockets: ${resolved.websockets ? 'enabled' : 'disabled'}`,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// messagebox-server's knexfile uses `./out/src/migrations` relative to
|
|
68
|
-
// cwd. Switch cwd to its install dir so that path resolves correctly
|
|
69
|
-
// regardless of where the CLI was invoked from.
|
|
70
|
-
const packageJsonUrl = import.meta.resolve(
|
|
71
|
-
'@bopen-io/messagebox-server/package.json',
|
|
72
|
-
)
|
|
73
|
-
const messageboxDir = dirname(fileURLToPath(packageJsonUrl))
|
|
74
|
-
process.chdir(messageboxDir)
|
|
75
|
-
|
|
76
|
-
// messagebox-server boots itself when its index module is loaded.
|
|
77
|
-
const mbox = (await import(
|
|
78
|
-
'@bopen-io/messagebox-server'
|
|
79
|
-
)) as unknown as MessageboxModule
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
async stop() {
|
|
83
|
-
await new Promise<void>((resolve) => {
|
|
84
|
-
try {
|
|
85
|
-
mbox.http.close(() => resolve())
|
|
86
|
-
} catch {
|
|
87
|
-
resolve()
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
if (mbox.io) {
|
|
91
|
-
await new Promise<void>((resolve) => {
|
|
92
|
-
try {
|
|
93
|
-
mbox.io?.close(() => resolve())
|
|
94
|
-
} catch {
|
|
95
|
-
resolve()
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function resolveMessagebox(
|
|
104
|
-
opts: GlobalFlags,
|
|
105
|
-
): Promise<ResolvedMessagebox> {
|
|
106
|
-
const config = loadConfig()
|
|
107
|
-
const server = config.server ?? {}
|
|
108
|
-
const messagebox: ServerMessageboxConfig = server.messagebox ?? {}
|
|
109
|
-
|
|
110
|
-
const chain = opts.chain ?? config.chain ?? 'main'
|
|
111
|
-
const port = resolvePort(messagebox.port)
|
|
112
|
-
const websockets = messagebox.websockets !== false
|
|
113
|
-
|
|
114
|
-
let privateKey: PrivateKey
|
|
115
|
-
try {
|
|
116
|
-
privateKey = await loadKey()
|
|
117
|
-
} catch (err) {
|
|
118
|
-
fatal((err as Error).message)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const identityHex = privateKey.toString()
|
|
122
|
-
const identityPub = privateKey.toPublicKey().toString()
|
|
123
|
-
|
|
124
|
-
const walletHost = server.host ?? DEFAULT_WALLET_HOST
|
|
125
|
-
const walletPort = server.port ?? DEFAULT_WALLET_PORT
|
|
126
|
-
const walletStorageUrl =
|
|
127
|
-
messagebox.walletStorageUrl ?? `http://${walletHost}:${walletPort}/`
|
|
128
|
-
|
|
129
|
-
const { knexClient, knexConnection } = resolveStorage(
|
|
130
|
-
server.storage ?? { provider: 'bun-sqlite' },
|
|
131
|
-
messagebox,
|
|
132
|
-
chain,
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
chain,
|
|
137
|
-
port,
|
|
138
|
-
websockets,
|
|
139
|
-
walletStorageUrl,
|
|
140
|
-
knexClient,
|
|
141
|
-
knexConnection,
|
|
142
|
-
identityHex,
|
|
143
|
-
identityPub,
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function resolvePort(configured: number | undefined): number {
|
|
148
|
-
const fromEnv = process.env.ONESAT_MESSAGEBOX_PORT
|
|
149
|
-
if (fromEnv && fromEnv.trim() !== '') {
|
|
150
|
-
const parsed = Number(fromEnv)
|
|
151
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 65535) {
|
|
152
|
-
fatal(
|
|
153
|
-
`ONESAT_MESSAGEBOX_PORT must be a valid port number, got: ${fromEnv}`,
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
return parsed
|
|
157
|
-
}
|
|
158
|
-
return configured ?? DEFAULT_MESSAGEBOX_PORT
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function resolveStorage(
|
|
162
|
-
storage: ServerStorageConfig,
|
|
163
|
-
messagebox: ServerMessageboxConfig,
|
|
164
|
-
chain: 'main' | 'test',
|
|
165
|
-
): { knexClient: string; knexConnection: Record<string, unknown> } {
|
|
166
|
-
if (storage.provider === 'bun-sqlite') {
|
|
167
|
-
const dataDir = ensureDataDir()
|
|
168
|
-
const filename =
|
|
169
|
-
messagebox.dbPath ?? join(dataDir, `messagebox-${chain}.db`)
|
|
170
|
-
return {
|
|
171
|
-
knexClient: 'sqlite3',
|
|
172
|
-
knexConnection: { filename },
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (storage.provider === 'pg') {
|
|
177
|
-
// Prefer a dedicated messagebox database when configured (recommended:
|
|
178
|
-
// avoids `knex_migrations` table collisions with wallet-toolbox).
|
|
179
|
-
// Fall back to schema isolation against the wallet's database — note
|
|
180
|
-
// the `messagebox` schema must exist in that database for table
|
|
181
|
-
// creation to land there instead of `public`.
|
|
182
|
-
if (messagebox.dbUrl) {
|
|
183
|
-
return {
|
|
184
|
-
knexClient: 'pg',
|
|
185
|
-
knexConnection: { connectionString: messagebox.dbUrl },
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
const schema = messagebox.pgSchema ?? DEFAULT_PG_SCHEMA
|
|
189
|
-
return {
|
|
190
|
-
knexClient: 'pg',
|
|
191
|
-
knexConnection: {
|
|
192
|
-
connectionString: storage.dbUrl,
|
|
193
|
-
searchPath: [schema, 'public'],
|
|
194
|
-
},
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
fatal(
|
|
199
|
-
`server.storage.provider '${(storage as { provider: string }).provider}' is not supported by 'serve messagebox'.`,
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function applyEnv(resolved: ResolvedMessagebox): void {
|
|
204
|
-
// messagebox-server reads env at module-load time, so we set everything
|
|
205
|
-
// before the dynamic import. dotenv.config() inside messagebox-server
|
|
206
|
-
// does not overwrite values already set on process.env.
|
|
207
|
-
process.env.SERVER_PRIVATE_KEY = resolved.identityHex
|
|
208
|
-
process.env.BSV_NETWORK = resolved.chain === 'main' ? 'mainnet' : 'testnet'
|
|
209
|
-
process.env.PORT = String(resolved.port)
|
|
210
|
-
// messagebox-server's index.ts forces HTTP_PORT=3000 when NODE_ENV
|
|
211
|
-
// is anything other than 'development', ignoring our PORT entirely.
|
|
212
|
-
// Use 'development' so PORT is honored.
|
|
213
|
-
process.env.NODE_ENV = 'development'
|
|
214
|
-
process.env.ENABLE_WEBSOCKETS = resolved.websockets ? 'true' : 'false'
|
|
215
|
-
process.env.WALLET_STORAGE_URL = resolved.walletStorageUrl
|
|
216
|
-
process.env.KNEX_DB_CLIENT = resolved.knexClient
|
|
217
|
-
process.env.KNEX_DB_CONNECTION = JSON.stringify(resolved.knexConnection)
|
|
218
|
-
if (process.env.ENABLE_FIREBASE === undefined) {
|
|
219
|
-
process.env.ENABLE_FIREBASE = 'false'
|
|
220
|
-
}
|
|
221
|
-
}
|