@rip-lang/server 1.3.125 → 1.4.1
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/{docs/READ_VALIDATORS.md → API.md} +41 -119
- package/CONFIG.md +408 -0
- package/README.md +246 -1109
- package/acme/crypto.rip +0 -2
- package/browse.rip +62 -0
- package/control/cli.rip +95 -36
- package/control/lifecycle.rip +67 -1
- package/control/manager.rip +250 -0
- package/control/mdns.rip +3 -0
- package/middleware.rip +1 -1
- package/package.json +14 -11
- package/server.rip +189 -673
- package/serving/config.rip +766 -0
- package/{edge → serving}/forwarding.rip +2 -2
- package/serving/logging.rip +101 -0
- package/{edge → serving}/metrics.rip +29 -1
- package/serving/proxy.rip +99 -0
- package/{edge → serving}/queue.rip +1 -1
- package/{edge → serving}/ratelimit.rip +1 -1
- package/{edge → serving}/realtime.rip +71 -2
- package/{edge → serving}/registry.rip +1 -1
- package/{edge → serving}/router.rip +3 -3
- package/{edge → serving}/runtime.rip +18 -16
- package/{edge → serving}/security.rip +1 -1
- package/serving/static.rip +393 -0
- package/{edge → serving}/tls.rip +3 -7
- package/{edge → serving}/upstream.rip +4 -4
- package/{edge → serving}/verify.rip +16 -16
- package/streams/{tls_clienthello.rip → clienthello.rip} +1 -1
- package/streams/config.rip +8 -8
- package/streams/index.rip +5 -5
- package/streams/router.rip +2 -2
- package/tests/acme.rip +1 -1
- package/tests/config.rip +215 -0
- package/tests/control.rip +1 -1
- package/tests/{runtime_entrypoints.rip → entrypoints.rip} +11 -7
- package/tests/extracted.rip +118 -0
- package/tests/helpers.rip +4 -4
- package/tests/metrics.rip +3 -3
- package/tests/proxy.rip +9 -8
- package/tests/read.rip +1 -1
- package/tests/realtime.rip +3 -3
- package/tests/registry.rip +4 -4
- package/tests/router.rip +27 -27
- package/tests/runner.rip +70 -0
- package/tests/security.rip +4 -4
- package/tests/servers.rip +102 -136
- package/tests/static.rip +2 -2
- package/tests/streams_clienthello.rip +2 -2
- package/tests/streams_index.rip +4 -4
- package/tests/streams_pipe.rip +1 -1
- package/tests/streams_router.rip +10 -10
- package/tests/streams_runtime.rip +4 -4
- package/tests/streams_upstream.rip +1 -1
- package/tests/upstream.rip +2 -2
- package/tests/verify.rip +18 -18
- package/tests/watchers.rip +4 -4
- package/default.rip +0 -435
- package/docs/edge/CONFIG_LIFECYCLE.md +0 -111
- package/docs/edge/CONTRACTS.md +0 -137
- package/docs/edge/EDGEFILE_CONTRACT.md +0 -282
- package/docs/edge/M0B_REVIEW_NOTES.md +0 -102
- package/docs/edge/SCHEDULER.md +0 -46
- package/docs/logo.png +0 -0
- package/docs/logo.svg +0 -13
- package/docs/social.png +0 -0
- package/edge/config.rip +0 -607
- package/edge/static.rip +0 -69
- package/tests/edgefile.rip +0 -165
package/server.rip
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# ==============================================================================
|
|
2
|
-
# @rip-lang/server —
|
|
2
|
+
# @rip-lang/server — Rip Server serves content
|
|
3
3
|
# ==============================================================================
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# One Bun-native runtime to serve static sites, apps, proxied HTTP services,
|
|
6
|
+
# and TCP/TLS services. Written entirely in Rip.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
9
|
# bun server.rip <app-path> # Start server
|
|
@@ -12,405 +12,45 @@
|
|
|
12
12
|
# bun server.rip list # List registered hosts
|
|
13
13
|
# ==============================================================================
|
|
14
14
|
|
|
15
|
-
import { existsSync,
|
|
16
|
-
import {
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
16
|
+
import { dirname, join } from 'node:path'
|
|
17
17
|
import { networkInterfaces } from 'node:os'
|
|
18
|
-
import { isCurrentVersion as schedulerIsCurrentVersion, getNextAvailableSocket as schedulerGetNextAvailableSocket, releaseWorker as schedulerReleaseWorker, shouldRetryBodylessBusy } from './edge/queue.rip'
|
|
19
|
-
import { formatPort, maybeAddSecurityHeaders as edgeMaybeAddSecurityHeaders, buildStatusBody as edgeBuildStatusBody, buildRipdevUrl as edgeBuildRipdevUrl, generateRequestId } from './edge/forwarding.rip'
|
|
20
|
-
import { loadTlsMaterial as edgeLoadTlsMaterial, printCertSummary as edgePrintCertSummary } from './edge/tls.rip'
|
|
21
|
-
import { createAppRegistry, registerApp, resolveHost, getAppState, removeApp } from './edge/registry.rip'
|
|
22
|
-
import { watchUnavailableResponse, serverBusyResponse, serviceUnavailableResponse, gatewayTimeoutResponse, queueTimeoutResponse, buildUpstreamResponse, forwardOnceWithTimeout, createWsPassthrough } from './edge/forwarding.rip'
|
|
23
|
-
import { processQueuedJob, drainQueueOnce } from './edge/queue.rip'
|
|
24
|
-
import { getControlSocketPath, getPidFilePath } from './control/control.rip'
|
|
25
|
-
import { handleWorkerControl, handleWatchControl, handleRegistryControl, handleReloadControl } from './control/control.rip'
|
|
26
|
-
import { getLanIP as controlGetLanIP, startMdnsAdvertisement as controlStartMdnsAdvertisement, stopMdnsAdvertisements as controlStopMdnsAdvertisements } from './control/mdns.rip'
|
|
27
|
-
import { registerWatchGroup, handleWatchGroup, broadcastWatchChange, registerAppWatchDirs, shouldTriggerCodeReload } from './control/watchers.rip'
|
|
28
|
-
import { computeHideUrls, logStartupSummary, createCleanup, installShutdownHandlers } from './control/lifecycle.rip'
|
|
29
|
-
import { runVersionOutput, runHelpOutput, stopServer, runReloadSubcommand, resolveAppEntry, parseFlags, coerceInt } from './control/cli.rip'
|
|
30
|
-
import { runSetupMode, runWorkerMode } from './control/worker.rip'
|
|
31
|
-
import { spawnTrackedWorker, postWorkerQuit, waitForWorkerReady } from './control/workers.rip'
|
|
32
|
-
import { createChallengeStore, handleChallengeRequest } from './acme/store.rip'
|
|
33
|
-
import { createAcmeManager } from './acme/manager.rip'
|
|
34
|
-
import { loadCert as acmeLoadCert, defaultCertDir } from './acme/store.rip'
|
|
35
|
-
import { createMetrics } from './edge/metrics.rip'
|
|
36
|
-
import { setEventJsonMode, logEvent } from './control/lifecycle.rip'
|
|
37
|
-
import { createHub, generateClientId, addClient, removeClient, processResponse, handlePublish, getRealtimeStats } from './edge/realtime.rip'
|
|
38
|
-
import { findConfigFile, findEdgeFile, resolveConfigSource, applyConfig, applyEdgeConfig, formatConfigErrors } from './edge/config.rip'
|
|
39
|
-
import { createEdgeRuntime, createReloadHistoryEntry, restoreRegistrySnapshot, configNote, toUpstreamWsUrl, loadRuntimeConfig, runCheckConfig } from './edge/runtime.rip'
|
|
40
|
-
import { createRateLimiter, rateLimitResponse } from './edge/ratelimit.rip'
|
|
41
|
-
import { validateRequest } from './edge/security.rip'
|
|
42
|
-
import { createUpstreamPool, addUpstream, getUpstream, listUpstreams, selectTarget, markTargetBusy, releaseTarget, shouldRetry as shouldRetryUpstream, computeRetryDelayMs, startHealthChecks, stopHealthChecks, checkTargetHealth } from './edge/upstream.rip'
|
|
43
|
-
import { compileRouteTable, matchRoute, describeRoute } from './edge/router.rip'
|
|
44
|
-
import { buildVerificationResult, verifyRouteRuntime } from './edge/verify.rip'
|
|
45
|
-
import { serveStaticRoute, buildRedirectResponse } from './edge/static.rip'
|
|
46
|
-
import { buildTlsArray } from './edge/tls.rip'
|
|
47
|
-
import { buildStreamRuntime, startStreamListeners, stopStreamListeners, streamDiagnostics } from './streams/index.rip'
|
|
48
|
-
import { createStreamRuntime, streamUsesListenPort } from './streams/runtime.rip'
|
|
49
|
-
|
|
50
|
-
# Match capture holder for Rip's =~
|
|
51
|
-
_ = null
|
|
52
|
-
|
|
53
|
-
# ==============================================================================
|
|
54
|
-
# Constants
|
|
55
|
-
# ==============================================================================
|
|
56
|
-
|
|
57
|
-
MAX_BACKOFF_MS = 30000 # Max delay between worker restart attempts
|
|
58
|
-
MAX_RESTART_COUNT = 10 # Max consecutive worker crashes before giving up
|
|
59
|
-
STABILITY_THRESHOLD_MS = 60000 # Worker uptime before restart count resets
|
|
60
|
-
|
|
61
|
-
# ==============================================================================
|
|
62
|
-
# Utilities
|
|
63
|
-
# ==============================================================================
|
|
64
|
-
|
|
65
|
-
nowMs = -> Date.now()
|
|
66
|
-
|
|
67
|
-
_envOverride = null
|
|
68
|
-
_debugMode = false
|
|
69
|
-
|
|
70
|
-
isDev = ->
|
|
71
|
-
env = (_envOverride or process.env.NODE_ENV or '').toLowerCase()
|
|
72
|
-
env in ['development', 'dev', '']
|
|
73
|
-
|
|
74
|
-
isDebug = -> _debugMode or process.env.RIP_DEBUG?
|
|
75
|
-
|
|
76
|
-
applyFlagSideEffects = (flags) ->
|
|
77
|
-
_envOverride = flags.envOverride if flags.envOverride
|
|
78
|
-
_debugMode = flags.debug is true
|
|
79
|
-
|
|
80
|
-
formatTimestamp = ->
|
|
81
|
-
now = new Date()
|
|
82
|
-
pad = (n, w = 2) -> String(n).padStart(w, '0')
|
|
83
|
-
timestamp = "#{now.getFullYear()}-#{pad(now.getMonth() + 1)}-#{pad(now.getDate())} #{pad(now.getHours())}:#{pad(now.getMinutes())}:#{pad(now.getSeconds())}.#{String(now.getMilliseconds()).padStart(3, '0')}"
|
|
84
|
-
tzMin = now.getTimezoneOffset()
|
|
85
|
-
tzSign = if tzMin <= 0 then '+' else '-'
|
|
86
|
-
tzAbs = Math.abs(tzMin)
|
|
87
|
-
timezone = "#{tzSign}#{String(Math.floor(tzAbs / 60)).padStart(2, '0')}#{String(tzAbs % 60).padStart(2, '0')}"
|
|
88
|
-
{ timestamp, timezone }
|
|
89
|
-
|
|
90
|
-
scale = (value, unit, pad = true) ->
|
|
91
|
-
if value > 0 and Number.isFinite(value)
|
|
92
|
-
span = ['T', 'G', 'M', 'k', (if pad then ' ' else ''), 'm', 'µ', 'n', 'p']
|
|
93
|
-
base = 4
|
|
94
|
-
minSlot = 0
|
|
95
|
-
maxSlot = span.length - 1
|
|
96
|
-
slot = base
|
|
97
|
-
|
|
98
|
-
while value < 0.995 and slot <= maxSlot # use 0.05 to try to keep units
|
|
99
|
-
value *= 1000
|
|
100
|
-
slot++
|
|
101
|
-
while value >= 999.5 and slot >= minSlot
|
|
102
|
-
value /= 1000
|
|
103
|
-
slot--
|
|
104
|
-
|
|
105
|
-
if slot >= minSlot and slot <= maxSlot
|
|
106
|
-
tens = Math.round(value * 10) / 10
|
|
107
|
-
if tens >= 99.5
|
|
108
|
-
nums = Math.round(value).toString()
|
|
109
|
-
else if tens >= 10
|
|
110
|
-
nums = Math.round(value).toString()
|
|
111
|
-
else
|
|
112
|
-
nums = tens.toFixed(1)
|
|
113
|
-
nums = nums.padStart(3, ' ') if pad
|
|
114
|
-
return "#{nums}#{span[slot]}#{unit}"
|
|
115
|
-
|
|
116
|
-
return (if pad then ' 0 ' else '0') + unit if value is 0
|
|
117
|
-
'???' + (if pad then ' ' else '') + unit
|
|
118
|
-
|
|
119
|
-
logAccessJson = (app, req, res, totalSeconds, workerSeconds) ->
|
|
120
|
-
url = new URL(req.url)
|
|
121
|
-
len = res.headers.get('content-length')
|
|
122
|
-
type = (res.headers.get('content-type') or '').split(';')[0] or undefined
|
|
123
|
-
p JSON.stringify
|
|
124
|
-
t: new Date().toISOString()
|
|
125
|
-
app: app
|
|
126
|
-
method: req.method or 'GET'
|
|
127
|
-
path: url.pathname
|
|
128
|
-
status: res.status
|
|
129
|
-
totalSeconds: totalSeconds
|
|
130
|
-
workerSeconds: workerSeconds
|
|
131
|
-
type: type
|
|
132
|
-
length: if len then Number(len) else undefined
|
|
133
|
-
|
|
134
|
-
typeAbbrev =
|
|
135
|
-
html: 'html', css: 'css', javascript: 'js', json: 'json', plain: 'text'
|
|
136
|
-
png: 'png', jpeg: 'jpg', gif: 'gif', webp: 'webp', svg: 'svg'
|
|
137
|
-
'svg+xml': 'svg', 'x-icon': 'ico', 'octet-stream': 'bin', 'x-rip': 'rip'
|
|
138
|
-
|
|
139
|
-
logAccessHuman = (app, req, res, totalSeconds, workerSeconds) ->
|
|
140
|
-
{ timestamp, timezone } = formatTimestamp()
|
|
141
|
-
dur = scale(totalSeconds, 's')
|
|
142
|
-
method = req.method or 'GET'
|
|
143
|
-
url = new URL(req.url)
|
|
144
|
-
path = url.pathname
|
|
145
|
-
status = res.status
|
|
146
|
-
bytes = Number(res.headers.get('content-length') or 0)
|
|
147
|
-
size = scale(bytes, 'B')
|
|
148
|
-
contentType = (res.headers.get('content-type') or '').split(';')[0] or ''
|
|
149
|
-
sub = if contentType.includes('/') then contentType.split('/')[1] else contentType
|
|
150
|
-
type = (typeAbbrev[sub] or sub or '').padEnd(4)
|
|
151
|
-
p "#{timestamp} #{timezone} #{dur} │ #{status} #{type} #{size} │ #{method} #{path}"
|
|
152
|
-
|
|
153
|
-
INTERNAL_HEADERS = new Set(['rip-worker-busy', 'rip-worker-id', 'rip-no-log'])
|
|
154
|
-
|
|
155
|
-
stripInternalHeaders = (h) ->
|
|
156
|
-
out = new Headers()
|
|
157
|
-
for [k, v] as h.entries()
|
|
158
|
-
continue if INTERNAL_HEADERS.has(k.toLowerCase())
|
|
159
|
-
out.append(k, v)
|
|
160
|
-
out
|
|
161
|
-
|
|
162
|
-
defaultEntry = join(import.meta.dir, 'default.rip')
|
|
163
|
-
|
|
164
|
-
# Edge runtime lifecycle helpers are in edge/runtime.rip
|
|
165
|
-
|
|
166
|
-
# ==============================================================================
|
|
167
|
-
# Worker Mode
|
|
168
|
-
# ==============================================================================
|
|
169
|
-
|
|
170
|
-
# ==============================================================================
|
|
171
|
-
# Manager Class
|
|
172
|
-
# ==============================================================================
|
|
173
|
-
|
|
174
|
-
class Manager
|
|
175
|
-
constructor: (@flags) ->
|
|
176
|
-
@appWorkers = new Map()
|
|
177
|
-
@shuttingDown = false
|
|
178
|
-
@lastCheck = 0
|
|
179
|
-
@currentMtimes = new Map()
|
|
180
|
-
@rollingApps = new Set()
|
|
181
|
-
@lastRollAt = new Map()
|
|
182
|
-
@nextWorkerId = -1
|
|
183
|
-
@retiringIds = new Set()
|
|
184
|
-
@restartBudgets = new Map() # slotIndex -> { count, backoffMs }
|
|
185
|
-
@deferredDeaths = new Set() # worker IDs that died during rolling restart
|
|
186
|
-
@currentVersion = 1
|
|
187
|
-
@server = null
|
|
188
|
-
@dbUrl = null
|
|
189
|
-
@appWatchers = new Map()
|
|
190
|
-
@codeWatchers = new Map()
|
|
191
|
-
@defaultAppId = @flags.appName
|
|
192
|
-
|
|
193
|
-
getWorkers: (appId) ->
|
|
194
|
-
appId = appId or @defaultAppId
|
|
195
|
-
unless @appWorkers.has(appId)
|
|
196
|
-
@appWorkers.set(appId, [])
|
|
197
|
-
@appWorkers.get(appId)
|
|
198
|
-
|
|
199
|
-
getAppState: (appId) ->
|
|
200
|
-
@server?.appRegistry?.apps?.get(appId) or null
|
|
201
|
-
|
|
202
|
-
allAppStates: ->
|
|
203
|
-
return [] unless @server?.appRegistry?.apps
|
|
204
|
-
Array.from(@server.appRegistry.apps.values())
|
|
205
|
-
|
|
206
|
-
runSetupForApp: (app) ->
|
|
207
|
-
setupFile = join(app.config.appBaseDir or dirname(app.config.entry or @flags.appEntry), 'setup.rip')
|
|
208
|
-
return unless existsSync(setupFile)
|
|
209
|
-
|
|
210
|
-
dbUrl = process.env.DB_URL or 'http://localhost:4213'
|
|
211
|
-
dbAlreadyRunning = try
|
|
212
|
-
fetch! dbUrl + '/health'
|
|
213
|
-
true
|
|
214
|
-
catch
|
|
215
|
-
false
|
|
216
|
-
@dbUrl = dbUrl unless dbAlreadyRunning
|
|
217
|
-
|
|
218
|
-
setupEnv = Object.assign {}, process.env, app.config.env or {},
|
|
219
|
-
RIP_SETUP_MODE: '1'
|
|
220
|
-
RIP_SETUP_FILE: setupFile
|
|
221
|
-
APP_ID: String(app.appId)
|
|
222
|
-
APP_ENTRY: app.config.entry or @flags.appEntry
|
|
223
|
-
APP_BASE_DIR: app.config.appBaseDir or @flags.appBaseDir
|
|
224
|
-
proc = Bun.spawn ['rip', import.meta.path],
|
|
225
|
-
stdout: 'inherit'
|
|
226
|
-
stderr: 'inherit'
|
|
227
|
-
stdin: 'ignore'
|
|
228
|
-
cwd: process.cwd()
|
|
229
|
-
env: setupEnv
|
|
230
|
-
|
|
231
|
-
code = await proc.exited
|
|
232
|
-
if code isnt 0
|
|
233
|
-
console.error "rip-server: setup exited with code #{code} for app #{app.appId}"
|
|
234
|
-
exit 1
|
|
235
|
-
|
|
236
|
-
start: ->
|
|
237
|
-
@stop!
|
|
238
|
-
|
|
239
|
-
for app in @allAppStates()
|
|
240
|
-
@runSetupForApp!(app) if app.config.entry
|
|
241
|
-
|
|
242
|
-
for app in @allAppStates()
|
|
243
|
-
workers = @getWorkers(app.appId)
|
|
244
|
-
workers.length = 0
|
|
245
|
-
desiredWorkers = app.config.workers or @flags.workers
|
|
246
|
-
for i in [0...desiredWorkers]
|
|
247
|
-
w = @spawnWorker!(app.appId, @currentVersion)
|
|
248
|
-
workers.push(w)
|
|
249
|
-
|
|
250
|
-
if @flags.reload
|
|
251
|
-
for app in @allAppStates()
|
|
252
|
-
@currentMtimes.set(app.appId, @getEntryMtime(app.appId))
|
|
253
|
-
|
|
254
|
-
interval = setInterval =>
|
|
255
|
-
return clearInterval(interval) if @shuttingDown
|
|
256
|
-
now = Date.now()
|
|
257
|
-
return if now - @lastCheck < 100
|
|
258
|
-
@lastCheck = now
|
|
259
|
-
for app in @allAppStates()
|
|
260
|
-
appId = app.appId
|
|
261
|
-
mt = @getEntryMtime(appId)
|
|
262
|
-
previous = @currentMtimes.get(appId) or 0
|
|
263
|
-
if mt > previous
|
|
264
|
-
continue if @rollingApps.has(appId)
|
|
265
|
-
continue if now - (@lastRollAt.get(appId) or 0) < 200
|
|
266
|
-
@currentMtimes.set(appId, mt)
|
|
267
|
-
@rollingApps.add(appId)
|
|
268
|
-
@lastRollAt.set(appId, now)
|
|
269
|
-
@rollingRestart(appId).finally => @rollingApps.delete(appId)
|
|
270
|
-
, 50
|
|
271
|
-
|
|
272
|
-
# Watch files in each app directory (on by default, --static disables)
|
|
273
|
-
if @flags.watch
|
|
274
|
-
for app in @allAppStates()
|
|
275
|
-
continue unless app.config.entry and app.config.appBaseDir
|
|
276
|
-
entryFile = app.config.entry
|
|
277
|
-
entryBase = basename(entryFile)
|
|
278
|
-
try
|
|
279
|
-
watcher = watch app.config.appBaseDir, { recursive: true }, (event, filename) =>
|
|
280
|
-
return unless shouldTriggerCodeReload(filename, @flags.watch, entryBase)
|
|
281
|
-
try
|
|
282
|
-
now = new Date()
|
|
283
|
-
utimesSync(entryFile, now, now)
|
|
284
|
-
catch
|
|
285
|
-
null
|
|
286
|
-
@codeWatchers.set(app.appId, watcher)
|
|
287
|
-
catch e
|
|
288
|
-
warn "rip-server: directory watch failed for #{app.appId}: #{e.message}"
|
|
289
|
-
|
|
290
|
-
stop: ->
|
|
291
|
-
for [appId, workers] as @appWorkers
|
|
292
|
-
for w in workers
|
|
293
|
-
try w.process.kill()
|
|
294
|
-
try unlinkSync(w.socketPath)
|
|
295
|
-
@appWorkers.clear()
|
|
296
|
-
|
|
297
|
-
# Close all FS watcher handles
|
|
298
|
-
for [prefix, entry] as @appWatchers
|
|
299
|
-
clearTimeout(entry.timer) if entry.timer
|
|
300
|
-
for watcher in (entry.watchers or [])
|
|
301
|
-
try watcher.close()
|
|
302
|
-
@appWatchers.clear()
|
|
303
|
-
for [appId, watcher] as @codeWatchers
|
|
304
|
-
try watcher.close()
|
|
305
|
-
@codeWatchers.clear()
|
|
306
|
-
|
|
307
|
-
watchDirs: (prefix, dirs) ->
|
|
308
|
-
registerAppWatchDirs(@appWatchers, prefix, dirs, @server, (-> process.cwd()))
|
|
309
|
-
|
|
310
|
-
spawnWorker: (appId, version) ->
|
|
311
|
-
workerId = ++@nextWorkerId
|
|
312
|
-
app = @getAppState(appId) or { appId: appId, config: {} }
|
|
313
|
-
tracked = spawnTrackedWorker(@flags, workerId, (version or @currentVersion), nowMs, import.meta.path, appId, app.config)
|
|
314
|
-
@monitor(tracked)
|
|
315
|
-
tracked
|
|
316
|
-
|
|
317
|
-
monitor: (w) ->
|
|
318
|
-
# Watch for process exit in background (don't block)
|
|
319
|
-
w.process.exited.then =>
|
|
320
|
-
return if @shuttingDown
|
|
321
|
-
return if @retiringIds.has(w.id)
|
|
322
|
-
if @rollingApps.has(w.appId)
|
|
323
|
-
@deferredDeaths.add(w.id)
|
|
324
|
-
return
|
|
325
18
|
|
|
326
|
-
|
|
327
|
-
|
|
19
|
+
import { nowMs, isDebug, applyFlagSideEffects, logAccessJson, logAccessHuman, stripInternalHeaders } from './serving/logging.rip'
|
|
20
|
+
import { applyConfig, formatConfigErrors } from './serving/config.rip'
|
|
21
|
+
import { createAppRegistry, registerApp, resolveHost, getAppState, removeApp } from './serving/registry.rip'
|
|
22
|
+
import { compileRouteTable, matchRoute, describeRoute } from './serving/router.rip'
|
|
23
|
+
import { serveStaticRoute, buildRedirectResponse } from './serving/static.rip'
|
|
24
|
+
import { createUpstreamPool, addUpstream, getUpstream, releaseTarget, startHealthChecks, stopHealthChecks, checkTargetHealth } from './serving/upstream.rip'
|
|
25
|
+
import { formatPort, maybeAddSecurityHeaders as addSecurityHeaders, buildStatusBody, buildRipdevUrl, generateRequestId, watchUnavailableResponse, serverBusyResponse, serviceUnavailableResponse, gatewayTimeoutResponse, queueTimeoutResponse, buildUpstreamResponse, forwardOnceWithTimeout, createWsPassthrough } from './serving/forwarding.rip'
|
|
26
|
+
import { proxyRouteToUpstream as proxyRouteToUpstreamFn, upgradeProxyWebSocket as upgradeProxyWebSocketFn } from './serving/proxy.rip'
|
|
27
|
+
import { isCurrentVersion as schedulerIsCurrentVersion, getNextAvailableSocket as schedulerGetNextAvailableSocket, releaseWorker as schedulerReleaseWorker, shouldRetryBodylessBusy, processQueuedJob, drainQueueOnce } from './serving/queue.rip'
|
|
28
|
+
import { createServingRuntime, createReloadHistoryEntry, restoreRegistrySnapshot, configNote, loadRuntimeConfig, runCheckConfig } from './serving/runtime.rip'
|
|
29
|
+
import { buildVerificationResult, verifyRouteRuntime } from './serving/verify.rip'
|
|
30
|
+
import { loadTlsMaterial as loadTls, printCertSummary, buildTlsArray } from './serving/tls.rip'
|
|
31
|
+
import { createMetrics, buildDiagnosticsBody } from './serving/metrics.rip'
|
|
32
|
+
import { createRateLimiter, rateLimitResponse } from './serving/ratelimit.rip'
|
|
33
|
+
import { validateRequest } from './serving/security.rip'
|
|
34
|
+
import { createHub, generateClientId, handlePublish, getRealtimeStats, proxyRealtimeToWorker as realtimeProxyToWorker, buildWebSocketHandlers as buildWsHandlers } from './serving/realtime.rip'
|
|
328
35
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
slotIdx = workers.findIndex((x) -> x.id is w.id)
|
|
332
|
-
return if slotIdx < 0
|
|
333
|
-
|
|
334
|
-
budget = @restartBudgets.get(slotIdx) or { count: 0, backoffMs: 1000 }
|
|
335
|
-
|
|
336
|
-
# Reset budget if worker ran long enough to be considered stable
|
|
337
|
-
if nowMs() - w.startedAt > STABILITY_THRESHOLD_MS
|
|
338
|
-
budget.count = 0
|
|
339
|
-
budget.backoffMs = 1000
|
|
340
|
-
|
|
341
|
-
budget.count++
|
|
342
|
-
budget.backoffMs = Math.min(budget.backoffMs * 2, MAX_BACKOFF_MS)
|
|
343
|
-
@restartBudgets.set(slotIdx, budget)
|
|
344
|
-
|
|
345
|
-
if budget.count > MAX_RESTART_COUNT
|
|
346
|
-
logEvent('worker_abandon', { workerId: w.id, slot: slotIdx, restarts: budget.count })
|
|
347
|
-
return
|
|
348
|
-
@server?.metrics and @server.metrics.workerRestarts++
|
|
349
|
-
logEvent('worker_restart', { workerId: w.id, slot: slotIdx, attempt: budget.count, backoffMs: budget.backoffMs })
|
|
350
|
-
setTimeout =>
|
|
351
|
-
workers[slotIdx] = @spawnWorker(w.appId, @currentVersion) if slotIdx < workers.length
|
|
352
|
-
, budget.backoffMs
|
|
353
|
-
|
|
354
|
-
waitWorkerReady: (socketPath, timeoutMs = 5000) ->
|
|
355
|
-
waitForWorkerReady(socketPath, timeoutMs)
|
|
356
|
-
|
|
357
|
-
rollingRestart: (appId = null) ->
|
|
358
|
-
apps = if appId then [@getAppState(appId)].filter(Boolean) else @allAppStates()
|
|
359
|
-
nextVersion = @currentVersion + 1
|
|
360
|
-
|
|
361
|
-
for app in apps
|
|
362
|
-
workers = @getWorkers(app.appId)
|
|
363
|
-
olds = [...workers]
|
|
364
|
-
pairs = []
|
|
365
|
-
|
|
366
|
-
for oldWorker in olds
|
|
367
|
-
replacement = @spawnWorker!(app.appId, nextVersion)
|
|
368
|
-
workers.push(replacement)
|
|
369
|
-
pairs.push({ old: oldWorker, replacement })
|
|
370
|
-
|
|
371
|
-
readyResults = Promise.all!(pairs.map((p) => @waitWorkerReady(p.replacement.socketPath, 3000)))
|
|
372
|
-
allReady = readyResults.every((ready) -> ready)
|
|
373
|
-
unless allReady
|
|
374
|
-
console.error "[manager] Rolling restart aborted: not all new workers ready for app #{app.appId}"
|
|
375
|
-
for pair, i in pairs
|
|
376
|
-
unless readyResults[i]
|
|
377
|
-
try pair.replacement.process.kill()
|
|
378
|
-
idx = workers.indexOf(pair.replacement)
|
|
379
|
-
workers.splice(idx, 1) if idx >= 0
|
|
380
|
-
if @deferredDeaths.size > 0
|
|
381
|
-
for deadId as @deferredDeaths
|
|
382
|
-
idx = workers.findIndex((x) -> x.id is deadId)
|
|
383
|
-
workers[idx] = @spawnWorker(app.appId, @currentVersion) if idx >= 0
|
|
384
|
-
@deferredDeaths.clear()
|
|
385
|
-
return
|
|
386
|
-
|
|
387
|
-
@currentVersion = nextVersion
|
|
388
|
-
|
|
389
|
-
for { old } in pairs
|
|
390
|
-
@retiringIds.add(old.id)
|
|
391
|
-
try old.process.kill()
|
|
36
|
+
import { buildStreamRuntime, startStreamListeners, stopStreamListeners, streamDiagnostics } from './streams/index.rip'
|
|
37
|
+
import { createStreamRuntime, streamUsesListenPort } from './streams/runtime.rip'
|
|
392
38
|
|
|
393
|
-
|
|
39
|
+
import { getControlSocketPath, getPidFilePath, handleWorkerControl, handleWatchControl, handleRegistryControl, handleReloadControl } from './control/control.rip'
|
|
40
|
+
import { getLanIP as controlGetLanIP, startMdnsAdvertisement as controlStartMdnsAdvertisement, stopMdnsAdvertisements as controlStopMdnsAdvertisements } from './control/mdns.rip'
|
|
41
|
+
import { registerWatchGroup, handleWatchGroup, broadcastWatchChange } from './control/watchers.rip'
|
|
42
|
+
import { computeHideUrls, logStartupSummary, createCleanup, installShutdownHandlers, setEventJsonMode, logEvent } from './control/lifecycle.rip'
|
|
43
|
+
import { runVersionOutput, runHelpOutput, stopServer, reloadConfig, listApps, showInfo, parseFlags } from './control/cli.rip'
|
|
44
|
+
import { runSetupMode, runWorkerMode } from './control/worker.rip'
|
|
45
|
+
import { Manager } from './control/manager.rip'
|
|
394
46
|
|
|
395
|
-
|
|
396
|
-
|
|
47
|
+
import { createChallengeStore, handleChallengeRequest, loadCert as acmeLoadCert, defaultCertDir } from './acme/store.rip'
|
|
48
|
+
import { createAcmeManager } from './acme/manager.rip'
|
|
397
49
|
|
|
398
|
-
|
|
399
|
-
filtered = workers.filter((w) -> not retiring.has(w.id))
|
|
400
|
-
workers.length = 0
|
|
401
|
-
workers.push(...filtered)
|
|
402
|
-
@retiringIds.delete(id) for id as retiring
|
|
50
|
+
_ = null
|
|
403
51
|
|
|
404
|
-
|
|
405
|
-
for deadId as @deferredDeaths
|
|
406
|
-
idx = workers.findIndex((x) -> x.id is deadId)
|
|
407
|
-
workers[idx] = @spawnWorker(app.appId, @currentVersion) if idx >= 0
|
|
408
|
-
@deferredDeaths.clear()
|
|
52
|
+
defaultEntry = join(import.meta.dir, 'browse.rip')
|
|
409
53
|
|
|
410
|
-
getEntryMtime: (appId = null) ->
|
|
411
|
-
app = @getAppState(appId or @defaultAppId)
|
|
412
|
-
entry = app?.config?.entry or @flags.appEntry
|
|
413
|
-
try statSync(entry).mtimeMs catch then 0
|
|
414
54
|
|
|
415
55
|
# ==============================================================================
|
|
416
56
|
# Server Class
|
|
@@ -441,12 +81,12 @@ class Server
|
|
|
441
81
|
@watchGroups = new Map()
|
|
442
82
|
@manager = null
|
|
443
83
|
@configuredAppIds = new Set()
|
|
444
|
-
@
|
|
445
|
-
@
|
|
84
|
+
@servingRuntime = createServingRuntime()
|
|
85
|
+
@retiredServingRuntimes = []
|
|
446
86
|
@streamRuntime = createStreamRuntime()
|
|
447
87
|
@retiredStreamRuntimes = []
|
|
448
88
|
@reloadAttemptSeq = 0
|
|
449
|
-
@configInfo = @
|
|
89
|
+
@configInfo = @servingRuntime.configInfo
|
|
450
90
|
@configInfo.reloadHistory = []
|
|
451
91
|
@configInfo.lastReload = null
|
|
452
92
|
try
|
|
@@ -482,21 +122,21 @@ class Server
|
|
|
482
122
|
removeApp(@appRegistry, appId)
|
|
483
123
|
@configuredAppIds.clear()
|
|
484
124
|
|
|
485
|
-
|
|
125
|
+
retainRuntime: (runtime) ->
|
|
486
126
|
runtime.inflight++
|
|
487
127
|
runtime
|
|
488
128
|
|
|
489
|
-
|
|
129
|
+
releaseRuntime: (runtime) ->
|
|
490
130
|
runtime.inflight = Math.max(0, runtime.inflight - 1)
|
|
491
|
-
@
|
|
131
|
+
@cleanupRetiredServingRuntimes()
|
|
492
132
|
|
|
493
|
-
|
|
133
|
+
retainRuntimeWs: (runtime) ->
|
|
494
134
|
runtime.wsConnections++
|
|
495
135
|
runtime
|
|
496
136
|
|
|
497
|
-
|
|
137
|
+
releaseRuntimeWs: (runtime) ->
|
|
498
138
|
runtime.wsConnections = Math.max(0, runtime.wsConnections - 1)
|
|
499
|
-
@
|
|
139
|
+
@cleanupRetiredServingRuntimes()
|
|
500
140
|
|
|
501
141
|
retainStreamRuntime: (runtime) ->
|
|
502
142
|
runtime.inflight++
|
|
@@ -506,14 +146,14 @@ class Server
|
|
|
506
146
|
runtime.inflight = Math.max(0, runtime.inflight - 1)
|
|
507
147
|
@cleanupRetiredStreamRuntimes()
|
|
508
148
|
|
|
509
|
-
|
|
149
|
+
cleanupRetiredServingRuntimes: ->
|
|
510
150
|
keep = []
|
|
511
|
-
for runtime in @
|
|
151
|
+
for runtime in @retiredServingRuntimes
|
|
512
152
|
if runtime.inflight > 0 or runtime.wsConnections > 0
|
|
513
153
|
keep.push(runtime)
|
|
514
154
|
else
|
|
515
155
|
stopHealthChecks(runtime.upstreamPool)
|
|
516
|
-
@
|
|
156
|
+
@retiredServingRuntimes = keep
|
|
517
157
|
|
|
518
158
|
cleanupRetiredStreamRuntimes: ->
|
|
519
159
|
keep = []
|
|
@@ -531,18 +171,18 @@ class Server
|
|
|
531
171
|
lastReload: entry
|
|
532
172
|
reloadHistory: history
|
|
533
173
|
)
|
|
534
|
-
@
|
|
174
|
+
@servingRuntime.configInfo = @configInfo if @servingRuntime?.configInfo
|
|
535
175
|
|
|
536
176
|
nextReloadAttemptId: ->
|
|
537
177
|
@reloadAttemptSeq++
|
|
538
178
|
"reload-#{@reloadAttemptSeq}"
|
|
539
179
|
|
|
540
|
-
|
|
541
|
-
return buildVerificationResult(true) unless runtime?.configInfo?.kind
|
|
180
|
+
verifyRuntime: (runtime, loaded) ->
|
|
181
|
+
return buildVerificationResult(true) unless runtime?.configInfo?.kind in ['serve', 'edge']
|
|
542
182
|
verifyRouteRuntime(runtime, @appRegistry, @defaultAppId, getUpstream, checkTargetHealth, getAppState, runtime.verifyPolicy or runtime.configInfo.verifyPolicy or {})
|
|
543
183
|
|
|
544
|
-
|
|
545
|
-
return
|
|
184
|
+
buildRuntime: (loaded) ->
|
|
185
|
+
return createServingRuntime() unless loaded?.source?.kind in ['serve', 'edge']
|
|
546
186
|
upstreamPool = createUpstreamPool()
|
|
547
187
|
for upstreamId, upstreamConfig of (loaded.normalized.upstreams or {})
|
|
548
188
|
addUpstream(upstreamPool, upstreamId, upstreamConfig)
|
|
@@ -559,17 +199,19 @@ class Server
|
|
|
559
199
|
lastErrorCode: null
|
|
560
200
|
lastErrorDetails: null
|
|
561
201
|
rolledBackFrom: null
|
|
562
|
-
verifyPolicy: loaded.normalized.
|
|
563
|
-
|
|
202
|
+
verifyPolicy: loaded.normalized.server?.verify or null
|
|
203
|
+
acmeDomains: loaded.normalized.server?.acmeDomains or []
|
|
204
|
+
certDir: loaded.normalized.server?.certDir or null
|
|
205
|
+
certs: loaded.normalized.resolvedCerts or null
|
|
564
206
|
activeRouteDescriptions: (routeTable.routes or []).map(describeRoute).filter(Boolean)
|
|
565
207
|
lastReload: @configInfo?.lastReload or null
|
|
566
208
|
reloadHistory: @configInfo?.reloadHistory or []
|
|
567
|
-
runtime =
|
|
209
|
+
runtime = createServingRuntime(configInfo, upstreamPool, routeTable)
|
|
568
210
|
startHealthChecks(runtime.upstreamPool)
|
|
569
211
|
runtime
|
|
570
212
|
|
|
571
213
|
buildStreamRuntimeForConfig: (loaded) ->
|
|
572
|
-
return createStreamRuntime() unless loaded?.source?.kind
|
|
214
|
+
return createStreamRuntime() unless loaded?.source?.kind in ['serve', 'edge']
|
|
573
215
|
buildStreamRuntime(loaded.normalized)
|
|
574
216
|
|
|
575
217
|
startAppServerOnPort: (p, opts = {}) ->
|
|
@@ -650,15 +292,15 @@ class Server
|
|
|
650
292
|
startStreamListeners(@streamRuntime, @buildStreamListenerOptions())
|
|
651
293
|
@streamListenersDeferred = false
|
|
652
294
|
|
|
653
|
-
|
|
295
|
+
activateRuntime: (runtime) ->
|
|
654
296
|
return unless runtime
|
|
655
|
-
oldRuntime = @
|
|
656
|
-
@
|
|
297
|
+
oldRuntime = @servingRuntime
|
|
298
|
+
@servingRuntime = runtime
|
|
657
299
|
@configInfo = runtime.configInfo
|
|
658
300
|
if oldRuntime and oldRuntime isnt runtime
|
|
659
301
|
oldRuntime.retiredAt = new Date().toISOString()
|
|
660
|
-
@
|
|
661
|
-
@
|
|
302
|
+
@retiredServingRuntimes.push(oldRuntime)
|
|
303
|
+
@cleanupRetiredServingRuntimes()
|
|
662
304
|
oldRuntime
|
|
663
305
|
|
|
664
306
|
activateStreamRuntime: (runtime, options = {}) ->
|
|
@@ -687,15 +329,15 @@ class Server
|
|
|
687
329
|
@cleanupRetiredStreamRuntimes()
|
|
688
330
|
oldRuntime
|
|
689
331
|
|
|
690
|
-
|
|
332
|
+
rollbackRuntime: (oldRuntime, failedRuntime, snapshot, verification) ->
|
|
691
333
|
restoreRegistrySnapshot(@appRegistry, snapshot)
|
|
692
334
|
@configuredAppIds = new Set(snapshot.configured)
|
|
693
335
|
|
|
694
336
|
stopHealthChecks(failedRuntime.upstreamPool)
|
|
695
|
-
@
|
|
337
|
+
@retiredServingRuntimes = @retiredServingRuntimes.filter((runtime) -> runtime isnt oldRuntime and runtime isnt failedRuntime)
|
|
696
338
|
if oldRuntime
|
|
697
339
|
oldRuntime.retiredAt = null
|
|
698
|
-
@
|
|
340
|
+
@servingRuntime = oldRuntime
|
|
699
341
|
@configInfo = Object.assign({}, oldRuntime.configInfo,
|
|
700
342
|
lastResult: 'rolled_back'
|
|
701
343
|
lastError: verification.message
|
|
@@ -711,8 +353,8 @@ class Server
|
|
|
711
353
|
lastErrorCode: verification.code
|
|
712
354
|
lastErrorDetails: verification.details or null
|
|
713
355
|
)
|
|
714
|
-
@
|
|
715
|
-
@
|
|
356
|
+
@retiredServingRuntimes.push(failedRuntime)
|
|
357
|
+
@cleanupRetiredServingRuntimes()
|
|
716
358
|
|
|
717
359
|
rollbackStreamRuntime: (oldRuntime, failedRuntime) ->
|
|
718
360
|
stopStreamListeners(failedRuntime, true)
|
|
@@ -740,10 +382,8 @@ class Server
|
|
|
740
382
|
details = if applied then null else (@configInfo?.lastErrorDetails or null)
|
|
741
383
|
entry = createReloadHistoryEntry(attemptId, source, oldVersion, newVersion, result, reason, code, details)
|
|
742
384
|
@appendReloadHistory(entry)
|
|
743
|
-
if loaded and not flags.quiet
|
|
744
|
-
|
|
745
|
-
p "rip-server: loaded #{label} with #{loaded.summary.counts.apps} app(s), #{loaded.summary.counts.upstreams} upstream(s), #{loaded.summary.counts.routes} route(s)"
|
|
746
|
-
p "rip-server: note: #{@configInfo.note}" if @configInfo.note
|
|
385
|
+
if loaded and not flags.quiet and source isnt 'startup'
|
|
386
|
+
p "rip-server: loaded serve.rip with #{loaded.summary.counts.apps} app(s), #{loaded.summary.counts.proxies} proxy(s), #{loaded.summary.counts.routes} route(s)"
|
|
747
387
|
logEvent('config_loaded',
|
|
748
388
|
id: attemptId
|
|
749
389
|
source: source
|
|
@@ -755,10 +395,10 @@ class Server
|
|
|
755
395
|
reason: reason
|
|
756
396
|
code: code
|
|
757
397
|
apps: @configInfo.counts.apps
|
|
758
|
-
|
|
398
|
+
proxies: @configInfo.counts.proxies
|
|
759
399
|
routes: @configInfo.counts.routes
|
|
760
|
-
|
|
761
|
-
) if
|
|
400
|
+
hosts: @configInfo.counts.hosts
|
|
401
|
+
) if source isnt 'startup'
|
|
762
402
|
{
|
|
763
403
|
ok: applied
|
|
764
404
|
id: attemptId
|
|
@@ -781,7 +421,7 @@ class Server
|
|
|
781
421
|
entry = createReloadHistoryEntry(attemptId, source, oldVersion, @configInfo?.version or null, 'rejected', e.message or String(e), 'reload_exception', null)
|
|
782
422
|
@appendReloadHistory(entry)
|
|
783
423
|
if e.validationErrors
|
|
784
|
-
label =
|
|
424
|
+
label = 'serve.rip'
|
|
785
425
|
console.error formatConfigErrors(label, e.validationErrors)
|
|
786
426
|
else
|
|
787
427
|
console.error "rip-server: failed to load active config: #{e.message or e}"
|
|
@@ -816,31 +456,28 @@ class Server
|
|
|
816
456
|
hostIndex: new Map(@appRegistry.hostIndex)
|
|
817
457
|
wildcardIndex: new Map(@appRegistry.wildcardIndex)
|
|
818
458
|
configured: new Set(@configuredAppIds)
|
|
819
|
-
oldRuntime = @
|
|
459
|
+
oldRuntime = @servingRuntime
|
|
820
460
|
oldStreamRuntime = @streamRuntime
|
|
821
|
-
stagedRuntime = @
|
|
461
|
+
stagedRuntime = @buildRuntime(loaded)
|
|
822
462
|
stagedStreamRuntime = @buildStreamRuntimeForConfig(loaded)
|
|
823
463
|
|
|
824
464
|
@clearConfiguredApps()
|
|
825
465
|
unless loaded
|
|
826
|
-
@
|
|
466
|
+
@activateRuntime(stagedRuntime)
|
|
827
467
|
@activateStreamRuntime(stagedStreamRuntime, deferListeners: source is 'startup')
|
|
828
468
|
return
|
|
829
469
|
|
|
830
470
|
baseDir = dirname(loaded.source.path)
|
|
831
471
|
try
|
|
832
|
-
registered =
|
|
833
|
-
applyEdgeConfig(loaded.normalized, @appRegistry, registerApp, baseDir)
|
|
834
|
-
else
|
|
835
|
-
applyConfig(loaded.normalized, @appRegistry, registerApp, baseDir)
|
|
472
|
+
registered = applyConfig(loaded.normalized, @appRegistry, registerApp, baseDir)
|
|
836
473
|
for app in registered
|
|
837
474
|
@configuredAppIds.add(app.id) unless app.id is @defaultAppId
|
|
838
|
-
oldRuntime = @
|
|
475
|
+
oldRuntime = @activateRuntime(stagedRuntime)
|
|
839
476
|
oldStreamRuntime = @activateStreamRuntime(stagedStreamRuntime, deferListeners: source is 'startup')
|
|
840
477
|
if verifyAfterActivate
|
|
841
|
-
result = @
|
|
478
|
+
result = @verifyRuntime!(stagedRuntime, loaded)
|
|
842
479
|
unless result.ok
|
|
843
|
-
@
|
|
480
|
+
@rollbackRuntime(oldRuntime, stagedRuntime, snapshot, result)
|
|
844
481
|
@rollbackStreamRuntime(oldStreamRuntime, stagedStreamRuntime)
|
|
845
482
|
logEvent('config_rollback',
|
|
846
483
|
source: source
|
|
@@ -858,7 +495,7 @@ class Server
|
|
|
858
495
|
rolledBackFrom: null
|
|
859
496
|
loadedAt: new Date().toISOString()
|
|
860
497
|
)
|
|
861
|
-
@
|
|
498
|
+
@servingRuntime.configInfo = @configInfo
|
|
862
499
|
logEvent('config_activated',
|
|
863
500
|
source: source
|
|
864
501
|
oldVersion: oldRuntime?.configInfo?.version
|
|
@@ -881,14 +518,15 @@ class Server
|
|
|
881
518
|
@flags.httpPort = @server.port
|
|
882
519
|
else
|
|
883
520
|
@tlsMaterial = @loadTlsMaterial!
|
|
884
|
-
if @
|
|
885
|
-
@tlsMaterial = buildTlsArray(@tlsMaterial, @
|
|
521
|
+
if @servingRuntime?.configInfo?.certs and Object.keys(@servingRuntime.configInfo.certs).length > 0
|
|
522
|
+
@tlsMaterial = buildTlsArray(@tlsMaterial, @servingRuntime.configInfo.certs)
|
|
886
523
|
publicHttpsPort = @flags.httpsPort or 443
|
|
887
524
|
@applyHttpsMode(@shouldUseInternalHttps(@streamRuntime, publicHttpsPort), publicHttpsPort)
|
|
888
525
|
httpsPort = @flags.httpsPort
|
|
889
526
|
@streamListenersDeferred = true if @internalHttpsServer and @streamRuntime.listeners.size is 0
|
|
890
527
|
|
|
891
|
-
|
|
528
|
+
configHasAcme = @servingRuntime?.configInfo?.acmeDomains?.length > 0
|
|
529
|
+
if @flags.redirectHttp or @flags.acme or @flags.acmeStaging or configHasAcme
|
|
892
530
|
challengeStore = @challengeStore
|
|
893
531
|
try
|
|
894
532
|
@server = Bun.serve
|
|
@@ -921,8 +559,8 @@ class Server
|
|
|
921
559
|
, 1000
|
|
922
560
|
|
|
923
561
|
stop: ->
|
|
924
|
-
stopHealthChecks(@
|
|
925
|
-
stopHealthChecks(runtime.upstreamPool) for runtime in @
|
|
562
|
+
stopHealthChecks(@servingRuntime.upstreamPool)
|
|
563
|
+
stopHealthChecks(runtime.upstreamPool) for runtime in @retiredServingRuntimes
|
|
926
564
|
stopStreamListeners(@streamRuntime, true)
|
|
927
565
|
stopStreamListeners(runtime, true) for runtime in @retiredStreamRuntimes
|
|
928
566
|
clearInterval(@queueSweepTimer) if @queueSweepTimer
|
|
@@ -942,90 +580,10 @@ class Server
|
|
|
942
580
|
controlStopMdnsAdvertisements(@mdnsProcesses)
|
|
943
581
|
|
|
944
582
|
proxyRouteToUpstream: (req, route, requestId, clientIp, runtime) ->
|
|
945
|
-
|
|
946
|
-
return serviceUnavailableResponse() unless upstream
|
|
947
|
-
|
|
948
|
-
attempt = 1
|
|
949
|
-
start = performance.now()
|
|
950
|
-
|
|
951
|
-
while attempt <= upstream.retry.attempts
|
|
952
|
-
target = selectTarget(upstream, runtime.upstreamPool.nowFn)
|
|
953
|
-
return serviceUnavailableResponse() unless target
|
|
954
|
-
|
|
955
|
-
markTargetBusy(target)
|
|
956
|
-
workerSeconds = 0
|
|
957
|
-
res = null
|
|
958
|
-
success = false
|
|
959
|
-
|
|
960
|
-
try
|
|
961
|
-
t0 = performance.now()
|
|
962
|
-
timeoutMs = route.timeouts?.readMs or upstream.timeouts?.readMs or @flags.readTimeoutMs
|
|
963
|
-
res = proxyToUpstream!(req, target.url,
|
|
964
|
-
timeoutMs: timeoutMs
|
|
965
|
-
clientIp: clientIp
|
|
966
|
-
)
|
|
967
|
-
workerSeconds = (performance.now() - t0) / 1000
|
|
968
|
-
success = res.status < 500
|
|
969
|
-
catch err
|
|
970
|
-
console.error "[server] proxyRouteToUpstream error:", err.message or err if isDebug()
|
|
971
|
-
res = serviceUnavailableResponse()
|
|
972
|
-
finally
|
|
973
|
-
releaseTarget(target, workerSeconds * 1000, success, runtime.upstreamPool)
|
|
974
|
-
|
|
975
|
-
if res and shouldRetryUpstream(upstream.retry, req.method, res.status, false) and attempt < upstream.retry.attempts
|
|
976
|
-
delayMs = computeRetryDelayMs(upstream.retry, attempt, runtime.upstreamPool.randomFn)
|
|
977
|
-
await new Promise (r) -> setTimeout(r, delayMs)
|
|
978
|
-
attempt++
|
|
979
|
-
continue
|
|
980
|
-
|
|
981
|
-
totalSeconds = (performance.now() - start) / 1000
|
|
982
|
-
@metrics.forwarded++
|
|
983
|
-
@metrics.recordLatency(totalSeconds)
|
|
984
|
-
@metrics.recordStatus(res.status)
|
|
985
|
-
response = buildUpstreamResponse(
|
|
986
|
-
res,
|
|
987
|
-
req,
|
|
988
|
-
totalSeconds,
|
|
989
|
-
workerSeconds,
|
|
990
|
-
@maybeAddSecurityHeaders.bind(@),
|
|
991
|
-
@logAccess.bind(@),
|
|
992
|
-
stripInternalHeaders
|
|
993
|
-
)
|
|
994
|
-
response.headers.set('X-Request-Id', requestId) if requestId
|
|
995
|
-
response.headers.set('X-Rip-Route', route.id) if route.id
|
|
996
|
-
return response
|
|
997
|
-
|
|
998
|
-
serviceUnavailableResponse()
|
|
583
|
+
proxyRouteToUpstreamFn!(req, route, requestId, clientIp, runtime, @metrics, @flags, @maybeAddSecurityHeaders.bind(@), @logAccess.bind(@))
|
|
999
584
|
|
|
1000
585
|
upgradeProxyWebSocket: (req, bunServer, route, requestId, runtime) ->
|
|
1001
|
-
|
|
1002
|
-
return new Response('Service unavailable', { status: 503 }) unless upstream
|
|
1003
|
-
target = selectTarget(upstream, runtime.upstreamPool.nowFn)
|
|
1004
|
-
return new Response('Service unavailable', { status: 503 }) unless target
|
|
1005
|
-
|
|
1006
|
-
inUrl = new URL(req.url)
|
|
1007
|
-
protocols = (req.headers.get('sec-websocket-protocol') or '')
|
|
1008
|
-
.split(',')
|
|
1009
|
-
.map((p) -> p.trim())
|
|
1010
|
-
.filter(Boolean)
|
|
1011
|
-
|
|
1012
|
-
markTargetBusy(target)
|
|
1013
|
-
data =
|
|
1014
|
-
kind: 'edge-proxy'
|
|
1015
|
-
runtime: runtime
|
|
1016
|
-
requestId: requestId
|
|
1017
|
-
routeId: route.id
|
|
1018
|
-
upstreamTarget: target
|
|
1019
|
-
upstreamUrl: toUpstreamWsUrl(target.url, inUrl.pathname, inUrl.search)
|
|
1020
|
-
protocols: protocols
|
|
1021
|
-
passthrough: null
|
|
1022
|
-
released: false
|
|
1023
|
-
|
|
1024
|
-
if bunServer.upgrade(req, { data })
|
|
1025
|
-
return
|
|
1026
|
-
|
|
1027
|
-
releaseTarget(target, 0, false, runtime.upstreamPool)
|
|
1028
|
-
new Response('WebSocket upgrade failed', { status: 400 })
|
|
586
|
+
upgradeProxyWebSocketFn(req, bunServer, route, requestId, runtime)
|
|
1029
587
|
|
|
1030
588
|
fetch: (req, bunServer) ->
|
|
1031
589
|
url = new URL(req.url)
|
|
@@ -1085,17 +643,17 @@ class Server
|
|
|
1085
643
|
# Assign request ID for tracing
|
|
1086
644
|
requestId = req.headers.get('x-request-id') or generateRequestId()
|
|
1087
645
|
|
|
1088
|
-
#
|
|
1089
|
-
runtime = @
|
|
646
|
+
# Route table — proxy routes can handle hosts outside the managed app registry
|
|
647
|
+
runtime = @servingRuntime
|
|
1090
648
|
matchedRoute = matchRoute(runtime.routeTable, host, url.pathname, req.method)
|
|
1091
|
-
if matchedRoute?.
|
|
649
|
+
if matchedRoute?.proxy and matchedRoute.websocket and bunServer
|
|
1092
650
|
return @upgradeProxyWebSocket(req, bunServer, matchedRoute, requestId, runtime)
|
|
1093
|
-
if matchedRoute?.
|
|
1094
|
-
@
|
|
651
|
+
if matchedRoute?.proxy
|
|
652
|
+
@retainRuntime(runtime)
|
|
1095
653
|
try
|
|
1096
654
|
return @proxyRouteToUpstream!(req, matchedRoute, requestId, clientIp, runtime)
|
|
1097
655
|
finally
|
|
1098
|
-
@
|
|
656
|
+
@releaseRuntime(runtime)
|
|
1099
657
|
if matchedRoute?.static
|
|
1100
658
|
return serveStaticRoute(req, url, matchedRoute)
|
|
1101
659
|
if matchedRoute?.redirect
|
|
@@ -1107,7 +665,7 @@ class Server
|
|
|
1107
665
|
app = getAppState(@appRegistry, appId)
|
|
1108
666
|
return new Response('Host not found', { status: 404 }) unless app
|
|
1109
667
|
|
|
1110
|
-
@
|
|
668
|
+
@retainRuntime(runtime) if matchedRoute?.app
|
|
1111
669
|
|
|
1112
670
|
# Fast path: try available worker
|
|
1113
671
|
if app.inflightTotal < Math.max(1, app.sockets.length)
|
|
@@ -1118,13 +676,13 @@ class Server
|
|
|
1118
676
|
try
|
|
1119
677
|
return @forwardToWorker!(req, sock, app, requestId)
|
|
1120
678
|
finally
|
|
1121
|
-
@
|
|
679
|
+
@releaseRuntime(runtime) if matchedRoute?.app
|
|
1122
680
|
app.inflightTotal--
|
|
1123
681
|
setImmediate => @drainQueue(app)
|
|
1124
682
|
|
|
1125
683
|
if app.queue.length >= app.maxQueue
|
|
1126
684
|
@metrics.queueShed++
|
|
1127
|
-
@
|
|
685
|
+
@releaseRuntime(runtime) if matchedRoute?.app
|
|
1128
686
|
return serverBusyResponse()
|
|
1129
687
|
|
|
1130
688
|
@metrics.queued++
|
|
@@ -1133,66 +691,56 @@ class Server
|
|
|
1133
691
|
originalResolve = resolve
|
|
1134
692
|
originalReject = reject
|
|
1135
693
|
resolve = (value) =>
|
|
1136
|
-
@
|
|
694
|
+
@releaseRuntime(runtime)
|
|
1137
695
|
originalResolve(value)
|
|
1138
696
|
reject = (err) =>
|
|
1139
|
-
@
|
|
697
|
+
@releaseRuntime(runtime)
|
|
1140
698
|
originalReject(err)
|
|
1141
699
|
app.queue.push({ req, resolve, reject, enqueuedAt: nowMs() })
|
|
1142
700
|
|
|
1143
701
|
status: ->
|
|
1144
702
|
app = getAppState(@appRegistry, @defaultAppId)
|
|
1145
703
|
workerCount = if app then app.sockets.length else 0
|
|
1146
|
-
body =
|
|
704
|
+
body = buildStatusBody(@startedAt, workerCount, @appRegistry.hostIndex, @serverVersion, @flags.appName, @flags.httpPort, @flags.httpsPort, nowMs)
|
|
1147
705
|
headers = new Headers({ 'content-type': 'application/json', 'cache-control': 'no-cache' })
|
|
1148
706
|
@maybeAddSecurityHeaders(headers)
|
|
1149
707
|
new Response(body, { headers })
|
|
1150
708
|
|
|
1151
709
|
diagnostics: ->
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
responses: snap.counters.responses
|
|
1162
|
-
latency: snap.latency
|
|
1163
|
-
queue: snap.counters.queue
|
|
1164
|
-
workers: snap.counters.workers
|
|
1165
|
-
acme: snap.counters.acme
|
|
1166
|
-
websocket: snap.counters.websocket
|
|
1167
|
-
gauges: snap.gauges
|
|
1168
|
-
realtime: getRealtimeStats(@realtimeHub)
|
|
710
|
+
body = buildDiagnosticsBody
|
|
711
|
+
metrics: @metrics
|
|
712
|
+
startedAt: @startedAt
|
|
713
|
+
appRegistry: @appRegistry
|
|
714
|
+
servingRuntime: @servingRuntime
|
|
715
|
+
serverVersion: @serverVersion
|
|
716
|
+
ripVersion: @ripVersion
|
|
717
|
+
configInfo: @configInfo
|
|
718
|
+
realtimeStats: getRealtimeStats(@realtimeHub)
|
|
1169
719
|
hosts: Array.from(@appRegistry.hostIndex.keys())
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
retiredAt: runtime.retiredAt
|
|
1195
|
-
)
|
|
720
|
+
streamDiagnostics: streamDiagnostics(@streamRuntime)
|
|
721
|
+
multiplexer:
|
|
722
|
+
enabled: Boolean(@internalHttpsServer)
|
|
723
|
+
publicHttpsPort: @multiplexerPorts.publicHttpsPort
|
|
724
|
+
internalHttpsPort: @multiplexerPorts.internalHttpsPort
|
|
725
|
+
activeRuntime:
|
|
726
|
+
id: @servingRuntime.id
|
|
727
|
+
inflight: @servingRuntime.inflight
|
|
728
|
+
wsConnections: @servingRuntime.wsConnections
|
|
729
|
+
retiredRuntimes: @retiredServingRuntimes.map((runtime) ->
|
|
730
|
+
id: runtime.id
|
|
731
|
+
inflight: runtime.inflight
|
|
732
|
+
wsConnections: runtime.wsConnections
|
|
733
|
+
retiredAt: runtime.retiredAt
|
|
734
|
+
)
|
|
735
|
+
activeStreamRuntime:
|
|
736
|
+
id: @streamRuntime.id
|
|
737
|
+
inflight: @streamRuntime.inflight
|
|
738
|
+
listeners: Array.from(@streamRuntime.listeners.keys())
|
|
739
|
+
retiredStreamRuntimes: @retiredStreamRuntimes.map((runtime) ->
|
|
740
|
+
id: runtime.id
|
|
741
|
+
inflight: runtime.inflight
|
|
742
|
+
listeners: Array.from(runtime.listeners.keys())
|
|
743
|
+
retiredAt: runtime.retiredAt
|
|
1196
744
|
)
|
|
1197
745
|
headers = new Headers({ 'content-type': 'application/json', 'cache-control': 'no-cache' })
|
|
1198
746
|
@maybeAddSecurityHeaders(headers)
|
|
@@ -1326,9 +874,8 @@ class Server
|
|
|
1326
874
|
|
|
1327
875
|
port = @flags.httpsPort or @flags.httpPort or 80
|
|
1328
876
|
protocol = if @flags.httpsPort then 'https' else 'http'
|
|
1329
|
-
url =
|
|
877
|
+
url = buildRipdevUrl(@flags.appName, protocol, port, formatPort)
|
|
1330
878
|
@urls.push(url)
|
|
1331
|
-
p "rip-server: #{url}" unless @flags.quiet or @flags.hideUrls
|
|
1332
879
|
|
|
1333
880
|
controlFetch: (req) ->
|
|
1334
881
|
url = new URL(req.url)
|
|
@@ -1366,13 +913,14 @@ class Server
|
|
|
1366
913
|
new Response('not-found', { status: 404 })
|
|
1367
914
|
|
|
1368
915
|
maybeAddSecurityHeaders: (headers) ->
|
|
1369
|
-
|
|
916
|
+
addSecurityHeaders(@httpsActive, @flags.hsts, headers)
|
|
1370
917
|
|
|
1371
918
|
loadTlsMaterial: ->
|
|
1372
|
-
|
|
919
|
+
certDir = @servingRuntime?.configInfo?.certDir or defaultCertDir()
|
|
920
|
+
loadTls(@flags, import.meta.dir, (domain) -> acmeLoadCert(certDir, domain))
|
|
1373
921
|
|
|
1374
922
|
printCertSummary: (certPem) ->
|
|
1375
|
-
|
|
923
|
+
printCertSummary(certPem)
|
|
1376
924
|
|
|
1377
925
|
getLanIP: ->
|
|
1378
926
|
controlGetLanIP(networkInterfaces)
|
|
@@ -1392,77 +940,23 @@ class Server
|
|
|
1392
940
|
proxyRealtimeToWorker: (headers, frameType, body) ->
|
|
1393
941
|
app = getAppState(@appRegistry, @defaultAppId)
|
|
1394
942
|
return null unless app
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
proxyHeaders.set('Content-Type', 'text/plain')
|
|
1400
|
-
proxyHeaders.delete('Upgrade')
|
|
1401
|
-
proxyHeaders.delete('Connection')
|
|
1402
|
-
proxyHeaders.delete('Sec-WebSocket-Key')
|
|
1403
|
-
proxyHeaders.delete('Sec-WebSocket-Version')
|
|
1404
|
-
proxyHeaders.delete('Sec-WebSocket-Extensions')
|
|
1405
|
-
try
|
|
1406
|
-
res = fetch! "http://localhost/v1/realtime",
|
|
1407
|
-
method: 'POST'
|
|
1408
|
-
headers: proxyHeaders
|
|
1409
|
-
body: body or ''
|
|
1410
|
-
unix: sock.socket
|
|
1411
|
-
decompress: false
|
|
1412
|
-
res.text!
|
|
1413
|
-
catch e
|
|
1414
|
-
console.error "realtime: worker proxy failed:", e.message
|
|
1415
|
-
null
|
|
1416
|
-
finally
|
|
1417
|
-
@releaseWorker(sock, app)
|
|
943
|
+
realtimeProxyToWorker(headers, frameType, body,
|
|
944
|
+
(=> @getNextAvailableSocket(app)),
|
|
945
|
+
((sock) => @releaseWorker(sock, app))
|
|
946
|
+
)
|
|
1418
947
|
|
|
1419
948
|
buildWebSocketHandlers: ->
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
return
|
|
1432
|
-
|
|
1433
|
-
{ clientId, headers } = ws.data
|
|
1434
|
-
addClient(hub, clientId, ws)
|
|
1435
|
-
server.metrics.wsConnections++
|
|
1436
|
-
logEvent 'ws_open', { clientId }
|
|
1437
|
-
response = server.proxyRealtimeToWorker!(headers, 'open', '')
|
|
1438
|
-
processResponse(hub, response, clientId) if response
|
|
1439
|
-
|
|
1440
|
-
message: (ws, message) ->
|
|
1441
|
-
if ws.data?.kind is 'edge-proxy'
|
|
1442
|
-
ws.data.passthrough?.sendToUpstream(message)
|
|
1443
|
-
return
|
|
1444
|
-
|
|
1445
|
-
{ clientId, headers } = ws.data
|
|
1446
|
-
server.metrics.wsMessages++
|
|
1447
|
-
isBinary = typeof message isnt 'string'
|
|
1448
|
-
msg = if isBinary then Buffer.from(message) else message
|
|
1449
|
-
frameType = if isBinary then 'binary' else 'text'
|
|
1450
|
-
response = server.proxyRealtimeToWorker!(headers, frameType, msg)
|
|
1451
|
-
processResponse(hub, response, clientId) if response
|
|
1452
|
-
|
|
1453
|
-
close: (ws) ->
|
|
1454
|
-
if ws.data?.kind is 'edge-proxy'
|
|
1455
|
-
ws.data.passthrough?.close()
|
|
1456
|
-
unless ws.data.released
|
|
1457
|
-
releaseTarget(ws.data.upstreamTarget, 0, true, ws.data.runtime.upstreamPool)
|
|
1458
|
-
server.releaseEdgeRuntimeWs(ws.data.runtime)
|
|
1459
|
-
ws.data.released = true
|
|
1460
|
-
return
|
|
1461
|
-
|
|
1462
|
-
{ clientId, headers } = ws.data
|
|
1463
|
-
logEvent 'ws_close', { clientId }
|
|
1464
|
-
removeClient(hub, clientId)
|
|
1465
|
-
server.proxyRealtimeToWorker!(headers, 'close', '')
|
|
949
|
+
buildWsHandlers(@realtimeHub,
|
|
950
|
+
metrics: @metrics
|
|
951
|
+
retainRuntimeWs: @retainRuntimeWs.bind(@)
|
|
952
|
+
releaseRuntimeWs: @releaseRuntimeWs.bind(@)
|
|
953
|
+
createWsPassthrough: (ws, url, protocols) -> createWsPassthrough(ws, url, protocols)
|
|
954
|
+
releaseProxyTarget: (data) =>
|
|
955
|
+
releaseTarget(data.upstreamTarget, 0, true, data.runtime.upstreamPool)
|
|
956
|
+
@releaseRuntimeWs(data.runtime)
|
|
957
|
+
logEvent: logEvent
|
|
958
|
+
proxyRealtimeToWorker: @proxyRealtimeToWorker.bind(@)
|
|
959
|
+
)
|
|
1466
960
|
|
|
1467
961
|
|
|
1468
962
|
# ==============================================================================
|
|
@@ -1481,8 +975,14 @@ main = ->
|
|
|
1481
975
|
if flags.checkConfig
|
|
1482
976
|
runCheckConfig!(flags)
|
|
1483
977
|
return
|
|
978
|
+
if flags.showInfo
|
|
979
|
+
showInfo!(fetch, exit)
|
|
980
|
+
return
|
|
1484
981
|
if flags.reloadConfig
|
|
1485
|
-
|
|
982
|
+
reloadConfig!(flags.socketPrefix, getControlSocketPath, fetch, exit)
|
|
983
|
+
return
|
|
984
|
+
if flags.listApps
|
|
985
|
+
listApps!(flags.socketPrefix, getControlSocketPath, fetch, exit)
|
|
1486
986
|
return
|
|
1487
987
|
if flags.stopServer
|
|
1488
988
|
stopServer(flags.socketPrefix, getPidFilePath, existsSync, readFileSync, process.kill.bind(process), Bun.spawnSync, import.meta.path)
|
|
@@ -1492,7 +992,7 @@ main = ->
|
|
|
1492
992
|
writeFileSync(pidFile, String(process.pid))
|
|
1493
993
|
|
|
1494
994
|
svr = new Server(flags)
|
|
1495
|
-
mgr = new Manager(flags)
|
|
995
|
+
mgr = new Manager(flags, import.meta.path)
|
|
1496
996
|
svr.manager = mgr
|
|
1497
997
|
mgr.server = svr
|
|
1498
998
|
|
|
@@ -1507,26 +1007,42 @@ main = ->
|
|
|
1507
1007
|
|
|
1508
1008
|
svr.start!
|
|
1509
1009
|
|
|
1510
|
-
# ACME auto-TLS: obtain
|
|
1511
|
-
|
|
1010
|
+
# ACME auto-TLS: obtain certs for all domains, start renewal loop
|
|
1011
|
+
unless flags.noAcme
|
|
1012
|
+
acmeDomains = []
|
|
1013
|
+
acmeDomains.push(flags.acmeDomain) if flags.acmeDomain
|
|
1014
|
+
configAcme = svr.servingRuntime?.configInfo?.acmeDomains or []
|
|
1015
|
+
for d in configAcme
|
|
1016
|
+
acmeDomains.push(d) unless acmeDomains.includes(d)
|
|
1017
|
+
|
|
1018
|
+
if not flags.noAcme and acmeDomains?.length > 0
|
|
1512
1019
|
acmeMgr = createAcmeManager
|
|
1513
|
-
certDir: defaultCertDir()
|
|
1020
|
+
certDir: svr.servingRuntime?.configInfo?.certDir or defaultCertDir()
|
|
1514
1021
|
staging: flags.acmeStaging
|
|
1515
1022
|
challengeStore: svr.challengeStore
|
|
1516
1023
|
onCertRenewed: (domain, result) ->
|
|
1517
|
-
p "rip-acme: cert renewed for #{domain}
|
|
1024
|
+
p "rip-acme: cert renewed for #{domain}"
|
|
1025
|
+
try
|
|
1026
|
+
svr.tlsMaterial = svr.loadTlsMaterial!
|
|
1027
|
+
if svr.servingRuntime?.configInfo?.certs and Object.keys(svr.servingRuntime.configInfo.certs).length > 0
|
|
1028
|
+
svr.tlsMaterial = buildTlsArray(svr.tlsMaterial, svr.servingRuntime.configInfo.certs)
|
|
1029
|
+
svr.httpsServer?.reload({ tls: svr.tlsMaterial }) if svr.httpsServer
|
|
1030
|
+
svr.internalHttpsServer?.reload({ tls: svr.tlsMaterial }) if svr.internalHttpsServer
|
|
1031
|
+
p "rip-acme: TLS reloaded with renewed cert for #{domain}"
|
|
1032
|
+
catch e
|
|
1033
|
+
console.error "rip-acme: TLS reload failed after renewal: #{e.message}"
|
|
1518
1034
|
svr.acmeManager = acmeMgr
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1035
|
+
for domain in acmeDomains
|
|
1036
|
+
try acmeMgr.obtainCert!(domain)
|
|
1037
|
+
catch e
|
|
1038
|
+
console.error "rip-acme: cert obtain failed for #{domain}: #{e.message}"
|
|
1039
|
+
console.error "rip-acme: will retry during renewal loop"
|
|
1040
|
+
acmeMgr.startRenewalLoop(acmeDomains)
|
|
1524
1041
|
|
|
1525
1042
|
process.env.RIP_URLS = svr.urls.join(',') # exact URLs the Server just built
|
|
1526
1043
|
mgr.start!
|
|
1527
1044
|
|
|
1528
|
-
logStartupSummary(svr, flags,
|
|
1529
|
-
logEvent('server_start', { app: flags.appName, workers: flags.workers })
|
|
1045
|
+
logStartupSummary(svr, flags, buildRipdevUrl, formatPort)
|
|
1530
1046
|
|
|
1531
1047
|
# ==============================================================================
|
|
1532
1048
|
# Entry Point
|