Skip to content

Commit ecf23bb

Browse files
fix(server): bind PORT and HOST env vars (#555)
* fix(server): bind PORT and HOST env vars Declares --port and --host via Option.env() so the env vars resolve with Commander precedence (cli > env > default) instead of being overwritten by the flag defaults before createConfig() reads them. Aligns the config loader and README on the 3000 and 127.0.0.1 defaults the CLI already uses. * fix(config): harden PORT and HOST env parsing Treats empty PORT/HOST (docker-compose interpolation of unset variables) as unset and rejects non-numeric or out-of-range ports with a clear startup error instead of passing NaN to the listener. An empty HOST previously reached fastify as '' and bound all interfaces. --------- Co-authored-by: Julian Gruber <julian@juliangruber.com>
1 parent 3074070 commit ecf23bb

5 files changed

Lines changed: 47 additions & 8 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ RPC_URL=wss://... # Filecoin RPC endpoint (overrides NETWORK if spe
254254
# Optional for Pinning Server Daemon
255255
ACCESS_TOKEN=... # Bearer token required on all API requests except GET /
256256
ALLOW_NO_AUTH=true # Start without a token, serving all requests unauthenticated (not recommended)
257-
PORT=3456 # Daemon server port
258-
HOST=localhost # Daemon server host
257+
PORT=3000 # Daemon server port
258+
HOST=127.0.0.1 # Daemon server host
259259
DATABASE_PATH=./pins.db # SQLite database location
260260
CAR_STORAGE_PATH=./cars # CAR file storage directory
261261
LOG_LEVEL=info # Logging verbosity (info, debug, error)

src/commands/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { addNetworkOptions, addSigningAuthOptions, rpcUrlOption } from '../utils
44

55
export const serverCommand = new Command('server')
66
.description('Start the IPFS Pinning Service API server')
7-
.option('-p, --port <number>', 'server port', '3000')
8-
.option('--host <string>', 'server host', '127.0.0.1')
7+
.addOption(new Option('-p, --port <number>', 'server port').env('PORT').default('3000'))
8+
.addOption(new Option('--host <string>', 'server host').env('HOST').default('127.0.0.1'))
99
.option('--car-storage <path>', 'path for CAR file storage', './cars')
1010
.option('--database <path>', 'path to SQLite database', './pins.db')
1111
.addOption(

src/config.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,18 @@ export function createConfig(): Config {
6868
// the endpoint to derive the chain. Default to mainnet when neither is supplied.
6969
const chain = hasRpcUrl ? resolveChain(undefined, true) : resolveChain(process.env.NETWORK ?? 'mainnet', false)
7070

71+
// Treat an empty PORT/HOST (e.g. docker-compose interpolation of an unset
72+
// variable) the same as unset, and reject non-numeric or out-of-range ports
73+
// with a clear error instead of letting NaN reach the listener.
74+
const port = parseInt(process.env.PORT || '3000', 10)
75+
if (Number.isNaN(port) || port < 0 || port > 65535) {
76+
throw new Error(`Configuration error: PORT must be a number between 0 and 65535, got '${process.env.PORT}'`)
77+
}
78+
7179
const config: Config = {
7280
// Application-specific configuration
73-
port: parseInt(process.env.PORT ?? '3456', 10),
74-
host: process.env.HOST ?? 'localhost',
81+
port,
82+
host: process.env.HOST || '127.0.0.1',
7583
accessToken: process.env.ACCESS_TOKEN,
7684
allowNoAuth: process.env.ALLOW_NO_AUTH === 'true',
7785

src/test/unit/cli-options.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,16 @@ describe('validateAndNormalizeAutoFundOptions', () => {
192192
)
193193
})
194194
})
195+
196+
describe('server command PORT/HOST env bindings', () => {
197+
function optionFor(long: string) {
198+
return serverCommand.options.find((o) => o.long === long)
199+
}
200+
201+
it('binds --port and --host to their env vars with the CLI defaults', () => {
202+
expect(optionFor('--port')?.envVar).toBe('PORT')
203+
expect(optionFor('--port')?.defaultValue).toBe('3000')
204+
expect(optionFor('--host')?.envVar).toBe('HOST')
205+
expect(optionFor('--host')?.defaultValue).toBe('127.0.0.1')
206+
})
207+
})

src/test/unit/config.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ describe('Config', () => {
5858

5959
const expectedRpcUrl = mainnet.rpcUrls.default.webSocket?.[0] ?? mainnet.rpcUrls.default.http[0]
6060

61-
expect(config.port).toBe(3456)
62-
expect(config.host).toBe('localhost')
61+
expect(config.port).toBe(3000)
62+
expect(config.host).toBe('127.0.0.1')
6363
expect(config.rpcUrl).toBe(expectedRpcUrl)
6464
expect(config.databasePath).toBe(join(expectedDataDir, 'pins.db'))
6565
expect(config.carStoragePath).toBe(join(expectedDataDir, 'cars'))
@@ -78,6 +78,24 @@ describe('Config', () => {
7878
expect(config.logLevel).toBe('debug')
7979
})
8080

81+
it('treats empty PORT and HOST as unset', () => {
82+
process.env.PORT = ''
83+
process.env.HOST = ''
84+
85+
const config = createConfig()
86+
87+
expect(config.port).toBe(3000)
88+
expect(config.host).toBe('127.0.0.1')
89+
})
90+
91+
it('throws on a non-numeric or out-of-range PORT', () => {
92+
process.env.PORT = 'abc'
93+
expect(() => createConfig()).toThrow(/PORT must be a number/)
94+
95+
process.env.PORT = '70000'
96+
expect(() => createConfig()).toThrow(/PORT must be a number/)
97+
})
98+
8199
it('resolves chain from NETWORK so Synapse uses matching contracts', () => {
82100
process.env.NETWORK = 'mainnet'
83101
expect(createConfig().chain).toBe(mainnet)

0 commit comments

Comments
 (0)