@rip-lang/server 1.3.115 → 1.3.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +435 -622
- package/api.rip +4 -4
- package/control/cli.rip +221 -1
- package/control/control.rip +9 -0
- package/control/lifecycle.rip +6 -1
- package/control/watchers.rip +10 -0
- package/control/workers.rip +9 -5
- package/default.rip +3 -1
- package/docs/READ_VALIDATORS.md +656 -0
- package/docs/edge/CONFIG_LIFECYCLE.md +70 -32
- package/docs/edge/CONTRACTS.md +60 -69
- package/docs/edge/EDGEFILE_CONTRACT.md +258 -29
- package/docs/edge/M0B_REVIEW_NOTES.md +5 -5
- package/edge/config.rip +584 -52
- package/edge/forwarding.rip +6 -2
- package/edge/metrics.rip +19 -1
- package/edge/registry.rip +29 -3
- package/edge/router.rip +138 -0
- package/edge/runtime.rip +98 -0
- package/edge/static.rip +69 -0
- package/edge/tls.rip +23 -0
- package/edge/upstream.rip +272 -0
- package/edge/verify.rip +73 -0
- package/middleware.rip +3 -3
- package/package.json +2 -2
- package/server.rip +775 -393
- package/tests/control.rip +18 -0
- package/tests/edgefile.rip +165 -0
- package/tests/metrics.rip +16 -0
- package/tests/proxy.rip +22 -1
- package/tests/registry.rip +27 -0
- package/tests/router.rip +101 -0
- package/tests/runtime_entrypoints.rip +16 -0
- package/tests/servers.rip +262 -0
- package/tests/static.rip +64 -0
- package/tests/streams_clienthello.rip +108 -0
- package/tests/streams_index.rip +53 -0
- package/tests/streams_pipe.rip +70 -0
- package/tests/streams_router.rip +39 -0
- package/tests/streams_runtime.rip +38 -0
- package/tests/streams_upstream.rip +34 -0
- package/tests/upstream.rip +191 -0
- package/tests/verify.rip +148 -0
- package/tests/watchers.rip +15 -0
|
@@ -1,73 +1,111 @@
|
|
|
1
|
-
# Edge Config Lifecycle
|
|
1
|
+
# Edge Config Lifecycle
|
|
2
2
|
|
|
3
|
-
This document
|
|
3
|
+
This document describes the implemented v1 lifecycle for `Edgefile.rip` changes.
|
|
4
4
|
|
|
5
5
|
## Goal
|
|
6
6
|
|
|
7
|
-
Config updates must be
|
|
7
|
+
Config updates must be:
|
|
8
|
+
|
|
9
|
+
- atomic
|
|
10
|
+
- reversible
|
|
11
|
+
- observable
|
|
12
|
+
- safe for in-flight HTTP and websocket proxy traffic
|
|
8
13
|
|
|
9
14
|
## Apply pipeline
|
|
10
15
|
|
|
11
16
|
1. **Parse**
|
|
12
17
|
- Load `Edgefile.rip`.
|
|
13
|
-
-
|
|
14
|
-
- Reject
|
|
18
|
+
- Evaluate it synchronously in config context.
|
|
19
|
+
- Reject on syntax or runtime errors.
|
|
15
20
|
|
|
16
21
|
2. **Validate**
|
|
17
|
-
-
|
|
18
|
-
- Validate
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
- Require `version: 1`.
|
|
23
|
+
- Validate only supported top-level keys:
|
|
24
|
+
- `version`
|
|
25
|
+
- `edge`
|
|
26
|
+
- `upstreams`
|
|
27
|
+
- `apps`
|
|
28
|
+
- `routes`
|
|
29
|
+
- `sites`
|
|
30
|
+
- Validate route references, wildcard hosts, websocket route requirements,
|
|
31
|
+
timeout shapes, and verification policy.
|
|
21
32
|
|
|
22
33
|
3. **Normalize**
|
|
23
34
|
- Expand defaults from `CONTRACTS.md`.
|
|
24
|
-
-
|
|
25
|
-
|
|
35
|
+
- Normalize upstreams, managed apps, route rules, site groups, timeouts, and
|
|
36
|
+
verification policy.
|
|
37
|
+
- Compile the deterministic route table.
|
|
26
38
|
|
|
27
39
|
4. **Stage**
|
|
28
|
-
- Build a
|
|
29
|
-
|
|
40
|
+
- Build a new edge runtime generation:
|
|
41
|
+
- upstream pool
|
|
42
|
+
- route table
|
|
43
|
+
- config metadata
|
|
44
|
+
- verification policy
|
|
45
|
+
- Prepare managed app registry changes for the new config.
|
|
30
46
|
- Do not affect active traffic yet.
|
|
31
47
|
|
|
32
48
|
5. **Activate**
|
|
33
|
-
- Swap active
|
|
34
|
-
- Begin routing new
|
|
35
|
-
- Keep previous
|
|
49
|
+
- Swap the active edge runtime pointer atomically.
|
|
50
|
+
- Begin routing new requests through the new generation immediately.
|
|
51
|
+
- Keep the previous generation as a retired runtime for rollback or drain.
|
|
36
52
|
|
|
37
53
|
6. **Post-activate verify**
|
|
38
|
-
-
|
|
39
|
-
-
|
|
54
|
+
- Run route-aware verification according to `edge.verify`.
|
|
55
|
+
- Check referenced upstreams, healthy target counts, and managed app worker readiness.
|
|
56
|
+
- Emit structured activation events.
|
|
40
57
|
|
|
41
58
|
7. **Rollback (if needed)**
|
|
42
|
-
-
|
|
43
|
-
-
|
|
59
|
+
- Restore the previous app registry snapshot.
|
|
60
|
+
- Restore the previous active edge runtime.
|
|
61
|
+
- Mark the failed generation retired and let it drain if needed.
|
|
62
|
+
- Record structured rollback metadata including reason code and details.
|
|
44
63
|
|
|
45
64
|
## Trigger modes
|
|
46
65
|
|
|
47
66
|
- file watch (`Edgefile.rip`)
|
|
48
67
|
- `SIGHUP`
|
|
49
|
-
- control API
|
|
68
|
+
- control API: `POST /reload` on the control Unix socket
|
|
50
69
|
|
|
51
|
-
All triggers use the same
|
|
70
|
+
All triggers use the same runtime reload path and the same safeguards.
|
|
52
71
|
|
|
53
72
|
## Determinism rules
|
|
54
73
|
|
|
55
|
-
- No async
|
|
56
|
-
-
|
|
57
|
-
-
|
|
74
|
+
- No async work while evaluating config.
|
|
75
|
+
- No network I/O while evaluating config.
|
|
76
|
+
- Validation must be repeatable for identical input.
|
|
58
77
|
|
|
59
78
|
## Failure behavior
|
|
60
79
|
|
|
61
|
-
- Parse/validate failure: keep serving with existing config.
|
|
62
|
-
- Stage failure: keep serving with existing config.
|
|
63
|
-
- Post-activate
|
|
80
|
+
- Parse/validate failure: keep serving with the existing config.
|
|
81
|
+
- Stage failure: keep serving with the existing config.
|
|
82
|
+
- Post-activate verification failure: rollback to the previous runtime automatically.
|
|
83
|
+
|
|
84
|
+
## Draining behavior
|
|
85
|
+
|
|
86
|
+
- Retired runtimes remain alive while:
|
|
87
|
+
- HTTP requests that started on them are still in flight
|
|
88
|
+
- websocket proxy connections that started on them are still open
|
|
89
|
+
- Health checks are stopped only after a retired runtime finishes draining.
|
|
64
90
|
|
|
65
91
|
## Observability
|
|
66
92
|
|
|
67
|
-
Every
|
|
93
|
+
Every reload attempt records:
|
|
94
|
+
|
|
68
95
|
- attempt ID
|
|
69
|
-
- source trigger (
|
|
96
|
+
- source trigger (`startup`, `sighup`, `control_api`)
|
|
70
97
|
- old version
|
|
71
|
-
- new version
|
|
72
|
-
- result
|
|
98
|
+
- new version
|
|
99
|
+
- result (`loaded`, `applied`, `rejected`, `rolled_back`)
|
|
100
|
+
- reason
|
|
73
101
|
- reason code
|
|
102
|
+
- reason details
|
|
103
|
+
- timestamp
|
|
104
|
+
|
|
105
|
+
`/diagnostics` exposes:
|
|
106
|
+
|
|
107
|
+
- active config metadata
|
|
108
|
+
- active runtime inflight and websocket counts
|
|
109
|
+
- retired runtimes still draining
|
|
110
|
+
- last reload attempt
|
|
111
|
+
- bounded reload history
|
package/docs/edge/CONTRACTS.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
# Edge Contracts
|
|
1
|
+
# Edge Contracts
|
|
2
2
|
|
|
3
|
-
This document
|
|
4
|
-
|
|
5
|
-
Status: Draft frozen for M0b review.
|
|
3
|
+
This document describes the implemented core contracts for the unified
|
|
4
|
+
edge/app runtime in `@rip-lang/server`.
|
|
6
5
|
|
|
7
6
|
## Source of truth inputs
|
|
8
7
|
|
|
9
|
-
-
|
|
8
|
+
- Runtime implementation: `packages/server/server.rip`
|
|
9
|
+
- Edge config normalization: `packages/server/edge/config.rip`
|
|
10
|
+
- Edge runtime lifecycle: `packages/server/edge/runtime.rip`
|
|
11
|
+
- Verification policy: `packages/server/edge/verify.rip`
|
|
10
12
|
- TLS findings: `packages/server/spikes/tls/FINDINGS.md`
|
|
11
13
|
|
|
12
14
|
## AppDescriptor
|
|
@@ -14,35 +16,17 @@ Status: Draft frozen for M0b review.
|
|
|
14
16
|
```ts
|
|
15
17
|
type AppDescriptor = {
|
|
16
18
|
id: string
|
|
17
|
-
entry: string
|
|
19
|
+
entry: string | null
|
|
20
|
+
appBaseDir: string | null
|
|
18
21
|
hosts: string[]
|
|
19
|
-
|
|
20
|
-
workers: number
|
|
22
|
+
workers: number | null
|
|
21
23
|
maxQueue: number
|
|
22
|
-
|
|
24
|
+
queueTimeoutMs: number
|
|
25
|
+
readTimeoutMs: number
|
|
23
26
|
env: Record<string, string>
|
|
24
|
-
restartPolicy: {
|
|
25
|
-
maxRestarts: number
|
|
26
|
-
backoffMs: number
|
|
27
|
-
windowMs: number
|
|
28
|
-
}
|
|
29
|
-
healthCheck: {
|
|
30
|
-
path: string
|
|
31
|
-
intervalMs: number
|
|
32
|
-
timeoutMs: number
|
|
33
|
-
unhealthyThreshold: number
|
|
34
|
-
}
|
|
35
27
|
}
|
|
36
28
|
```
|
|
37
29
|
|
|
38
|
-
Defaults:
|
|
39
|
-
- `workers`: `cpus().length`, min `2`
|
|
40
|
-
- `maxQueue`: `1000`
|
|
41
|
-
- `maxInflight`: `workers * 32`
|
|
42
|
-
- `env`: `{}`
|
|
43
|
-
- `restartPolicy`: `{ maxRestarts: 10, backoffMs: 1000, windowMs: 60000 }`
|
|
44
|
-
- `healthCheck`: `{ path: "/ready", intervalMs: 5000, timeoutMs: 2000, unhealthyThreshold: 3 }`
|
|
45
|
-
|
|
46
30
|
## WorkerEndpoint
|
|
47
31
|
|
|
48
32
|
```ts
|
|
@@ -51,48 +35,62 @@ type WorkerEndpoint = {
|
|
|
51
35
|
workerId: number
|
|
52
36
|
socketPath: string
|
|
53
37
|
inflight: number
|
|
54
|
-
version: number
|
|
55
|
-
state: "starting" | "ready" | "draining" | "stopped"
|
|
38
|
+
version: number | null
|
|
56
39
|
startedAt: number
|
|
57
|
-
requestCount: number
|
|
58
40
|
}
|
|
59
41
|
```
|
|
60
42
|
|
|
61
|
-
Defaults:
|
|
62
|
-
- `inflight`: `0`
|
|
63
|
-
- `state`: `"starting"`
|
|
64
|
-
- `requestCount`: `0`
|
|
65
|
-
|
|
66
|
-
## RouteAction
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
type RouteAction =
|
|
70
|
-
| { kind: "toApp"; appId: string }
|
|
71
|
-
| { kind: "proxy"; upstream: string; host?: string }
|
|
72
|
-
| { kind: "static"; dir: string; spaFallback?: string }
|
|
73
|
-
| { kind: "redirect"; to: string; status: number }
|
|
74
|
-
| { kind: "headers"; set?: Record<string, string>; remove?: string[] }
|
|
75
|
-
```
|
|
76
|
-
|
|
77
43
|
## RouteRule
|
|
78
44
|
|
|
79
45
|
```ts
|
|
80
46
|
type RouteRule = {
|
|
81
47
|
id: string
|
|
82
|
-
host: string | "*"
|
|
83
|
-
|
|
48
|
+
host: string | "*" | "*.example.com"
|
|
49
|
+
path: string
|
|
84
50
|
methods: string[] | "*"
|
|
85
|
-
action: RouteAction
|
|
86
51
|
priority: number
|
|
52
|
+
upstream?: string | null
|
|
53
|
+
app?: string | null
|
|
54
|
+
static?: string | null
|
|
55
|
+
redirect?: { to: string, status: number } | null
|
|
56
|
+
headers?: { set?: Record<string, string>, remove?: string[] } | null
|
|
57
|
+
websocket?: boolean
|
|
87
58
|
timeouts?: Partial<TimeoutPolicy>
|
|
88
59
|
}
|
|
89
60
|
```
|
|
90
61
|
|
|
62
|
+
## VerifyPolicy
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
type VerifyPolicy = {
|
|
66
|
+
requireHealthyUpstreams: boolean
|
|
67
|
+
requireReadyApps: boolean
|
|
68
|
+
includeUnroutedManagedApps: boolean
|
|
69
|
+
minHealthyTargetsPerUpstream: number
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
91
73
|
Defaults:
|
|
92
|
-
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
74
|
+
|
|
75
|
+
- `requireHealthyUpstreams: true`
|
|
76
|
+
- `requireReadyApps: true`
|
|
77
|
+
- `includeUnroutedManagedApps: true`
|
|
78
|
+
- `minHealthyTargetsPerUpstream: 1`
|
|
79
|
+
|
|
80
|
+
## EdgeRuntime
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
type EdgeRuntime = {
|
|
84
|
+
id: string
|
|
85
|
+
upstreamPool: UpstreamPool
|
|
86
|
+
routeTable: RouteTable
|
|
87
|
+
configInfo: ConfigInfo
|
|
88
|
+
verifyPolicy: VerifyPolicy | null
|
|
89
|
+
inflight: number
|
|
90
|
+
wsConnections: number
|
|
91
|
+
retiredAt: string | null
|
|
92
|
+
}
|
|
93
|
+
```
|
|
96
94
|
|
|
97
95
|
## SchedulerDecision
|
|
98
96
|
|
|
@@ -107,9 +105,10 @@ type SchedulerDecision = {
|
|
|
107
105
|
```
|
|
108
106
|
|
|
109
107
|
Defaults:
|
|
110
|
-
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
108
|
+
|
|
109
|
+
- `algorithm: "least-inflight"`
|
|
110
|
+
- `fallback: "queue"` while queue `< maxQueue`, else `"shed-503"`
|
|
111
|
+
- `queuePosition: null` when immediately assigned
|
|
113
112
|
|
|
114
113
|
## TlsCertRecord
|
|
115
114
|
|
|
@@ -128,19 +127,11 @@ type TlsCertRecord = {
|
|
|
128
127
|
}
|
|
129
128
|
```
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
- `source`: `"acme"` for auto-managed certificates
|
|
133
|
-
- `lastRenewAttempt`: `null`
|
|
134
|
-
- `lastRenewResult`: `null`
|
|
135
|
-
|
|
136
|
-
## Constraint from M0a
|
|
130
|
+
## TLS constraints
|
|
137
131
|
|
|
138
132
|
From `packages/server/spikes/tls/FINDINGS.md`:
|
|
139
|
-
- dynamic SNI selection and ALPN-driven cert selection were not observed
|
|
140
|
-
- in-process cert hot reload was not observed
|
|
141
|
-
- graceful restart reload works
|
|
142
133
|
|
|
143
|
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
134
|
+
- dynamic SNI selection was not observed
|
|
135
|
+
- in-process cert hot reload was not observed
|
|
136
|
+
- graceful restart cert activation works
|
|
146
137
|
- ACME HTTP-01 is the reliable v1 baseline
|
|
@@ -1,53 +1,282 @@
|
|
|
1
|
-
# Edgefile Contract
|
|
1
|
+
# Edgefile Contract
|
|
2
2
|
|
|
3
|
-
This document
|
|
3
|
+
This document defines the `Edgefile.rip` contract as implemented in
|
|
4
|
+
`@rip-lang/server`.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Host model detection
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
The Edgefile supports two host models, auto-detected by key presence:
|
|
9
|
+
|
|
10
|
+
- **Flat model** (`routes`/`sites`) -- routes listed at the top level with optional per-host site groups
|
|
11
|
+
- **Host blocks** (`hosts`) -- per-domain blocks that own cert, root, routes, and passthrough
|
|
12
|
+
`version` and `edge` are optional (default to `1` and `{}` respectively).
|
|
13
|
+
|
|
14
|
+
## Flat model shape
|
|
15
|
+
|
|
16
|
+
```coffee
|
|
17
|
+
export default
|
|
18
|
+
version: 1
|
|
19
|
+
edge: ...
|
|
20
|
+
upstreams: ...
|
|
21
|
+
streamUpstreams: ...
|
|
22
|
+
apps: ...
|
|
23
|
+
routes: ...
|
|
24
|
+
streams: ...
|
|
25
|
+
sites: ...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Server blocks shape
|
|
29
|
+
|
|
30
|
+
```coffee
|
|
31
|
+
export default
|
|
32
|
+
hosts: ...
|
|
33
|
+
upstreams: ...
|
|
34
|
+
apps: ...
|
|
35
|
+
streamUpstreams: ...
|
|
36
|
+
streams: ...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `hosts`
|
|
40
|
+
|
|
41
|
+
Per-domain server blocks, keyed by hostname (exact or wildcard).
|
|
42
|
+
|
|
43
|
+
```coffee
|
|
44
|
+
hosts:
|
|
45
|
+
'*.trusthealth.com':
|
|
46
|
+
cert: '/ssl/trusthealth.com.crt'
|
|
47
|
+
key: '/ssl/trusthealth.com.key'
|
|
48
|
+
root: '/mnt/trusthealth/website'
|
|
49
|
+
routes: [
|
|
50
|
+
{ path: '/*', static: '.', spa: true }
|
|
51
|
+
]
|
|
12
52
|
```
|
|
13
53
|
|
|
54
|
+
Server block fields:
|
|
55
|
+
|
|
56
|
+
- `passthrough?: string` -- raw TLS passthrough to `host:port` (no cert, no routes needed)
|
|
57
|
+
- `cert?: string` -- TLS certificate path (must pair with `key`)
|
|
58
|
+
- `key?: string` -- TLS private key path (must pair with `cert`)
|
|
59
|
+
- `root?: string` -- default filesystem base; serves static files if no routes are defined
|
|
60
|
+
- `spa?: boolean` -- server-level SPA fallback (used with root-only blocks)
|
|
61
|
+
- `routes?: array` -- route objects (optional if `root` or `passthrough` is set)
|
|
62
|
+
- `timeouts?: object` -- per-server timeout defaults
|
|
63
|
+
|
|
64
|
+
Per-server `cert`/`key` enable SNI-based certificate selection via Bun's TLS
|
|
65
|
+
array. `edge.cert` and `edge.key` remain the fallback TLS identity for
|
|
66
|
+
unmatched hostnames. The TLS array is sorted by specificity: exact hosts first,
|
|
67
|
+
then wildcards by label count, then the fallback entry.
|
|
68
|
+
|
|
69
|
+
Routes inside a server block inherit the server hostname and must not specify
|
|
70
|
+
`host`. Routes with `upstream` or `app` must reference known entries from the
|
|
71
|
+
top-level `upstreams` or `apps` sections.
|
|
72
|
+
|
|
73
|
+
Static routes support `static` (string path), `root` (overrides server root),
|
|
74
|
+
and `spa` (boolean, serves `index.html` on miss for GET/HEAD with Accept
|
|
75
|
+
text/html).
|
|
76
|
+
|
|
77
|
+
Hosts not matching any server block or stream route fall through to the default
|
|
78
|
+
app.
|
|
79
|
+
|
|
14
80
|
## Determinism policy
|
|
15
81
|
|
|
16
82
|
- Config evaluation is synchronous.
|
|
17
83
|
- Async operations are disallowed.
|
|
18
84
|
- Network I/O is disallowed.
|
|
19
|
-
- Validation errors must include
|
|
85
|
+
- Validation errors must include a field path, message, and remediation hint.
|
|
86
|
+
- With the same file contents, config evaluation must produce the same normalized shape.
|
|
87
|
+
|
|
88
|
+
## Top-level sections
|
|
89
|
+
|
|
90
|
+
### `version`
|
|
91
|
+
|
|
92
|
+
- Optional. Defaults to `1`.
|
|
93
|
+
|
|
94
|
+
### `edge`
|
|
95
|
+
|
|
96
|
+
Global edge settings. Optional (defaults to `{}`).
|
|
97
|
+
|
|
98
|
+
Supported keys:
|
|
99
|
+
|
|
100
|
+
- `acme: boolean`
|
|
101
|
+
- `acmeDomains: string[]`
|
|
102
|
+
- `cert: string`
|
|
103
|
+
- `key: string`
|
|
104
|
+
- `hsts: boolean`
|
|
105
|
+
- `trustedProxies: string[]`
|
|
106
|
+
- `timeouts: { connectMs, readMs }`
|
|
107
|
+
- `verify: { requireHealthyUpstreams, requireReadyApps, includeUnroutedManagedApps, minHealthyTargetsPerUpstream }`
|
|
108
|
+
|
|
109
|
+
### `upstreams`
|
|
110
|
+
|
|
111
|
+
Named reverse-proxy backends.
|
|
112
|
+
|
|
113
|
+
```coffee
|
|
114
|
+
upstreams:
|
|
115
|
+
app:
|
|
116
|
+
targets: ['http://app.incusbr0:3000']
|
|
117
|
+
healthCheck:
|
|
118
|
+
path: '/health'
|
|
119
|
+
intervalMs: 5000
|
|
120
|
+
timeoutMs: 2000
|
|
121
|
+
retry:
|
|
122
|
+
attempts: 2
|
|
123
|
+
retryOn: [502, 503, 504]
|
|
124
|
+
timeouts:
|
|
125
|
+
connectMs: 2000
|
|
126
|
+
readMs: 30000
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `apps`
|
|
130
|
+
|
|
131
|
+
Managed Rip applications with worker pools.
|
|
132
|
+
|
|
133
|
+
```coffee
|
|
134
|
+
apps:
|
|
135
|
+
admin:
|
|
136
|
+
entry: './admin/index.rip'
|
|
137
|
+
hosts: ['admin.example.com']
|
|
138
|
+
workers: 2
|
|
139
|
+
maxQueue: 512
|
|
140
|
+
queueTimeoutMs: 30000
|
|
141
|
+
readTimeoutMs: 30000
|
|
142
|
+
env:
|
|
143
|
+
ADMIN_MODE: '1'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `streamUpstreams`
|
|
20
147
|
|
|
21
|
-
|
|
148
|
+
Named raw TCP upstreams for Layer 4 passthrough.
|
|
22
149
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
150
|
+
```coffee
|
|
151
|
+
streamUpstreams:
|
|
152
|
+
incus:
|
|
153
|
+
targets: ['127.0.0.1:8443']
|
|
154
|
+
connectTimeoutMs: 5000 # optional, default 5000
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `routes`
|
|
158
|
+
|
|
159
|
+
Declarative edge route objects.
|
|
160
|
+
|
|
161
|
+
Each route must define exactly one action:
|
|
162
|
+
|
|
163
|
+
- `upstream`
|
|
164
|
+
- `app`
|
|
165
|
+
- `static`
|
|
166
|
+
- `redirect`
|
|
167
|
+
- `headers`
|
|
168
|
+
|
|
169
|
+
Common route fields:
|
|
170
|
+
|
|
171
|
+
- `id?: string`
|
|
172
|
+
- `host?: string` — exact host, wildcard host like `*.example.com`, or `*`
|
|
173
|
+
- `path: string` — must start with `/`
|
|
174
|
+
- `methods?: string[] | "*"`
|
|
175
|
+
- `priority?: number`
|
|
176
|
+
- `timeouts?: { connectMs, readMs }`
|
|
177
|
+
|
|
178
|
+
Action-specific fields:
|
|
179
|
+
|
|
180
|
+
- `upstream: string`
|
|
181
|
+
- `app: string`
|
|
182
|
+
- `static: string`
|
|
183
|
+
- `redirect: { to: string, status: number }`
|
|
184
|
+
- `headers: { set?: object, remove?: string[] }`
|
|
185
|
+
|
|
186
|
+
WebSocket proxy routes use:
|
|
187
|
+
|
|
188
|
+
- `websocket: true`
|
|
189
|
+
- `upstream: string`
|
|
190
|
+
|
|
191
|
+
### `streams`
|
|
192
|
+
|
|
193
|
+
Declarative Layer 4 stream routes.
|
|
194
|
+
|
|
195
|
+
```coffee
|
|
196
|
+
streams: [
|
|
197
|
+
{ listen: 8443, sni: ['incus.example.com'], upstream: 'incus' }
|
|
198
|
+
]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Supported fields:
|
|
202
|
+
|
|
203
|
+
- `id?: string`
|
|
204
|
+
- `listen: number`
|
|
205
|
+
- `sni: string[]`
|
|
206
|
+
- `upstream: string`
|
|
207
|
+
- `timeouts?: { handshakeMs, idleMs, connectMs }`
|
|
208
|
+
|
|
209
|
+
If a stream route listens on the active HTTPS port, Rip switches that port into
|
|
210
|
+
a shared multiplexer mode:
|
|
211
|
+
|
|
212
|
+
- the public port is owned by the Layer 4 listener
|
|
213
|
+
- matching SNI traffic is passed through to the configured `streamUpstreams`
|
|
214
|
+
- non-matching SNI, or TLS clients without SNI, fall through to Rip's internal
|
|
215
|
+
HTTPS server and continue through the normal HTTP/WebSocket edge runtime
|
|
216
|
+
|
|
217
|
+
### `sites`
|
|
218
|
+
|
|
219
|
+
Per-host route groups and policy overrides.
|
|
220
|
+
|
|
221
|
+
```coffee
|
|
222
|
+
sites:
|
|
223
|
+
'admin.example.com':
|
|
224
|
+
routes: [
|
|
225
|
+
{ path: '/*', app: 'admin' }
|
|
226
|
+
]
|
|
227
|
+
```
|
|
28
228
|
|
|
29
229
|
## Host/path precedence
|
|
30
230
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
231
|
+
Route precedence is:
|
|
232
|
+
|
|
233
|
+
1. exact host
|
|
234
|
+
2. wildcard host
|
|
235
|
+
3. catch-all host
|
|
236
|
+
4. more specific path
|
|
237
|
+
5. lower explicit priority number
|
|
238
|
+
6. declaration order tie-break
|
|
239
|
+
|
|
240
|
+
Wildcard hosts are single-label only:
|
|
241
|
+
|
|
242
|
+
- `*.example.com` matches `api.example.com`
|
|
243
|
+
- `*.example.com` does **not** match `a.b.example.com`
|
|
35
244
|
|
|
36
245
|
## Timeout inheritance
|
|
37
246
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
247
|
+
Timeout resolution is:
|
|
248
|
+
|
|
249
|
+
1. route-level `timeouts`
|
|
250
|
+
2. site-level `timeouts`
|
|
251
|
+
3. edge-level `timeouts`
|
|
252
|
+
4. server defaults
|
|
253
|
+
|
|
254
|
+
## Verification policy
|
|
255
|
+
|
|
256
|
+
`edge.verify` tunes post-activate verification:
|
|
257
|
+
|
|
258
|
+
- `requireHealthyUpstreams: boolean`
|
|
259
|
+
- `requireReadyApps: boolean`
|
|
260
|
+
- `includeUnroutedManagedApps: boolean`
|
|
261
|
+
- `minHealthyTargetsPerUpstream: number`
|
|
262
|
+
|
|
263
|
+
These settings affect staged activation, post-activate verification, and rollback.
|
|
41
264
|
|
|
42
265
|
## Reload semantics
|
|
43
266
|
|
|
44
|
-
-
|
|
45
|
-
- Invalid config never replaces active config.
|
|
267
|
+
- Every reload trigger runs the same lifecycle.
|
|
268
|
+
- Invalid config never replaces the active config.
|
|
269
|
+
- A new edge runtime is staged, activated atomically, then verified.
|
|
270
|
+
- Failed verification causes automatic rollback to the previous runtime.
|
|
271
|
+
- Retired runtimes drain in-flight HTTP and websocket proxy traffic before cleanup.
|
|
46
272
|
|
|
47
|
-
## v1 TLS implications
|
|
273
|
+
## v1 TLS implications
|
|
48
274
|
|
|
49
275
|
Based on `packages/server/spikes/tls/FINDINGS.md`:
|
|
50
|
-
|
|
51
|
-
- v1
|
|
52
|
-
- v1
|
|
53
|
-
- v1
|
|
276
|
+
|
|
277
|
+
- v1 does not assume dynamic per-SNI cert switching.
|
|
278
|
+
- v1 does not assume in-process cert hot reload.
|
|
279
|
+
- v1 uses graceful restart for cert activation.
|
|
280
|
+
- v1 ACME prioritizes HTTP-01.
|
|
281
|
+
- ACME HTTP-01 cannot issue wildcard certificates. Wildcard TLS requires manual
|
|
282
|
+
`cert` + `key`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# M0b Review Notes
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: Maintained
|
|
4
4
|
|
|
5
5
|
## Accepted decisions
|
|
6
6
|
|
|
@@ -25,9 +25,9 @@ Status: Active
|
|
|
25
25
|
|
|
26
26
|
## Open items for next review pass
|
|
27
27
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Confirm
|
|
28
|
+
- Expand end-to-end websocket proxy coverage beyond the current package tests
|
|
29
|
+
- Decide whether staged rollout policy should gain additional operator tuning beyond `edge.verify`
|
|
30
|
+
- Confirm retention policy expectations for reload history and retired runtime visibility in deployment docs
|
|
31
31
|
|
|
32
32
|
## Refactor Guardrails
|
|
33
33
|
|
|
@@ -99,4 +99,4 @@ catch e
|
|
|
99
99
|
- `packages/server/spikes/tls/README.md`
|
|
100
100
|
- `packages/server/spikes/tls/FINDINGS.md`
|
|
101
101
|
- Plan of record:
|
|
102
|
-
-
|
|
102
|
+
- `packages/server/docs/edge/PURE_BUN_EDGE_PLAN.md`
|