@yannelli/zoomies 0.0.0
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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/cli/client.d.ts +77 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +300 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/commands/certs.d.ts +11 -0
- package/dist/cli/commands/certs.d.ts.map +1 -0
- package/dist/cli/commands/certs.js +110 -0
- package/dist/cli/commands/certs.js.map +1 -0
- package/dist/cli/commands/flags.d.ts +29 -0
- package/dist/cli/commands/flags.d.ts.map +1 -0
- package/dist/cli/commands/flags.js +104 -0
- package/dist/cli/commands/flags.js.map +1 -0
- package/dist/cli/commands/reload.d.ts +11 -0
- package/dist/cli/commands/reload.d.ts.map +1 -0
- package/dist/cli/commands/reload.js +35 -0
- package/dist/cli/commands/reload.js.map +1 -0
- package/dist/cli/commands/sites.d.ts +11 -0
- package/dist/cli/commands/sites.d.ts.map +1 -0
- package/dist/cli/commands/sites.js +221 -0
- package/dist/cli/commands/sites.js.map +1 -0
- package/dist/cli/commands/status.d.ts +10 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +41 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/upstreams.d.ts +21 -0
- package/dist/cli/commands/upstreams.d.ts.map +1 -0
- package/dist/cli/commands/upstreams.js +248 -0
- package/dist/cli/commands/upstreams.js.map +1 -0
- package/dist/cli/dispatcher.d.ts +45 -0
- package/dist/cli/dispatcher.d.ts.map +1 -0
- package/dist/cli/dispatcher.js +192 -0
- package/dist/cli/dispatcher.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/server/api/db-context.d.ts +50 -0
- package/dist/server/api/db-context.d.ts.map +1 -0
- package/dist/server/api/db-context.js +76 -0
- package/dist/server/api/db-context.js.map +1 -0
- package/dist/server/api/error-mapping.d.ts +19 -0
- package/dist/server/api/error-mapping.d.ts.map +1 -0
- package/dist/server/api/error-mapping.js +56 -0
- package/dist/server/api/error-mapping.js.map +1 -0
- package/dist/server/api/handlers/site-cert.d.ts +49 -0
- package/dist/server/api/handlers/site-cert.d.ts.map +1 -0
- package/dist/server/api/handlers/site-cert.js +54 -0
- package/dist/server/api/handlers/site-cert.js.map +1 -0
- package/dist/server/api/handlers/sites.d.ts +67 -0
- package/dist/server/api/handlers/sites.d.ts.map +1 -0
- package/dist/server/api/handlers/sites.js +78 -0
- package/dist/server/api/handlers/sites.js.map +1 -0
- package/dist/server/api/handlers/upstreams.d.ts +64 -0
- package/dist/server/api/handlers/upstreams.d.ts.map +1 -0
- package/dist/server/api/handlers/upstreams.js +97 -0
- package/dist/server/api/handlers/upstreams.js.map +1 -0
- package/dist/server/auth/require-token.d.ts +24 -0
- package/dist/server/auth/require-token.d.ts.map +1 -0
- package/dist/server/auth/require-token.js +98 -0
- package/dist/server/auth/require-token.js.map +1 -0
- package/dist/server/certs/acme-account.d.ts +37 -0
- package/dist/server/certs/acme-account.d.ts.map +1 -0
- package/dist/server/certs/acme-account.js +49 -0
- package/dist/server/certs/acme-account.js.map +1 -0
- package/dist/server/certs/challenge-store.d.ts +53 -0
- package/dist/server/certs/challenge-store.d.ts.map +1 -0
- package/dist/server/certs/challenge-store.js +66 -0
- package/dist/server/certs/challenge-store.js.map +1 -0
- package/dist/server/certs/issue.d.ts +106 -0
- package/dist/server/certs/issue.d.ts.map +1 -0
- package/dist/server/certs/issue.js +107 -0
- package/dist/server/certs/issue.js.map +1 -0
- package/dist/server/certs/renew.d.ts +34 -0
- package/dist/server/certs/renew.d.ts.map +1 -0
- package/dist/server/certs/renew.js +36 -0
- package/dist/server/certs/renew.js.map +1 -0
- package/dist/server/certs/scheduler.d.ts +68 -0
- package/dist/server/certs/scheduler.d.ts.map +1 -0
- package/dist/server/certs/scheduler.js +76 -0
- package/dist/server/certs/scheduler.js.map +1 -0
- package/dist/server/db/connection.d.ts +10 -0
- package/dist/server/db/connection.d.ts.map +1 -0
- package/dist/server/db/connection.js +16 -0
- package/dist/server/db/connection.js.map +1 -0
- package/dist/server/db/migrate.d.ts +12 -0
- package/dist/server/db/migrate.d.ts.map +1 -0
- package/dist/server/db/migrate.js +37 -0
- package/dist/server/db/migrate.js.map +1 -0
- package/dist/server/db/migrations/0001_init.sql +42 -0
- package/dist/server/domain/cert.d.ts +17 -0
- package/dist/server/domain/cert.d.ts.map +1 -0
- package/dist/server/domain/cert.js +22 -0
- package/dist/server/domain/cert.js.map +1 -0
- package/dist/server/domain/errors.d.ts +36 -0
- package/dist/server/domain/errors.d.ts.map +1 -0
- package/dist/server/domain/errors.js +37 -0
- package/dist/server/domain/errors.js.map +1 -0
- package/dist/server/domain/site.d.ts +15 -0
- package/dist/server/domain/site.d.ts.map +1 -0
- package/dist/server/domain/site.js +25 -0
- package/dist/server/domain/site.js.map +1 -0
- package/dist/server/domain/upstream.d.ts +25 -0
- package/dist/server/domain/upstream.d.ts.map +1 -0
- package/dist/server/domain/upstream.js +24 -0
- package/dist/server/domain/upstream.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/reload/atomic-write.d.ts +44 -0
- package/dist/server/reload/atomic-write.d.ts.map +1 -0
- package/dist/server/reload/atomic-write.js +151 -0
- package/dist/server/reload/atomic-write.js.map +1 -0
- package/dist/server/reload/health-probe.d.ts +62 -0
- package/dist/server/reload/health-probe.d.ts.map +1 -0
- package/dist/server/reload/health-probe.js +105 -0
- package/dist/server/reload/health-probe.js.map +1 -0
- package/dist/server/reload/reload.d.ts +118 -0
- package/dist/server/reload/reload.d.ts.map +1 -0
- package/dist/server/reload/reload.js +232 -0
- package/dist/server/reload/reload.js.map +1 -0
- package/dist/server/renderer/render-bundle.d.ts +18 -0
- package/dist/server/renderer/render-bundle.d.ts.map +1 -0
- package/dist/server/renderer/render-bundle.js +32 -0
- package/dist/server/renderer/render-bundle.js.map +1 -0
- package/dist/server/renderer/render-site.d.ts +5 -0
- package/dist/server/renderer/render-site.d.ts.map +1 -0
- package/dist/server/renderer/render-site.js +144 -0
- package/dist/server/renderer/render-site.js.map +1 -0
- package/dist/server/repositories/cert-repository.d.ts +19 -0
- package/dist/server/repositories/cert-repository.d.ts.map +1 -0
- package/dist/server/repositories/cert-repository.js +112 -0
- package/dist/server/repositories/cert-repository.js.map +1 -0
- package/dist/server/repositories/site-repository.d.ts +17 -0
- package/dist/server/repositories/site-repository.d.ts.map +1 -0
- package/dist/server/repositories/site-repository.js +122 -0
- package/dist/server/repositories/site-repository.js.map +1 -0
- package/dist/server/repositories/upstream-repository.d.ts +22 -0
- package/dist/server/repositories/upstream-repository.d.ts.map +1 -0
- package/dist/server/repositories/upstream-repository.js +142 -0
- package/dist/server/repositories/upstream-repository.js.map +1 -0
- package/dist/server/validator/nginx-binary.d.ts +9 -0
- package/dist/server/validator/nginx-binary.d.ts.map +1 -0
- package/dist/server/validator/nginx-binary.js +11 -0
- package/dist/server/validator/nginx-binary.js.map +1 -0
- package/dist/server/validator/validate.d.ts +29 -0
- package/dist/server/validator/validate.d.ts.map +1 -0
- package/dist/server/validator/validate.js +69 -0
- package/dist/server/validator/validate.js.map +1 -0
- package/dist/server/worker/main.d.ts +43 -0
- package/dist/server/worker/main.d.ts.map +1 -0
- package/dist/server/worker/main.js +181 -0
- package/dist/server/worker/main.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reload orchestrator — the single chokepoint that turns a freshly rendered
|
|
3
|
+
* bundle into a live NGINX configuration.
|
|
4
|
+
*
|
|
5
|
+
* The orchestrator is the only place in the codebase allowed to mutate the
|
|
6
|
+
* managed sites directory or send a signal to NGINX. It is deliberately
|
|
7
|
+
* dependency-injected: every external side effect (validate, write, delete,
|
|
8
|
+
* reload, probe, listdir) is exposed via {@link ReloadDeps} so tests can drive
|
|
9
|
+
* each failure path without touching the real filesystem or nginx binary.
|
|
10
|
+
*
|
|
11
|
+
* Flow (each step short-circuits on failure with a labeled {@link ApplyResult}):
|
|
12
|
+
*
|
|
13
|
+
* 1. Validate the candidate via `nginx -t` — pure check, no disk writes.
|
|
14
|
+
* 2. Diff disk against `rendered` to compute the writes + orphan deletes.
|
|
15
|
+
* 3. Apply writes/deletes, accumulating rollback handles in order.
|
|
16
|
+
* 4. SIGHUP NGINX. If it refuses, roll back everything and reload again.
|
|
17
|
+
* 5. Probe the configured health URL. If it fails, roll back + reload again.
|
|
18
|
+
* 6. Success: discard rollback handles.
|
|
19
|
+
*
|
|
20
|
+
* On any step that triggers a rollback we re-run the reload so NGINX matches
|
|
21
|
+
* the disk we just restored. If that second reload also fails, we log both
|
|
22
|
+
* stderrs and still return the original failure — operator intervention is
|
|
23
|
+
* needed anyway, and recursing would hide the real cause.
|
|
24
|
+
*/
|
|
25
|
+
import { execa } from 'execa';
|
|
26
|
+
import { readdir } from 'node:fs/promises';
|
|
27
|
+
import { basename, resolve } from 'node:path';
|
|
28
|
+
import { getNginxBinary } from '../validator/nginx-binary.js';
|
|
29
|
+
import { validateConfig } from '../validator/validate.js';
|
|
30
|
+
import { deleteAtomic as deleteAtomicImpl, writeAtomic as writeAtomicImpl, } from './atomic-write.js';
|
|
31
|
+
import { probeHealth } from './health-probe.js';
|
|
32
|
+
/** Default argv for `nginx -s reload`. Kept as a constant so tests can compare by reference. */
|
|
33
|
+
const DEFAULT_RELOAD_ARGS = ['-s', 'reload'];
|
|
34
|
+
/**
|
|
35
|
+
* Concatenate the rendered per-site fragments into a single candidate the
|
|
36
|
+
* validator can `nginx -t`. Blank-line separated so NGINX parses cleanly even
|
|
37
|
+
* when individual fragments end without trailing newlines.
|
|
38
|
+
*/
|
|
39
|
+
function joinRendered(rendered) {
|
|
40
|
+
return Array.from(rendered.values()).join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Default `listManagedFiles` — read every `*.conf` directly inside `sitesDir`
|
|
44
|
+
* and return absolute paths. ENOENT (the dir hasn't been created yet) is
|
|
45
|
+
* treated as "empty" so first-run installs work without a pre-step.
|
|
46
|
+
*
|
|
47
|
+
* We do not recurse: Zoomies owns this directory flatly. If an operator drops
|
|
48
|
+
* a subdirectory in here, we ignore it.
|
|
49
|
+
*/
|
|
50
|
+
async function defaultListManagedFiles(sitesDir) {
|
|
51
|
+
try {
|
|
52
|
+
const entries = await readdir(sitesDir);
|
|
53
|
+
return entries.filter((name) => name.endsWith('.conf')).map((name) => resolve(sitesDir, name));
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err.code === 'ENOENT') {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Default `reload` — thin wrapper around `execa` that follows the project's
|
|
64
|
+
* no-shell rule (argv array, never a string). `reject: false` lets us surface
|
|
65
|
+
* a non-zero exit as a normal return value instead of an exception, which is
|
|
66
|
+
* what the orchestrator needs to decide on rollback.
|
|
67
|
+
*/
|
|
68
|
+
async function defaultReload(bin, args) {
|
|
69
|
+
const result = await execa(bin, [...args], { reject: false });
|
|
70
|
+
return {
|
|
71
|
+
exitCode: result.exitCode ?? null,
|
|
72
|
+
stderr: result.stderr ?? '',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the full {@link ReloadDeps} record, layering caller overrides onto
|
|
77
|
+
* the defaults. The defaults are constructed lazily here (rather than at
|
|
78
|
+
* module load) so tests that stub the validator or atomic-write modules can
|
|
79
|
+
* still benefit from those stubs without monkey-patching this file.
|
|
80
|
+
*/
|
|
81
|
+
function resolveDeps(overrides) {
|
|
82
|
+
return {
|
|
83
|
+
validate: overrides?.validate ?? validateConfig,
|
|
84
|
+
reload: overrides?.reload ?? defaultReload,
|
|
85
|
+
probe: overrides?.probe ?? probeHealth,
|
|
86
|
+
listManagedFiles: overrides?.listManagedFiles ?? defaultListManagedFiles,
|
|
87
|
+
writeAtomic: overrides?.writeAtomic ?? writeAtomicImpl,
|
|
88
|
+
deleteAtomic: overrides?.deleteAtomic ?? deleteAtomicImpl,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Determine which files on disk are no longer wanted (orphans to delete) and
|
|
93
|
+
* which sites need their config written (the full `rendered` set — we always
|
|
94
|
+
* overwrite, even if the contents happen to match what is on disk, because
|
|
95
|
+
* `writeAtomic`'s rollback semantics rely on snapshotting the prior bytes).
|
|
96
|
+
*
|
|
97
|
+
* Orphans are matched by basename: a file `foo.conf` whose `foo` is not a key
|
|
98
|
+
* in `rendered` gets deleted. Subtle edge case: a file ending in `.conf.new`
|
|
99
|
+
* would not be filtered here because we accept only basename-ends-with-conf
|
|
100
|
+
* via `defaultListManagedFiles`. That filter lives in the listing layer so
|
|
101
|
+
* the orchestrator can stay agnostic about disk layout.
|
|
102
|
+
*/
|
|
103
|
+
function planDiff(rendered, sitesDir, existing) {
|
|
104
|
+
const desiredIds = new Set(rendered.keys());
|
|
105
|
+
const toDelete = [];
|
|
106
|
+
for (const filePath of existing) {
|
|
107
|
+
const id = basename(filePath, '.conf');
|
|
108
|
+
if (!desiredIds.has(id)) {
|
|
109
|
+
toDelete.push(filePath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const toWrite = [];
|
|
113
|
+
for (const [siteId, contents] of rendered) {
|
|
114
|
+
toWrite.push({ path: resolve(sitesDir, `${siteId}.conf`), contents });
|
|
115
|
+
}
|
|
116
|
+
return { toWrite, toDelete };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Restore every rollback handle in reverse order, swallowing individual
|
|
120
|
+
* failures so a broken restore in the middle of the chain doesn't strand the
|
|
121
|
+
* earlier handles. We log each failure so the operator can investigate.
|
|
122
|
+
*
|
|
123
|
+
* Reverse order matters because the handles model a sequence of changes:
|
|
124
|
+
* the last write may have replaced a file that the previous delete had just
|
|
125
|
+
* removed. Unwinding LIFO mirrors the apply order and keeps the intermediate
|
|
126
|
+
* states consistent.
|
|
127
|
+
*/
|
|
128
|
+
async function rollbackAll(handles) {
|
|
129
|
+
for (let i = handles.length - 1; i >= 0; i -= 1) {
|
|
130
|
+
const handle = handles[i];
|
|
131
|
+
if (handle === undefined) {
|
|
132
|
+
// Guard for noUncheckedIndexedAccess; cannot actually happen because i
|
|
133
|
+
// is always within bounds, but the type-system needs the check.
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
await handle.restore();
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error('zoomies: rollback handle failed to restore:', err);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Apply a freshly rendered bundle to the managed sites directory.
|
|
146
|
+
*
|
|
147
|
+
* Contract:
|
|
148
|
+
* - `rendered` is the output of `renderBundle`: a map from site id to the
|
|
149
|
+
* NGINX `server { ... }` snippet for that site. Site ids are presumed
|
|
150
|
+
* safe (UUIDs per Phase 1); we do not sanitize them against `..`.
|
|
151
|
+
* - `opts.sitesDir` is the absolute path of the directory Zoomies owns.
|
|
152
|
+
* Anything `*.conf` in there that is not a current site id is an orphan
|
|
153
|
+
* and will be deleted.
|
|
154
|
+
* - `opts.healthCheckUrl` is the post-reload smoke test target.
|
|
155
|
+
*
|
|
156
|
+
* Behaviour:
|
|
157
|
+
* - Pure validation failure -> no disk side effects.
|
|
158
|
+
* - Disk-apply failure -> rolled back to the pre-call state, no reload.
|
|
159
|
+
* - Reload or probe failure -> rolled back AND a second reload kicks NGINX
|
|
160
|
+
* back to the pre-call state. If that second reload also fails the
|
|
161
|
+
* result still reports the original step (reload/probe) — operator
|
|
162
|
+
* intervention is required either way.
|
|
163
|
+
*
|
|
164
|
+
* Returns an {@link ApplyResult}. Never throws on operational failures.
|
|
165
|
+
*/
|
|
166
|
+
export async function applyDesiredState(rendered, opts) {
|
|
167
|
+
const deps = resolveDeps(opts.deps);
|
|
168
|
+
const reloadArgs = opts.nginxReloadArgs ?? DEFAULT_RELOAD_ARGS;
|
|
169
|
+
// Step 1: validate. Pure check — no disk writes, no NGINX signals.
|
|
170
|
+
const candidate = joinRendered(rendered);
|
|
171
|
+
const validation = await deps.validate(candidate);
|
|
172
|
+
if (!validation.ok) {
|
|
173
|
+
return { ok: false, step: 'validate', validation };
|
|
174
|
+
}
|
|
175
|
+
// Step 2: diff disk against desired state.
|
|
176
|
+
const existing = await deps.listManagedFiles(opts.sitesDir);
|
|
177
|
+
const { toWrite, toDelete } = planDiff(rendered, opts.sitesDir, existing);
|
|
178
|
+
// Step 3: apply writes/deletes, accumulating rollback handles in order.
|
|
179
|
+
// Any single failure rolls back everything accumulated so far and aborts
|
|
180
|
+
// BEFORE we touch NGINX — disk is back where it started, no reload needed.
|
|
181
|
+
const rollbacks = [];
|
|
182
|
+
try {
|
|
183
|
+
for (const { path, contents } of toWrite) {
|
|
184
|
+
const handle = await deps.writeAtomic(path, contents);
|
|
185
|
+
rollbacks.push(handle);
|
|
186
|
+
}
|
|
187
|
+
for (const path of toDelete) {
|
|
188
|
+
const handle = await deps.deleteAtomic(path);
|
|
189
|
+
rollbacks.push(handle);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
await rollbackAll(rollbacks);
|
|
194
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
195
|
+
return { ok: false, step: 'write', message };
|
|
196
|
+
}
|
|
197
|
+
// Step 4: reload NGINX. If it refuses the new bundle, we need to roll the
|
|
198
|
+
// disk back AND tell NGINX to re-read the (now restored) config so its
|
|
199
|
+
// in-memory state matches what we just wrote.
|
|
200
|
+
const reloadResult = await deps.reload(getNginxBinary(), reloadArgs);
|
|
201
|
+
if (reloadResult.exitCode !== 0) {
|
|
202
|
+
await rollbackAll(rollbacks);
|
|
203
|
+
const secondReload = await deps.reload(getNginxBinary(), reloadArgs);
|
|
204
|
+
if (secondReload.exitCode !== 0) {
|
|
205
|
+
// Both reloads failed — log the post-rollback stderr so the operator
|
|
206
|
+
// has both ends of the story, but preserve the original failure as
|
|
207
|
+
// the reported step. The system is in a hand-fix state either way.
|
|
208
|
+
console.error('zoomies: post-rollback reload also failed (exitCode=%s): %s', secondReload.exitCode, secondReload.stderr);
|
|
209
|
+
}
|
|
210
|
+
return { ok: false, step: 'reload', message: reloadResult.stderr };
|
|
211
|
+
}
|
|
212
|
+
// Step 5: probe. The new config parsed and reloaded, but the upstream may
|
|
213
|
+
// still be unreachable from the freshly reloaded NGINX. A failed probe
|
|
214
|
+
// means the new bundle is bad in a way `nginx -t` cannot detect — roll
|
|
215
|
+
// back and re-reload to restore the prior working configuration.
|
|
216
|
+
const probeResult = await deps.probe({
|
|
217
|
+
url: opts.healthCheckUrl,
|
|
218
|
+
...opts.healthCheckOptions,
|
|
219
|
+
});
|
|
220
|
+
if (!probeResult.ok) {
|
|
221
|
+
await rollbackAll(rollbacks);
|
|
222
|
+
const secondReload = await deps.reload(getNginxBinary(), reloadArgs);
|
|
223
|
+
if (secondReload.exitCode !== 0) {
|
|
224
|
+
console.error('zoomies: post-rollback reload also failed (exitCode=%s): %s', secondReload.exitCode, secondReload.stderr);
|
|
225
|
+
}
|
|
226
|
+
return { ok: false, step: 'probe', probe: probeResult };
|
|
227
|
+
}
|
|
228
|
+
// Step 6: success. Rollback handles are deliberately not invoked — the
|
|
229
|
+
// change is now the committed state.
|
|
230
|
+
return { ok: true, step: 'success' };
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=reload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reload.js","sourceRoot":"","sources":["../../../src/server/reload/reload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Cert } from '../domain/cert.js';
|
|
2
|
+
import type { Site } from '../domain/site.js';
|
|
3
|
+
import type { Upstream } from '../domain/upstream.js';
|
|
4
|
+
/**
|
|
5
|
+
* Renders a complete bundle of sites into NGINX `server { ... }` snippets.
|
|
6
|
+
*
|
|
7
|
+
* Joins each {@link Site} to its referenced {@link Upstream} (by `upstreamId`)
|
|
8
|
+
* and to its matching {@link Cert} (by `cert.domain === site.hostname`). A
|
|
9
|
+
* missing upstream is a domain-invariant violation — every site must point
|
|
10
|
+
* at an upstream that exists — so we throw {@link NotFoundError} rather than
|
|
11
|
+
* silently degrading. A missing cert is fine; the renderer encodes "no cert
|
|
12
|
+
* yet" semantics per `tlsMode`.
|
|
13
|
+
*
|
|
14
|
+
* Pure function. No I/O. Returns a `Map` keyed by `site.id` to preserve the
|
|
15
|
+
* caller's iteration order and to make per-site lookups O(1) downstream.
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderBundle(sites: Site[], upstreams: Upstream[], certs: Cert[]): Map<string, string>;
|
|
18
|
+
//# sourceMappingURL=render-bundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-bundle.d.ts","sourceRoot":"","sources":["../../../src/server/renderer/render-bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,IAAI,EAAE,EACb,SAAS,EAAE,QAAQ,EAAE,EACrB,KAAK,EAAE,IAAI,EAAE,GACZ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBrB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NotFoundError } from '../domain/errors.js';
|
|
2
|
+
import { renderSite } from './render-site.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders a complete bundle of sites into NGINX `server { ... }` snippets.
|
|
5
|
+
*
|
|
6
|
+
* Joins each {@link Site} to its referenced {@link Upstream} (by `upstreamId`)
|
|
7
|
+
* and to its matching {@link Cert} (by `cert.domain === site.hostname`). A
|
|
8
|
+
* missing upstream is a domain-invariant violation — every site must point
|
|
9
|
+
* at an upstream that exists — so we throw {@link NotFoundError} rather than
|
|
10
|
+
* silently degrading. A missing cert is fine; the renderer encodes "no cert
|
|
11
|
+
* yet" semantics per `tlsMode`.
|
|
12
|
+
*
|
|
13
|
+
* Pure function. No I/O. Returns a `Map` keyed by `site.id` to preserve the
|
|
14
|
+
* caller's iteration order and to make per-site lookups O(1) downstream.
|
|
15
|
+
*/
|
|
16
|
+
export function renderBundle(sites, upstreams, certs) {
|
|
17
|
+
const upstreamsById = new Map(upstreams.map((u) => [u.id, u]));
|
|
18
|
+
// Certs are indexed by `domain` because the join key from a Site is its
|
|
19
|
+
// hostname, not a cert id. Stale cert rows (no matching site) are tolerated.
|
|
20
|
+
const certsByDomain = new Map(certs.map((c) => [c.domain, c]));
|
|
21
|
+
const rendered = new Map();
|
|
22
|
+
for (const site of sites) {
|
|
23
|
+
const upstream = upstreamsById.get(site.upstreamId);
|
|
24
|
+
if (upstream === undefined) {
|
|
25
|
+
throw new NotFoundError(`site ${site.id} references upstream ${site.upstreamId} which does not exist`);
|
|
26
|
+
}
|
|
27
|
+
const cert = certsByDomain.get(site.hostname) ?? null;
|
|
28
|
+
rendered.set(site.id, renderSite(site, upstream, cert));
|
|
29
|
+
}
|
|
30
|
+
return rendered;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=render-bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-bundle.js","sourceRoot":"","sources":["../../../src/server/renderer/render-bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAa,EACb,SAAqB,EACrB,KAAa;IAEb,MAAM,aAAa,GAAG,IAAI,GAAG,CAAmB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,wEAAwE;IACxE,6EAA6E;IAC7E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAe,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CACrB,QAAQ,IAAI,CAAC,EAAE,wBAAwB,IAAI,CAAC,UAAU,uBAAuB,CAC9E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;QACtD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Cert } from '../domain/cert.js';
|
|
2
|
+
import type { Site } from '../domain/site.js';
|
|
3
|
+
import type { Upstream } from '../domain/upstream.js';
|
|
4
|
+
export declare function renderSite(site: Site, upstream: Upstream, cert: Cert | null): string;
|
|
5
|
+
//# sourceMappingURL=render-site.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-site.d.ts","sourceRoot":"","sources":["../../../src/server/renderer/render-site.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,uBAAuB,CAAC;AAwJtE,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAMpF"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure NGINX config renderer for a single {@link Site}.
|
|
3
|
+
*
|
|
4
|
+
* Input: already-validated domain entities. Output: a deterministic, contiguous
|
|
5
|
+
* string ending with a single trailing newline. The renderer performs **no**
|
|
6
|
+
* I/O — no filesystem, no exec, no clocks. Two invocations with identical
|
|
7
|
+
* inputs MUST produce byte-identical output (the test suite enforces this via
|
|
8
|
+
* golden fixtures).
|
|
9
|
+
*
|
|
10
|
+
* NGINX validation (`nginx -t`) and reload orchestration live in later phases;
|
|
11
|
+
* this module only generates the text.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* NGINX `upstream` block identifiers must be valid identifiers — UUID hyphens
|
|
15
|
+
* break that. We sanitize once and reuse for both the upstream block name and
|
|
16
|
+
* the matching `proxy_pass` target inside the `server` block.
|
|
17
|
+
*/
|
|
18
|
+
function upstreamBlockName(site) {
|
|
19
|
+
return `zoomies_${site.id.replace(/-/g, '_')}`;
|
|
20
|
+
}
|
|
21
|
+
function renderLoadBalancerDirective(upstream) {
|
|
22
|
+
// Round-robin is the NGINX default — emitting it explicitly would be noise.
|
|
23
|
+
if (upstream.loadBalancer === 'round_robin')
|
|
24
|
+
return '';
|
|
25
|
+
if (upstream.loadBalancer === 'least_conn')
|
|
26
|
+
return ' least_conn;\n';
|
|
27
|
+
return ' ip_hash;\n';
|
|
28
|
+
}
|
|
29
|
+
function renderTarget(target) {
|
|
30
|
+
// Always emit `weight=` (even for weight=1) so snapshots stay stable when
|
|
31
|
+
// the schema default eventually changes.
|
|
32
|
+
return ` server ${target.host}:${target.port} weight=${target.weight};\n`;
|
|
33
|
+
}
|
|
34
|
+
function renderUpstreamBlock(site, upstream) {
|
|
35
|
+
const name = upstreamBlockName(site);
|
|
36
|
+
const lbDirective = renderLoadBalancerDirective(upstream);
|
|
37
|
+
const targetLines = upstream.targets.map(renderTarget).join('');
|
|
38
|
+
return `upstream ${name} {\n${lbDirective}${targetLines} keepalive 32;\n}\n`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `location /` block is identical across HTTP and HTTPS server blocks —
|
|
42
|
+
* extract it so both sides stay in sync.
|
|
43
|
+
*/
|
|
44
|
+
function renderProxyLocation(site) {
|
|
45
|
+
const name = upstreamBlockName(site);
|
|
46
|
+
return [
|
|
47
|
+
' location / {\n',
|
|
48
|
+
' proxy_http_version 1.1;\n',
|
|
49
|
+
' proxy_set_header Host $host;\n',
|
|
50
|
+
' proxy_set_header X-Real-IP $remote_addr;\n',
|
|
51
|
+
' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n',
|
|
52
|
+
' proxy_set_header X-Forwarded-Proto $scheme;\n',
|
|
53
|
+
' proxy_set_header Connection "";\n',
|
|
54
|
+
` proxy_pass http://${name};\n`,
|
|
55
|
+
' }\n',
|
|
56
|
+
].join('');
|
|
57
|
+
}
|
|
58
|
+
function renderHttpRedirectServer(site) {
|
|
59
|
+
return [
|
|
60
|
+
'server {\n',
|
|
61
|
+
' listen 80;\n',
|
|
62
|
+
' listen [::]:80;\n',
|
|
63
|
+
` server_name ${site.hostname};\n`,
|
|
64
|
+
'\n',
|
|
65
|
+
' return 301 https://$host$request_uri;\n',
|
|
66
|
+
'}\n',
|
|
67
|
+
].join('');
|
|
68
|
+
}
|
|
69
|
+
function renderHttpsServer(site, cert) {
|
|
70
|
+
return [
|
|
71
|
+
'server {\n',
|
|
72
|
+
' listen 443 ssl;\n',
|
|
73
|
+
' listen [::]:443 ssl;\n',
|
|
74
|
+
` server_name ${site.hostname};\n`,
|
|
75
|
+
` ssl_certificate ${cert.pemPath};\n`,
|
|
76
|
+
` ssl_certificate_key ${cert.keyPath};\n`,
|
|
77
|
+
'\n',
|
|
78
|
+
renderProxyLocation(site),
|
|
79
|
+
'}\n',
|
|
80
|
+
].join('');
|
|
81
|
+
}
|
|
82
|
+
function renderHttpServerOff(site) {
|
|
83
|
+
return [
|
|
84
|
+
'server {\n',
|
|
85
|
+
' listen 80;\n',
|
|
86
|
+
' listen [::]:80;\n',
|
|
87
|
+
` server_name ${site.hostname};\n`,
|
|
88
|
+
'\n',
|
|
89
|
+
renderProxyLocation(site),
|
|
90
|
+
'}\n',
|
|
91
|
+
].join('');
|
|
92
|
+
}
|
|
93
|
+
function renderHttpServerAcmePending(site) {
|
|
94
|
+
return [
|
|
95
|
+
'server {\n',
|
|
96
|
+
' listen 80;\n',
|
|
97
|
+
' listen [::]:80;\n',
|
|
98
|
+
` server_name ${site.hostname};\n`,
|
|
99
|
+
'\n',
|
|
100
|
+
' # acme: awaiting issuance\n',
|
|
101
|
+
' location /.well-known/acme-challenge/ {\n',
|
|
102
|
+
' root /var/lib/zoomies/acme;\n',
|
|
103
|
+
' }\n',
|
|
104
|
+
'\n',
|
|
105
|
+
renderProxyLocation(site),
|
|
106
|
+
'}\n',
|
|
107
|
+
].join('');
|
|
108
|
+
}
|
|
109
|
+
function renderHttpServerManualNoCert(site) {
|
|
110
|
+
return [
|
|
111
|
+
'server {\n',
|
|
112
|
+
' listen 80;\n',
|
|
113
|
+
' listen [::]:80;\n',
|
|
114
|
+
` server_name ${site.hostname};\n`,
|
|
115
|
+
'\n',
|
|
116
|
+
' # tls=manual: cert missing, refusing to render https block\n',
|
|
117
|
+
'\n',
|
|
118
|
+
renderProxyLocation(site),
|
|
119
|
+
'}\n',
|
|
120
|
+
].join('');
|
|
121
|
+
}
|
|
122
|
+
function renderServerBlocks(site, cert) {
|
|
123
|
+
if (site.tlsMode === 'off') {
|
|
124
|
+
return renderHttpServerOff(site);
|
|
125
|
+
}
|
|
126
|
+
if (site.tlsMode === 'acme') {
|
|
127
|
+
if (cert === null) {
|
|
128
|
+
return renderHttpServerAcmePending(site);
|
|
129
|
+
}
|
|
130
|
+
return `${renderHttpRedirectServer(site)}\n${renderHttpsServer(site, cert)}`;
|
|
131
|
+
}
|
|
132
|
+
// tlsMode === 'manual'
|
|
133
|
+
if (cert === null) {
|
|
134
|
+
return renderHttpServerManualNoCert(site);
|
|
135
|
+
}
|
|
136
|
+
return `${renderHttpRedirectServer(site)}\n${renderHttpsServer(site, cert)}`;
|
|
137
|
+
}
|
|
138
|
+
export function renderSite(site, upstream, cert) {
|
|
139
|
+
const header = `# managed-by zoomies — site:${site.id} upstream:${upstream.id}\n`;
|
|
140
|
+
const upstreamBlock = renderUpstreamBlock(site, upstream);
|
|
141
|
+
const serverBlocks = renderServerBlocks(site, cert);
|
|
142
|
+
return `${header}${upstreamBlock}\n${serverBlocks}`;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=render-site.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-site.js","sourceRoot":"","sources":["../../../src/server/renderer/render-site.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAAU;IACnC,OAAO,WAAW,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;AACjD,CAAC;AAED,SAAS,2BAA2B,CAAC,QAAkB;IACrD,4EAA4E;IAC5E,IAAI,QAAQ,CAAC,YAAY,KAAK,aAAa;QAAE,OAAO,EAAE,CAAC;IACvD,IAAI,QAAQ,CAAC,YAAY,KAAK,YAAY;QAAE,OAAO,mBAAmB,CAAC;IACvE,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CAAC,MAAsB;IAC1C,0EAA0E;IAC1E,yCAAyC;IACzC,OAAO,cAAc,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,WAAW,MAAM,CAAC,MAAM,KAAK,CAAC;AAC/E,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAU,EAAE,QAAkB;IACzD,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEhE,OAAO,YAAY,IAAI,OAAO,WAAW,GAAG,WAAW,wBAAwB,CAAC;AAClF,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,IAAU;IACrC,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO;QACL,oBAAoB;QACpB,mCAAmC;QACnC,wCAAwC;QACxC,oDAAoD;QACpD,wEAAwE;QACxE,uDAAuD;QACvD,2CAA2C;QAC3C,6BAA6B,IAAI,KAAK;QACtC,SAAS;KACV,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAU;IAC1C,OAAO;QACL,YAAY;QACZ,kBAAkB;QAClB,uBAAuB;QACvB,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACrC,IAAI;QACJ,6CAA6C;QAC7C,KAAK;KACN,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAU,EAAE,IAAU;IAC/C,OAAO;QACL,YAAY;QACZ,uBAAuB;QACvB,4BAA4B;QAC5B,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACrC,uBAAuB,IAAI,CAAC,OAAO,KAAK;QACxC,2BAA2B,IAAI,CAAC,OAAO,KAAK;QAC5C,IAAI;QACJ,mBAAmB,CAAC,IAAI,CAAC;QACzB,KAAK;KACN,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAU;IACrC,OAAO;QACL,YAAY;QACZ,kBAAkB;QAClB,uBAAuB;QACvB,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACrC,IAAI;QACJ,mBAAmB,CAAC,IAAI,CAAC;QACzB,KAAK;KACN,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,2BAA2B,CAAC,IAAU;IAC7C,OAAO;QACL,YAAY;QACZ,kBAAkB;QAClB,uBAAuB;QACvB,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACrC,IAAI;QACJ,iCAAiC;QACjC,+CAA+C;QAC/C,uCAAuC;QACvC,SAAS;QACT,IAAI;QACJ,mBAAmB,CAAC,IAAI,CAAC;QACzB,KAAK;KACN,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,4BAA4B,CAAC,IAAU;IAC9C,OAAO;QACL,YAAY;QACZ,kBAAkB;QAClB,uBAAuB;QACvB,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACrC,IAAI;QACJ,kEAAkE;QAClE,IAAI;QACJ,mBAAmB,CAAC,IAAI,CAAC;QACzB,KAAK;KACN,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAU,EAAE,IAAiB;IACvD,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC3B,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC5B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,OAAO,2BAA2B,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,KAAK,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;IAC/E,CAAC;IAED,uBAAuB;IACvB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,KAAK,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAU,EAAE,QAAkB,EAAE,IAAiB;IAC1E,MAAM,MAAM,GAAG,+BAA+B,IAAI,CAAC,EAAE,cAAc,QAAQ,CAAC,EAAE,IAAI,CAAC;IACnF,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,YAAY,GAAG,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAEpD,OAAO,GAAG,MAAM,GAAG,aAAa,KAAK,YAAY,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import { type Cert } from '../domain/cert.js';
|
|
3
|
+
export declare class CertRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
private readonly insertStmt;
|
|
6
|
+
private readonly selectByIdStmt;
|
|
7
|
+
private readonly selectByDomainStmt;
|
|
8
|
+
private readonly selectAllStmt;
|
|
9
|
+
private readonly updateStmt;
|
|
10
|
+
private readonly deleteStmt;
|
|
11
|
+
constructor(db: Database.Database);
|
|
12
|
+
create(input: Omit<Cert, 'id' | 'createdAt' | 'updatedAt'>): Cert;
|
|
13
|
+
findById(id: string): Cert | null;
|
|
14
|
+
findByDomain(domain: string): Cert | null;
|
|
15
|
+
list(): Cert[];
|
|
16
|
+
update(id: string, patch: Partial<Omit<Cert, 'id' | 'createdAt' | 'updatedAt'>>): Cert;
|
|
17
|
+
delete(id: string): boolean;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=cert-repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert-repository.d.ts","sourceRoot":"","sources":["../../../src/server/repositories/cert-repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AA0C1D,qBAAa,cAAc;IAQb,OAAO,CAAC,QAAQ,CAAC,EAAE;IAP/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAEC,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAqBlD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,IAAI;IAoCjE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKjC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKzC,IAAI,IAAI,IAAI,EAAE;IAId,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC,GAAG,IAAI;IA6CtF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CAG5B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { CertSchema } from '../domain/cert.js';
|
|
3
|
+
import { ConflictError, NotFoundError } from '../domain/errors.js';
|
|
4
|
+
function isUniqueConstraintError(err) {
|
|
5
|
+
return (typeof err === 'object' &&
|
|
6
|
+
err !== null &&
|
|
7
|
+
typeof err.code === 'string' &&
|
|
8
|
+
err.code.startsWith('SQLITE_CONSTRAINT_UNIQUE'));
|
|
9
|
+
}
|
|
10
|
+
function rowToCert(row) {
|
|
11
|
+
return CertSchema.parse({
|
|
12
|
+
id: row.id,
|
|
13
|
+
domain: row.domain,
|
|
14
|
+
provider: row.provider,
|
|
15
|
+
pemPath: row.pem_path,
|
|
16
|
+
keyPath: row.key_path,
|
|
17
|
+
notBefore: row.not_before,
|
|
18
|
+
notAfter: row.not_after,
|
|
19
|
+
createdAt: row.created_at,
|
|
20
|
+
updatedAt: row.updated_at,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export class CertRepository {
|
|
24
|
+
db;
|
|
25
|
+
insertStmt;
|
|
26
|
+
selectByIdStmt;
|
|
27
|
+
selectByDomainStmt;
|
|
28
|
+
selectAllStmt;
|
|
29
|
+
updateStmt;
|
|
30
|
+
deleteStmt;
|
|
31
|
+
constructor(db) {
|
|
32
|
+
this.db = db;
|
|
33
|
+
this.insertStmt = db.prepare('INSERT INTO certs (id, domain, provider, pem_path, key_path, not_before, not_after, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
|
34
|
+
this.selectByIdStmt = db.prepare('SELECT id, domain, provider, pem_path, key_path, not_before, not_after, created_at, updated_at FROM certs WHERE id = ?');
|
|
35
|
+
this.selectByDomainStmt = db.prepare('SELECT id, domain, provider, pem_path, key_path, not_before, not_after, created_at, updated_at FROM certs WHERE domain = ?');
|
|
36
|
+
this.selectAllStmt = db.prepare('SELECT id, domain, provider, pem_path, key_path, not_before, not_after, created_at, updated_at FROM certs ORDER BY domain ASC');
|
|
37
|
+
this.updateStmt = db.prepare('UPDATE certs SET domain = ?, provider = ?, pem_path = ?, key_path = ?, not_before = ?, not_after = ?, updated_at = ? WHERE id = ?');
|
|
38
|
+
this.deleteStmt = db.prepare('DELETE FROM certs WHERE id = ?');
|
|
39
|
+
}
|
|
40
|
+
create(input) {
|
|
41
|
+
const id = randomUUID();
|
|
42
|
+
const now = new Date().toISOString();
|
|
43
|
+
try {
|
|
44
|
+
this.insertStmt.run(id, input.domain, input.provider, input.pemPath, input.keyPath, input.notBefore, input.notAfter, now, now);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (isUniqueConstraintError(err)) {
|
|
48
|
+
throw new ConflictError(`domain already has a cert: ${input.domain}`, { cause: err });
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
return rowToCert({
|
|
53
|
+
id,
|
|
54
|
+
domain: input.domain,
|
|
55
|
+
provider: input.provider,
|
|
56
|
+
pem_path: input.pemPath,
|
|
57
|
+
key_path: input.keyPath,
|
|
58
|
+
not_before: input.notBefore,
|
|
59
|
+
not_after: input.notAfter,
|
|
60
|
+
created_at: now,
|
|
61
|
+
updated_at: now,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
findById(id) {
|
|
65
|
+
const row = this.selectByIdStmt.get(id);
|
|
66
|
+
return row ? rowToCert(row) : null;
|
|
67
|
+
}
|
|
68
|
+
findByDomain(domain) {
|
|
69
|
+
const row = this.selectByDomainStmt.get(domain);
|
|
70
|
+
return row ? rowToCert(row) : null;
|
|
71
|
+
}
|
|
72
|
+
list() {
|
|
73
|
+
return this.selectAllStmt.all().map(rowToCert);
|
|
74
|
+
}
|
|
75
|
+
update(id, patch) {
|
|
76
|
+
const existing = this.selectByIdStmt.get(id);
|
|
77
|
+
if (!existing) {
|
|
78
|
+
throw new NotFoundError(`cert not found: ${id}`);
|
|
79
|
+
}
|
|
80
|
+
const now = new Date().toISOString();
|
|
81
|
+
const nextDomain = patch.domain ?? existing.domain;
|
|
82
|
+
const nextProvider = patch.provider ?? existing.provider;
|
|
83
|
+
const nextPemPath = patch.pemPath ?? existing.pem_path;
|
|
84
|
+
const nextKeyPath = patch.keyPath ?? existing.key_path;
|
|
85
|
+
const nextNotBefore = patch.notBefore ?? existing.not_before;
|
|
86
|
+
const nextNotAfter = patch.notAfter ?? existing.not_after;
|
|
87
|
+
try {
|
|
88
|
+
this.updateStmt.run(nextDomain, nextProvider, nextPemPath, nextKeyPath, nextNotBefore, nextNotAfter, now, id);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (isUniqueConstraintError(err)) {
|
|
92
|
+
throw new ConflictError(`domain already has a cert: ${nextDomain}`, { cause: err });
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
return rowToCert({
|
|
97
|
+
id,
|
|
98
|
+
domain: nextDomain,
|
|
99
|
+
provider: nextProvider,
|
|
100
|
+
pem_path: nextPemPath,
|
|
101
|
+
key_path: nextKeyPath,
|
|
102
|
+
not_before: nextNotBefore,
|
|
103
|
+
not_after: nextNotAfter,
|
|
104
|
+
created_at: existing.created_at,
|
|
105
|
+
updated_at: now,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
delete(id) {
|
|
109
|
+
return this.deleteStmt.run(id).changes > 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=cert-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert-repository.js","sourceRoot":"","sources":["../../../src/server/repositories/cert-repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import { type Site } from '../domain/site.js';
|
|
3
|
+
type SiteInsert = Omit<Site, 'id' | 'createdAt' | 'updatedAt'>;
|
|
4
|
+
type SiteUpdate = Partial<Omit<Site, 'id' | 'createdAt' | 'updatedAt'>>;
|
|
5
|
+
export declare class SiteRepository {
|
|
6
|
+
#private;
|
|
7
|
+
private readonly db;
|
|
8
|
+
constructor(db: Database.Database);
|
|
9
|
+
create(input: SiteInsert): Site;
|
|
10
|
+
findById(id: string): Site | null;
|
|
11
|
+
findByHostname(hostname: string): Site | null;
|
|
12
|
+
list(): Site[];
|
|
13
|
+
update(id: string, patch: SiteUpdate): Site;
|
|
14
|
+
delete(id: string): boolean;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=site-repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-repository.d.ts","sourceRoot":"","sources":["../../../src/server/repositories/site-repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAkC1D,KAAK,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;AAC/D,KAAK,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC;AAUxE,qBAAa,cAAc;;IAQb,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAWlD,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAqB/B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKjC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAK7C,IAAI,IAAI,IAAI,EAAE;IAKd,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IA8B3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CA8B5B"}
|