@rip-lang/server 1.3.110 → 1.3.112
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/acme/client.rip +138 -0
- package/acme/crypto.rip +130 -0
- package/acme/manager.rip +107 -0
- package/acme/store.rip +67 -0
- package/control/cli.rip +123 -0
- package/control/control.rip +75 -0
- package/control/lifecycle.rip +71 -0
- package/control/mdns.rip +53 -0
- package/control/watchers.rip +68 -0
- package/control/worker.rip +136 -0
- package/control/workers.rip +53 -0
- package/edge/config.rip +75 -0
- package/edge/forwarding.rip +149 -0
- package/edge/metrics.rip +85 -0
- package/edge/queue.rip +53 -0
- package/edge/ratelimit.rip +63 -0
- package/edge/realtime.rip +200 -0
- package/edge/registry.rip +44 -0
- package/edge/security.rip +38 -0
- package/edge/tls.rip +49 -0
- package/package.json +6 -3
package/edge/queue.rip
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# edge/queue.rip — scheduling, queue drain, and job processing
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
|
|
5
|
+
# --- Scheduler ---
|
|
6
|
+
|
|
7
|
+
export isCurrentVersion = (newestVersion, worker) ->
|
|
8
|
+
newestVersion is null or worker.version is null or worker.version >= newestVersion
|
|
9
|
+
|
|
10
|
+
export getNextAvailableSocket = (availableWorkers, newestVersion) ->
|
|
11
|
+
while availableWorkers.length > 0
|
|
12
|
+
worker = availableWorkers.pop()
|
|
13
|
+
return worker if worker.inflight is 0 and isCurrentVersion(newestVersion, worker)
|
|
14
|
+
null
|
|
15
|
+
|
|
16
|
+
export releaseWorker = (sockets, availableWorkers, newestVersion, worker) ->
|
|
17
|
+
return unless worker
|
|
18
|
+
return unless sockets.some((s) -> s.socket is worker.socket)
|
|
19
|
+
worker.inflight = 0
|
|
20
|
+
if isCurrentVersion(newestVersion, worker)
|
|
21
|
+
availableWorkers.push(worker)
|
|
22
|
+
|
|
23
|
+
export shouldRetryBodylessBusy = (req, res) ->
|
|
24
|
+
req.method in ['GET', 'HEAD', 'OPTIONS', 'DELETE'] and
|
|
25
|
+
res.status is 503 and
|
|
26
|
+
res.headers.get('Rip-Worker-Busy') is '1'
|
|
27
|
+
|
|
28
|
+
# --- Queue ---
|
|
29
|
+
|
|
30
|
+
resolveJobError = (e) ->
|
|
31
|
+
if e instanceof Response then e else new Response('Internal error', { status: 500 })
|
|
32
|
+
|
|
33
|
+
export processQueuedJob = (job, worker, forwardToWorker, onFinally) ->
|
|
34
|
+
forwardToWorker(job.req, worker)
|
|
35
|
+
.then((r) -> job.resolve(r))
|
|
36
|
+
.catch((e) -> job.resolve(resolveJobError(e)))
|
|
37
|
+
.finally(-> onFinally())
|
|
38
|
+
|
|
39
|
+
export drainQueueOnce = (inflightTotal, socketsLength, availableWorkersLength, shiftJob, isJobTimedOut, queueTimeoutResponse, getNextAvailableSocket, processJob) ->
|
|
40
|
+
while inflightTotal < Math.max(1, socketsLength) and availableWorkersLength() > 0
|
|
41
|
+
job = shiftJob()
|
|
42
|
+
break unless job
|
|
43
|
+
if isJobTimedOut(job)
|
|
44
|
+
job.resolve(queueTimeoutResponse())
|
|
45
|
+
continue
|
|
46
|
+
inflightTotal++
|
|
47
|
+
worker = getNextAvailableSocket()
|
|
48
|
+
unless worker
|
|
49
|
+
inflightTotal--
|
|
50
|
+
job.resolve(new Response('Service unavailable', { status: 503, headers: { 'Retry-After': '1' } }))
|
|
51
|
+
break
|
|
52
|
+
processJob(job, worker)
|
|
53
|
+
inflightTotal
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# edge/ratelimit.rip — per-IP sliding window rate limiter
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
|
|
5
|
+
DEFAULT_WINDOW_MS = 60000 # 1 minute
|
|
6
|
+
DEFAULT_MAX_REQUESTS = 1000 # per window
|
|
7
|
+
CLEANUP_INTERVAL_MS = 30000 # prune stale entries
|
|
8
|
+
MAX_BUCKETS = 100000 # hard cap on tracked IPs to prevent memory exhaustion
|
|
9
|
+
|
|
10
|
+
export createRateLimiter = (maxRequests, windowMs) ->
|
|
11
|
+
# Explicit check: 0 means disabled, null/undefined gets default
|
|
12
|
+
maxRequests = if typeof maxRequests is 'number' then maxRequests else DEFAULT_MAX_REQUESTS
|
|
13
|
+
windowMs = if typeof windowMs is 'number' and windowMs > 0 then windowMs else DEFAULT_WINDOW_MS
|
|
14
|
+
buckets = new Map()
|
|
15
|
+
|
|
16
|
+
cleanupTimer = if maxRequests > 0
|
|
17
|
+
setInterval ->
|
|
18
|
+
now = Date.now()
|
|
19
|
+
for [ip, timestamps] as buckets
|
|
20
|
+
fresh = timestamps.filter((t) -> now - t < windowMs)
|
|
21
|
+
if fresh.length is 0
|
|
22
|
+
buckets.delete(ip)
|
|
23
|
+
else
|
|
24
|
+
buckets.set(ip, fresh)
|
|
25
|
+
, CLEANUP_INTERVAL_MS
|
|
26
|
+
else
|
|
27
|
+
null
|
|
28
|
+
|
|
29
|
+
limiter =
|
|
30
|
+
maxRequests: maxRequests
|
|
31
|
+
windowMs: windowMs
|
|
32
|
+
|
|
33
|
+
check: (ip) ->
|
|
34
|
+
return { allowed: true, remaining: 0, retryAfter: 0 } if maxRequests <= 0
|
|
35
|
+
now = Date.now()
|
|
36
|
+
timestamps = buckets.get(ip) or []
|
|
37
|
+
timestamps = timestamps.filter((t) -> now - t < windowMs)
|
|
38
|
+
if timestamps.length >= maxRequests
|
|
39
|
+
buckets.set(ip, timestamps)
|
|
40
|
+
retryAfter = Math.ceil((timestamps[0] + windowMs - now) / 1000)
|
|
41
|
+
return { allowed: false, remaining: 0, retryAfter }
|
|
42
|
+
timestamps.push(now)
|
|
43
|
+
buckets.set(ip, timestamps)
|
|
44
|
+
# Evict oldest-inserted bucket if over cap (FIFO, not LRU — simple and bounded)
|
|
45
|
+
if buckets.size > MAX_BUCKETS
|
|
46
|
+
oldest = buckets.keys().next().value
|
|
47
|
+
buckets.delete(oldest)
|
|
48
|
+
{ allowed: true, remaining: maxRequests - timestamps.length, retryAfter: 0 }
|
|
49
|
+
|
|
50
|
+
reset: (ip) ->
|
|
51
|
+
buckets.delete(ip)
|
|
52
|
+
|
|
53
|
+
stop: ->
|
|
54
|
+
clearInterval(cleanupTimer) if cleanupTimer
|
|
55
|
+
|
|
56
|
+
limiter
|
|
57
|
+
|
|
58
|
+
export rateLimitResponse = (retryAfter) ->
|
|
59
|
+
new Response 'Rate limit exceeded',
|
|
60
|
+
status: 429
|
|
61
|
+
headers:
|
|
62
|
+
'Retry-After': String(retryAfter)
|
|
63
|
+
'Content-Type': 'text/plain'
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# edge/realtime.rip — Bam-style WebSocket realtime hub
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
#
|
|
5
|
+
# Manages WebSocket connections, group membership, and message routing.
|
|
6
|
+
# Backend stays HTTP-only — the hub proxies WS events as HTTP POSTs and
|
|
7
|
+
# processes JSON responses for membership updates and message delivery.
|
|
8
|
+
#
|
|
9
|
+
# Protocol keys:
|
|
10
|
+
# @ target groups (who receives)
|
|
11
|
+
# + subscribe to groups
|
|
12
|
+
# - unsubscribe from groups
|
|
13
|
+
# > senders (exclude from delivery)
|
|
14
|
+
# * close with reason
|
|
15
|
+
# Other keys are event payloads delivered to targets
|
|
16
|
+
|
|
17
|
+
import { randomBytes } from 'node:crypto'
|
|
18
|
+
|
|
19
|
+
# --- Hub lifecycle ---
|
|
20
|
+
|
|
21
|
+
export createHub = ->
|
|
22
|
+
sockets: new Map() # clientId -> ws
|
|
23
|
+
members: new Map() # groupId -> Set(clientId) (bidirectional)
|
|
24
|
+
backendUrl: null # set during wiring
|
|
25
|
+
deliveries: 0
|
|
26
|
+
messages: 0
|
|
27
|
+
connections: 0
|
|
28
|
+
|
|
29
|
+
export generateClientId = ->
|
|
30
|
+
randomBytes(16).toString('hex')
|
|
31
|
+
|
|
32
|
+
# --- Client management ---
|
|
33
|
+
|
|
34
|
+
export addClient = (hub, clientId, ws) ->
|
|
35
|
+
hub.sockets.set(clientId, ws)
|
|
36
|
+
hub.members.set(clientId, new Set())
|
|
37
|
+
hub.connections++
|
|
38
|
+
clientId
|
|
39
|
+
|
|
40
|
+
export removeClient = (hub, clientId) ->
|
|
41
|
+
hub.sockets.delete(clientId)
|
|
42
|
+
groups = hub.members.get(clientId)
|
|
43
|
+
hub.members.delete(clientId)
|
|
44
|
+
if groups
|
|
45
|
+
for group as groups
|
|
46
|
+
set = hub.members.get(group)
|
|
47
|
+
if set
|
|
48
|
+
set.delete(clientId)
|
|
49
|
+
hub.members.delete(group) if set.size is 0
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# --- Membership ---
|
|
53
|
+
|
|
54
|
+
applySubscribe = (hub, groups, channels) ->
|
|
55
|
+
for group as groups
|
|
56
|
+
set = hub.members.get(group)
|
|
57
|
+
if set
|
|
58
|
+
for ch as channels
|
|
59
|
+
set.add(ch)
|
|
60
|
+
else
|
|
61
|
+
hub.members.set(group, new Set(channels))
|
|
62
|
+
for ch as channels
|
|
63
|
+
set = hub.members.get(ch)
|
|
64
|
+
if set
|
|
65
|
+
for group as groups
|
|
66
|
+
set.add(group)
|
|
67
|
+
else
|
|
68
|
+
hub.members.set(ch, new Set(groups))
|
|
69
|
+
|
|
70
|
+
applyUnsubscribe = (hub, groups, channels) ->
|
|
71
|
+
for group as groups
|
|
72
|
+
set = hub.members.get(group)
|
|
73
|
+
if set
|
|
74
|
+
for ch as channels
|
|
75
|
+
set.delete(ch)
|
|
76
|
+
hub.members.delete(group) if set.size is 0
|
|
77
|
+
for ch as channels
|
|
78
|
+
set = hub.members.get(ch)
|
|
79
|
+
if set
|
|
80
|
+
for group as groups
|
|
81
|
+
set.delete(group)
|
|
82
|
+
hub.members.delete(ch) if set.size is 0
|
|
83
|
+
|
|
84
|
+
# --- Message routing ---
|
|
85
|
+
|
|
86
|
+
resolveTargets = (hub, groups) ->
|
|
87
|
+
clients = new Set()
|
|
88
|
+
for group as groups
|
|
89
|
+
set = hub.members.get(group)
|
|
90
|
+
if set
|
|
91
|
+
if String(group).startsWith('/')
|
|
92
|
+
for id as set
|
|
93
|
+
clients.add(id)
|
|
94
|
+
else
|
|
95
|
+
clients.add(group)
|
|
96
|
+
clients
|
|
97
|
+
|
|
98
|
+
deliverToClients = (hub, clients, senders, payload) ->
|
|
99
|
+
for id as clients
|
|
100
|
+
continue if senders and senders.has(String(id))
|
|
101
|
+
ws = hub.sockets.get(id)
|
|
102
|
+
if ws
|
|
103
|
+
try
|
|
104
|
+
ws.send(payload)
|
|
105
|
+
hub.deliveries++
|
|
106
|
+
hub.messages++
|
|
107
|
+
|
|
108
|
+
# --- Process backend response ---
|
|
109
|
+
|
|
110
|
+
export processResponse = (hub, body, clientId) ->
|
|
111
|
+
try
|
|
112
|
+
json = JSON.parse(body)
|
|
113
|
+
return unless json and typeof json is 'object' # reject primitives (strings, numbers, booleans)
|
|
114
|
+
list = if Array.isArray(json) then json else [json]
|
|
115
|
+
|
|
116
|
+
for item in list
|
|
117
|
+
groups = null
|
|
118
|
+
concat = null
|
|
119
|
+
remove = null
|
|
120
|
+
senders = null
|
|
121
|
+
bundle = {}
|
|
122
|
+
|
|
123
|
+
for key, val of item
|
|
124
|
+
switch key
|
|
125
|
+
when '@' then groups = new Set(val.map(String))
|
|
126
|
+
when '+' then concat = new Set(val.map(String))
|
|
127
|
+
when '-' then remove = new Set(val.map(String))
|
|
128
|
+
when '>' then senders = new Set(val.map(String))
|
|
129
|
+
when '*' then null # close reason — handled by caller
|
|
130
|
+
else bundle[key] = val
|
|
131
|
+
|
|
132
|
+
groups = new Set([clientId]) if not groups and clientId
|
|
133
|
+
|
|
134
|
+
if groups
|
|
135
|
+
applySubscribe(hub, groups, concat) if concat
|
|
136
|
+
applyUnsubscribe(hub, groups, remove) if remove
|
|
137
|
+
|
|
138
|
+
if groups and Object.keys(bundle).length > 0
|
|
139
|
+
clients = resolveTargets(hub, groups)
|
|
140
|
+
deliverToClients(hub, clients, senders, JSON.stringify(bundle))
|
|
141
|
+
catch e
|
|
142
|
+
console.error "realtime: invalid response:", e.message
|
|
143
|
+
|
|
144
|
+
# --- Proxy to backend ---
|
|
145
|
+
|
|
146
|
+
export proxyToBackend = (hub, headers, frameType, body) ->
|
|
147
|
+
return unless hub.backendUrl
|
|
148
|
+
proxyHeaders = new Headers(headers)
|
|
149
|
+
proxyHeaders.set('Sec-WebSocket-Frame', frameType)
|
|
150
|
+
proxyHeaders.delete('Upgrade')
|
|
151
|
+
proxyHeaders.delete('Connection')
|
|
152
|
+
proxyHeaders.delete('Sec-WebSocket-Key')
|
|
153
|
+
proxyHeaders.delete('Sec-WebSocket-Version')
|
|
154
|
+
proxyHeaders.delete('Sec-WebSocket-Extensions')
|
|
155
|
+
|
|
156
|
+
try
|
|
157
|
+
res = fetch! hub.backendUrl,
|
|
158
|
+
method: 'POST'
|
|
159
|
+
headers: proxyHeaders
|
|
160
|
+
body: body or ''
|
|
161
|
+
res.text!
|
|
162
|
+
catch e
|
|
163
|
+
console.error "realtime: proxy failed:", e.message
|
|
164
|
+
null
|
|
165
|
+
|
|
166
|
+
# --- Binary delivery (for CRDT and raw data) ---
|
|
167
|
+
|
|
168
|
+
export broadcastBinary = (hub, groupIds, data, excludeClientId) ->
|
|
169
|
+
clients = new Set()
|
|
170
|
+
for group in groupIds
|
|
171
|
+
set = hub.members.get(String(group))
|
|
172
|
+
if set
|
|
173
|
+
if String(group).startsWith('/')
|
|
174
|
+
for id as set
|
|
175
|
+
clients.add(id)
|
|
176
|
+
else
|
|
177
|
+
clients.add(String(group))
|
|
178
|
+
for id as clients
|
|
179
|
+
continue if excludeClientId and id is excludeClientId
|
|
180
|
+
ws = hub.sockets.get(id)
|
|
181
|
+
if ws
|
|
182
|
+
try
|
|
183
|
+
ws.send(data)
|
|
184
|
+
hub.deliveries++
|
|
185
|
+
hub.messages++
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
# --- Publish (external) ---
|
|
189
|
+
|
|
190
|
+
export handlePublish = (hub, body) ->
|
|
191
|
+
processResponse(hub, body, null)
|
|
192
|
+
|
|
193
|
+
# --- Stats ---
|
|
194
|
+
|
|
195
|
+
export getRealtimeStats = (hub) ->
|
|
196
|
+
clients: hub.sockets.size
|
|
197
|
+
groups: hub.members.size - hub.sockets.size # groups minus client entries
|
|
198
|
+
deliveries: hub.deliveries
|
|
199
|
+
messages: hub.messages
|
|
200
|
+
connections: hub.connections
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# edge/registry.rip — per-app state and multi-app registry
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
|
|
5
|
+
createAppState = (appId, config) ->
|
|
6
|
+
appId: appId
|
|
7
|
+
config: config
|
|
8
|
+
sockets: []
|
|
9
|
+
availableWorkers: []
|
|
10
|
+
queue: []
|
|
11
|
+
inflightTotal: 0
|
|
12
|
+
newestVersion: null
|
|
13
|
+
maxQueue: config.maxQueue or 512
|
|
14
|
+
queueTimeoutMs: config.queueTimeoutMs or 30000
|
|
15
|
+
readTimeoutMs: config.readTimeoutMs or 30000
|
|
16
|
+
hosts: config.hosts or []
|
|
17
|
+
|
|
18
|
+
export createAppRegistry = ->
|
|
19
|
+
apps: new Map()
|
|
20
|
+
hostIndex: new Map()
|
|
21
|
+
|
|
22
|
+
export registerApp = (registry, appId, config) ->
|
|
23
|
+
state = createAppState(appId, config)
|
|
24
|
+
registry.apps.set(appId, state)
|
|
25
|
+
for host in (config.hosts or [])
|
|
26
|
+
registry.hostIndex.set(host.toLowerCase(), appId)
|
|
27
|
+
state
|
|
28
|
+
|
|
29
|
+
export removeApp = (registry, appId) ->
|
|
30
|
+
state = registry.apps.get(appId)
|
|
31
|
+
return unless state
|
|
32
|
+
for host in state.hosts
|
|
33
|
+
h = host.toLowerCase()
|
|
34
|
+
registry.hostIndex.delete(h) if registry.hostIndex.get(h) is appId
|
|
35
|
+
registry.apps.delete(appId)
|
|
36
|
+
|
|
37
|
+
export resolveHost = (registry, hostname) ->
|
|
38
|
+
registry.hostIndex.get(hostname.toLowerCase()) ?? null
|
|
39
|
+
|
|
40
|
+
export getAppState = (registry, appId) ->
|
|
41
|
+
registry.apps.get(appId) ?? null
|
|
42
|
+
|
|
43
|
+
export getAllApps = (registry) ->
|
|
44
|
+
Array.from(registry.apps.values())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# edge/security.rip — request smuggling and validation defenses
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
|
|
5
|
+
MAX_URL_LENGTH = 8192
|
|
6
|
+
|
|
7
|
+
export validateRequest = (req) ->
|
|
8
|
+
url = new URL(req.url)
|
|
9
|
+
|
|
10
|
+
# Reject oversized URLs
|
|
11
|
+
if url.href.length > MAX_URL_LENGTH
|
|
12
|
+
return { valid: false, status: 400, message: 'URL too long' }
|
|
13
|
+
|
|
14
|
+
# Reject conflicting Content-Length and Transfer-Encoding
|
|
15
|
+
hasContentLength = req.headers.has('content-length')
|
|
16
|
+
hasTransferEncoding = req.headers.has('transfer-encoding')
|
|
17
|
+
if hasContentLength and hasTransferEncoding
|
|
18
|
+
return { valid: false, status: 400, message: 'Conflicting Content-Length and Transfer-Encoding' }
|
|
19
|
+
|
|
20
|
+
# Reject multiple Host headers (check for comma which indicates multiple values)
|
|
21
|
+
hostHeader = req.headers.get('host')
|
|
22
|
+
if hostHeader and hostHeader.includes(',')
|
|
23
|
+
return { valid: false, status: 400, message: 'Multiple Host headers' }
|
|
24
|
+
|
|
25
|
+
# Reject null bytes in URL
|
|
26
|
+
if url.pathname.includes('\x00') or url.search.includes('\x00')
|
|
27
|
+
return { valid: false, status: 400, message: 'Null byte in URL' }
|
|
28
|
+
|
|
29
|
+
# Reject path traversal — three decodes beyond URL parser
|
|
30
|
+
# Catches single (%2e), double (%252e), and triple (%25252e) encoding
|
|
31
|
+
pathname = url.pathname
|
|
32
|
+
d1 = try decodeURIComponent(pathname) catch then pathname
|
|
33
|
+
d2 = try decodeURIComponent(d1) catch then d1
|
|
34
|
+
d3 = try decodeURIComponent(d2) catch then d2
|
|
35
|
+
if pathname.includes('..') or d1.includes('..') or d2.includes('..') or d3.includes('..')
|
|
36
|
+
return { valid: false, status: 400, message: 'Path traversal rejected' }
|
|
37
|
+
|
|
38
|
+
{ valid: true }
|
package/edge/tls.rip
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# edge/tls.rip — TLS material loading helpers
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { X509Certificate } from 'node:crypto'
|
|
8
|
+
|
|
9
|
+
export printCertSummary = (certPem) ->
|
|
10
|
+
try
|
|
11
|
+
x = new X509Certificate(certPem)
|
|
12
|
+
subject = x.subject.split(/,/)[0]?.trim() or x.subject
|
|
13
|
+
issuer = x.issuer.split(/,/)[0]?.trim() or x.issuer
|
|
14
|
+
exp = new Date(x.validTo)
|
|
15
|
+
p "rip-server: tls cert #{subject} issued by #{issuer} expires #{exp.toISOString()}"
|
|
16
|
+
|
|
17
|
+
export loadTlsMaterial = (flags, serverDir, acmeLoadCertFn) ->
|
|
18
|
+
# Explicit cert/key paths
|
|
19
|
+
if flags.certPath and flags.keyPath
|
|
20
|
+
try
|
|
21
|
+
cert = readFileSync(flags.certPath, 'utf8')
|
|
22
|
+
key = readFileSync(flags.keyPath, 'utf8')
|
|
23
|
+
printCertSummary(cert)
|
|
24
|
+
return { cert, key }
|
|
25
|
+
catch
|
|
26
|
+
console.error 'Failed to read TLS cert/key from provided paths. Use http or fix paths.'
|
|
27
|
+
exit 2
|
|
28
|
+
|
|
29
|
+
# ACME-managed cert (if domain is configured)
|
|
30
|
+
if acmeLoadCertFn and (flags.acme or flags.acmeStaging) and flags.acmeDomain
|
|
31
|
+
acmeCert = acmeLoadCertFn(flags.acmeDomain)
|
|
32
|
+
if acmeCert
|
|
33
|
+
p "rip-server: using ACME cert for #{flags.acmeDomain}"
|
|
34
|
+
printCertSummary(acmeCert.cert)
|
|
35
|
+
return { cert: acmeCert.cert, key: acmeCert.key }
|
|
36
|
+
|
|
37
|
+
# Shipped wildcard cert for *.ripdev.io (GlobalSign, valid for all subdomains)
|
|
38
|
+
certsDir = join(serverDir, 'certs')
|
|
39
|
+
certPath = join(certsDir, 'ripdev.io.crt')
|
|
40
|
+
keyPath = join(certsDir, 'ripdev.io.key')
|
|
41
|
+
try
|
|
42
|
+
cert = readFileSync(certPath, 'utf8')
|
|
43
|
+
key = readFileSync(keyPath, 'utf8')
|
|
44
|
+
printCertSummary(cert)
|
|
45
|
+
return { cert, key }
|
|
46
|
+
catch e
|
|
47
|
+
console.error "rip-server: failed to load TLS certs from #{certsDir}: #{e.message}"
|
|
48
|
+
console.error 'Use --cert/--key to provide your own, or use http to disable TLS.'
|
|
49
|
+
exit 2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rip-lang/server",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.112",
|
|
4
4
|
"description": "Pure Rip web framework and application server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "api.rip",
|
|
@@ -53,10 +53,13 @@
|
|
|
53
53
|
"middleware.rip",
|
|
54
54
|
"server.rip",
|
|
55
55
|
"server.html",
|
|
56
|
-
"
|
|
56
|
+
"acme/",
|
|
57
57
|
"bin/",
|
|
58
|
-
"
|
|
58
|
+
"certs/",
|
|
59
|
+
"control/",
|
|
59
60
|
"docs/",
|
|
61
|
+
"edge/",
|
|
62
|
+
"tests/",
|
|
60
63
|
"README.md"
|
|
61
64
|
]
|
|
62
65
|
}
|