@songsid/agend 2.1.6-beta.1 → 2.1.6-beta.3
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/dist/apply-job.d.ts +114 -0
- package/dist/apply-job.js +214 -0
- package/dist/apply-job.js.map +1 -0
- package/dist/backend/codex.d.ts +24 -0
- package/dist/backend/codex.js +102 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +159 -0
- package/dist/backend/credential-profile.js +340 -0
- package/dist/backend/credential-profile.js.map +1 -0
- package/dist/backend/kiro-auth-store.d.ts +25 -0
- package/dist/backend/kiro-auth-store.js +61 -0
- package/dist/backend/kiro-auth-store.js.map +1 -0
- package/dist/backend/kiro.d.ts +8 -0
- package/dist/backend/kiro.js +31 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +21 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +10 -0
- package/dist/daemon.js +27 -1
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-level-config.d.ts +42 -0
- package/dist/fleet-level-config.js +78 -0
- package/dist/fleet-level-config.js.map +1 -0
- package/dist/fleet-lock.d.ts +21 -0
- package/dist/fleet-lock.js +42 -8
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +122 -1
- package/dist/fleet-manager.js +537 -83
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +145 -0
- package/dist/instance-config-impact.d.ts +55 -0
- package/dist/instance-config-impact.js +152 -0
- package/dist/instance-config-impact.js.map +1 -0
- package/dist/locale.js +2 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.d.ts +5 -1
- package/dist/outbound-handlers.js +199 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +6 -5
- package/dist/outbound-schemas.js +1 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +45 -0
- package/dist/provider-probe.js +111 -0
- package/dist/provider-probe.js.map +1 -0
- package/dist/quickstart-api.d.ts +161 -0
- package/dist/quickstart-api.js +340 -0
- package/dist/quickstart-api.js.map +1 -0
- package/dist/quickstart.js +23 -50
- package/dist/quickstart.js.map +1 -1
- package/dist/self-restart-limit.d.ts +21 -0
- package/dist/self-restart-limit.js +129 -0
- package/dist/self-restart-limit.js.map +1 -0
- package/dist/settings-api.d.ts +15 -0
- package/dist/settings-api.js +126 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-auth.d.ts +113 -0
- package/dist/setup-auth.js +181 -0
- package/dist/setup-auth.js.map +1 -0
- package/dist/setup-form.d.ts +18 -0
- package/dist/setup-form.js +315 -0
- package/dist/setup-form.js.map +1 -0
- package/dist/setup-host.d.ts +162 -0
- package/dist/setup-host.js +496 -0
- package/dist/setup-host.js.map +1 -0
- package/dist/setup-marker.d.ts +8 -0
- package/dist/setup-marker.js +39 -0
- package/dist/setup-marker.js.map +1 -0
- package/dist/setup-tunnel-consent.d.ts +53 -0
- package/dist/setup-tunnel-consent.js +82 -0
- package/dist/setup-tunnel-consent.js.map +1 -0
- package/dist/tips.js +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/transcript-sources.d.ts +12 -1
- package/dist/transcript-sources.js +19 -3
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +71 -0
- package/dist/tunnel/cloudflared.js +447 -0
- package/dist/tunnel/cloudflared.js.map +1 -0
- package/dist/tunnel/lease.d.ts +75 -0
- package/dist/tunnel/lease.js +209 -0
- package/dist/tunnel/lease.js.map +1 -0
- package/dist/tunnel/manager.d.ts +56 -0
- package/dist/tunnel/manager.js +167 -0
- package/dist/tunnel/manager.js.map +1 -0
- package/dist/tunnel/types.d.ts +119 -0
- package/dist/tunnel/types.js +33 -0
- package/dist/tunnel/types.js.map +1 -0
- package/dist/ui/dashboard.html +4 -3
- package/dist/ui/settings.html +640 -153
- package/dist/usage/providers.d.ts +17 -14
- package/dist/usage/providers.js +87 -55
- package/dist/usage/providers.js.map +1 -1
- package/dist/web-api.js +6 -4
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +75 -0
- package/dist/web-auth.js +208 -2
- package/dist/web-auth.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The form that runs before there is a fleet.
|
|
3
|
+
*
|
|
4
|
+
* This process exists only to collect what `agend quickstart` collects, write
|
|
5
|
+
* it, and hand the port to the real fleet. Three properties matter more than
|
|
6
|
+
* anything it does:
|
|
7
|
+
*
|
|
8
|
+
* - **It cannot reach the fleet.** Its imports stop at the config and the
|
|
9
|
+
* provider probes; `tests/prefleet-host.test.ts` asserts the graph never
|
|
10
|
+
* reaches fleet-manager, daemon or instance-lifecycle. A pre-fleet surface
|
|
11
|
+
* that could start or stop agents would be a much larger thing to defend.
|
|
12
|
+
* - **It lets go of the port before the fleet is started.** The fleet's
|
|
13
|
+
* health-port takeover is not a safety net: on a busy port it signals
|
|
14
|
+
* whatever fleet.pid names and then disables the dashboard for the rest of
|
|
15
|
+
* its life. So the listener is closed — including its open connections —
|
|
16
|
+
* and only then is the fleet spawned.
|
|
17
|
+
* - **It goes away on its own.** A setup form left open is an unauthenticated-
|
|
18
|
+
* by-default surface waiting for someone to find it, so it exits on its TTL
|
|
19
|
+
* and on idleness as readily as on success.
|
|
20
|
+
*
|
|
21
|
+
* Hardened ahead of being reachable from outside (see
|
|
22
|
+
* `docs/design/setup-host-tunnel.zh-TW.md`): the page lives under a random sid
|
|
23
|
+
* so a scanner cannot find it, the credential is a short code typed into the
|
|
24
|
+
* page rather than anything in the URL, the cookie is derived from a freshly
|
|
25
|
+
* minted 256-bit secret rather than from that code, and every presented-and-
|
|
26
|
+
* wrong credential spends one of five shared attempts.
|
|
27
|
+
*/
|
|
28
|
+
import { createServer } from "node:http";
|
|
29
|
+
import { spawn } from "node:child_process";
|
|
30
|
+
import { existsSync } from "node:fs";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { loadFleetConfig } from "./config.js";
|
|
34
|
+
import { acquireFleetLock, releaseFleetLock } from "./fleet-lock.js";
|
|
35
|
+
import { handleQuickstartRequest } from "./quickstart-api.js";
|
|
36
|
+
import { markSetupComplete } from "./setup-marker.js";
|
|
37
|
+
import { isSameOriginRequest, parseCookieHeader } from "./web-auth.js";
|
|
38
|
+
import { buildSetupCookie, SetupCredentials, SETUP_COOKIE_NAME, } from "./setup-auth.js";
|
|
39
|
+
import { SETUP_CODE_PAGE_HTML, SETUP_FORM_HTML } from "./setup-form.js";
|
|
40
|
+
import { ManagedTunnel } from "./tunnel/manager.js";
|
|
41
|
+
import { leasePath } from "./tunnel/lease.js";
|
|
42
|
+
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
43
|
+
import yaml from "js-yaml";
|
|
44
|
+
import { writeFileSync } from "node:fs";
|
|
45
|
+
/** How long the form may stay open at all. */
|
|
46
|
+
export const SETUP_HOST_TTL_MS = 15 * 60_000;
|
|
47
|
+
/**
|
|
48
|
+
* …and how long it may stay open with nobody touching it.
|
|
49
|
+
*
|
|
50
|
+
* Strictly shorter than the TTL, which it was not: both were fifteen minutes,
|
|
51
|
+
* so the idle timer could never fire first and the page always stayed open for
|
|
52
|
+
* the full quarter hour whether anyone was using it or not.
|
|
53
|
+
*/
|
|
54
|
+
export const SETUP_HOST_IDLE_MS = 10 * 60_000;
|
|
55
|
+
/**
|
|
56
|
+
* The port a setup host may bind.
|
|
57
|
+
*
|
|
58
|
+
* In tunnel mode it is always zero. Not "unless `--port` was given", and not
|
|
59
|
+
* "unless fleet.yaml names a health port" — those are the two ways a fixed
|
|
60
|
+
* port creeps back behind a tunnel, and the second one needs no flag at all.
|
|
61
|
+
* Whatever the fleet would later bind must never be the thing the tunnel is
|
|
62
|
+
* pointed at.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePort(opts) {
|
|
65
|
+
if (opts.tunnel)
|
|
66
|
+
return 0;
|
|
67
|
+
return opts.port ?? opts.healthPort ?? 19280;
|
|
68
|
+
}
|
|
69
|
+
function defaultSpawnFleet() {
|
|
70
|
+
const cliEntry = join(dirname(fileURLToPath(import.meta.url)), "cli.js");
|
|
71
|
+
const child = spawn(process.execPath, [cliEntry, "start"], { detached: true, stdio: "ignore" });
|
|
72
|
+
child.unref();
|
|
73
|
+
}
|
|
74
|
+
export class SetupHost {
|
|
75
|
+
opts;
|
|
76
|
+
server = null;
|
|
77
|
+
lock;
|
|
78
|
+
ttlTimer = null;
|
|
79
|
+
idleTimer = null;
|
|
80
|
+
stopping = false;
|
|
81
|
+
credentials;
|
|
82
|
+
/** Where this page lives. Random, and not a credential — see setup-auth.ts. */
|
|
83
|
+
sid;
|
|
84
|
+
/** What the person types. Printed by the CLI; never put in a URL. */
|
|
85
|
+
code;
|
|
86
|
+
constructor(opts) {
|
|
87
|
+
this.opts = opts;
|
|
88
|
+
this.credentials = opts.credentials ?? new SetupCredentials();
|
|
89
|
+
this.sid = this.credentials.sid;
|
|
90
|
+
this.code = this.credentials.code;
|
|
91
|
+
}
|
|
92
|
+
/** The page's own path. Everything outside it is not this page. */
|
|
93
|
+
get basePath() { return `/s/${this.sid}/`; }
|
|
94
|
+
managedTunnel = null;
|
|
95
|
+
tunnelHandle = null;
|
|
96
|
+
/** The exact host the tunnel answers on; nothing else is added to the set. */
|
|
97
|
+
externalHost = null;
|
|
98
|
+
tunnelStopResult = "none";
|
|
99
|
+
/**
|
|
100
|
+
* Set when the wizard asked to hand over, so a shutdown that started for
|
|
101
|
+
* another reason still completes it.
|
|
102
|
+
*
|
|
103
|
+
* Without this the tunnel dying in the moment between "finish" answering and
|
|
104
|
+
* the handover beginning would take the `stopping` flag first, and the
|
|
105
|
+
* handover would silently return: configuration written, page saying
|
|
106
|
+
* "starting", and no fleet.
|
|
107
|
+
*/
|
|
108
|
+
handoverRequested = false;
|
|
109
|
+
get ttlMs() { return this.opts.ttlMs ?? SETUP_HOST_TTL_MS; }
|
|
110
|
+
get idleMs() { return this.opts.idleMs ?? SETUP_HOST_IDLE_MS; }
|
|
111
|
+
log(message) { (this.opts.log ?? console.log)(message); }
|
|
112
|
+
/** The config the wizard edits: a file if there is one, otherwise a blank. */
|
|
113
|
+
readConfig() {
|
|
114
|
+
try {
|
|
115
|
+
return loadFleetConfig(this.opts.configPath);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { defaults: {}, instances: {} };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
context() {
|
|
122
|
+
const config = this.readConfig();
|
|
123
|
+
return {
|
|
124
|
+
fleetConfig: config,
|
|
125
|
+
dataDir: this.opts.dataDir,
|
|
126
|
+
logger: { info: () => { }, warn: () => { } },
|
|
127
|
+
// Nothing to preserve: before a fleet there are no comments and no hand
|
|
128
|
+
// edits to keep, so a plain dump is the whole writer.
|
|
129
|
+
saveFleetConfig: () => {
|
|
130
|
+
writeFileSync(this.opts.configPath, yaml.dump(config, { quotingType: '"', forceQuotes: false }), { mode: 0o600 });
|
|
131
|
+
},
|
|
132
|
+
// No fleet, so nothing is polling any bot token.
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async start() {
|
|
136
|
+
// This host writes fleet.yaml by dumping the loader's output, which expands
|
|
137
|
+
// every default and drops every comment. That is fine for a file it is
|
|
138
|
+
// creating and destructive to one somebody already has — and an existing
|
|
139
|
+
// installation should be using the panel's wizard anyway, which edits the
|
|
140
|
+
// document in place.
|
|
141
|
+
const existing = this.readConfig();
|
|
142
|
+
if (Object.keys(existing.instances ?? {}).length > 0) {
|
|
143
|
+
throw new Error("This installation already has agents configured. Open Settings in the dashboard and use the setup wizard there — it edits your fleet.yaml in place.");
|
|
144
|
+
}
|
|
145
|
+
// Refuses while a fleet runs, and — since the record carries a role — is
|
|
146
|
+
// equally refused to a fleet that starts while this host is up.
|
|
147
|
+
this.lock = acquireFleetLock(this.opts.dataDir, { role: "setup-host", ...this.opts.lockProbe });
|
|
148
|
+
const server = createServer((req, res) => this.handle(req, res));
|
|
149
|
+
this.server = server;
|
|
150
|
+
// Zero in tunnel mode, and the caller cannot override it: `resolvePort` is
|
|
151
|
+
// the only thing that decides, so a flag or a config value cannot put a
|
|
152
|
+
// fixed port behind a tunnel.
|
|
153
|
+
const bindPort = this.opts.tunnel ? 0 : this.opts.port;
|
|
154
|
+
await new Promise((resolve, reject) => {
|
|
155
|
+
server.once("error", reject);
|
|
156
|
+
server.listen(bindPort, "127.0.0.1", () => { server.removeListener("error", reject); resolve(); });
|
|
157
|
+
});
|
|
158
|
+
this.ttlTimer = setTimeout(() => { void this.shutdown(false, "ttl"); }, this.ttlMs);
|
|
159
|
+
this.ttlTimer.unref?.();
|
|
160
|
+
this.boundPort = server.address().port;
|
|
161
|
+
this.touch();
|
|
162
|
+
const publicUrl = this.opts.tunnel ? await this.openTunnel() : null;
|
|
163
|
+
return { port: this.boundPort, sid: this.sid, code: this.code, path: this.basePath, publicUrl };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Put the page behind a tunnel, or say why it is not.
|
|
167
|
+
*
|
|
168
|
+
* Three outcomes, and the third is the one that matters: a start that failed
|
|
169
|
+
* while leaving a process it could not account for is not a fallback, it is a
|
|
170
|
+
* stop. Carrying on would mean serving this page on loopback while something
|
|
171
|
+
* we cannot see may still be exposing it.
|
|
172
|
+
*/
|
|
173
|
+
async openTunnel() {
|
|
174
|
+
const managed = new ManagedTunnel({ dataDir: this.opts.dataDir, log: m => this.log(m) });
|
|
175
|
+
this.managedTunnel = managed;
|
|
176
|
+
const provider = this.opts.tunnelProvider ?? new CloudflaredProvider();
|
|
177
|
+
const result = await managed.start(provider, {
|
|
178
|
+
sid: this.sid,
|
|
179
|
+
origin: new URL(`http://127.0.0.1:${this.boundPort}`),
|
|
180
|
+
pagePath: this.basePath,
|
|
181
|
+
readinessMarker: this.credentials.readinessMarker,
|
|
182
|
+
expiresAt: (this.opts.now ?? Date.now)() + this.ttlMs,
|
|
183
|
+
signal: new AbortController().signal,
|
|
184
|
+
// The provider's own readiness probe arrives here under the public
|
|
185
|
+
// hostname, so it has to be on the list before that probe runs. Exactly
|
|
186
|
+
// the one host the validator just accepted — the alternative shortcuts,
|
|
187
|
+
// accepting any Host during startup or any `*.trycloudflare.com`, are
|
|
188
|
+
// each a hole that outlives the ten seconds they would save.
|
|
189
|
+
onCandidateHost: host => { this.externalHost = host; },
|
|
190
|
+
});
|
|
191
|
+
if (result.ok) {
|
|
192
|
+
this.tunnelHandle = result.handle;
|
|
193
|
+
this.externalHost = new URL(result.handle.baseUrl).host;
|
|
194
|
+
// The page is gone the moment the tunnel is, and a session whose
|
|
195
|
+
// hostname has changed underneath it is not a session to keep.
|
|
196
|
+
result.handle.onUnexpectedExit(() => {
|
|
197
|
+
this.log("The tunnel closed unexpectedly. Run `agend setup --tunnel` again for a new link.");
|
|
198
|
+
void this.shutdown(false, "tunnel died");
|
|
199
|
+
});
|
|
200
|
+
return result.handle.pageUrl;
|
|
201
|
+
}
|
|
202
|
+
// Whatever happens below, a host that never became a working tunnel does
|
|
203
|
+
// not stay on the list.
|
|
204
|
+
if (!result.ok)
|
|
205
|
+
this.externalHost = null;
|
|
206
|
+
if (result.leaseHeld) {
|
|
207
|
+
// Fail closed: a tunnel process we cannot account for may still be
|
|
208
|
+
// pointed at this listener, so there is no honest "falling back to
|
|
209
|
+
// localhost" to announce.
|
|
210
|
+
await this.shutdown(false, "tunnel could not be cleaned up");
|
|
211
|
+
throw new Error(`Could not open a tunnel, and could not confirm it was cleaned up: ${result.message}`);
|
|
212
|
+
}
|
|
213
|
+
this.log(`No public tunnel: ${result.message}`);
|
|
214
|
+
this.log("The link below only works on this machine — a phone will not be able to open it.");
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
boundPort = 0;
|
|
218
|
+
/**
|
|
219
|
+
* The hosts this listener will answer to.
|
|
220
|
+
*
|
|
221
|
+
* Origin==Host alone accepts any Host at all, which was harmless while the
|
|
222
|
+
* only way in was loopback. It stops being harmless the moment something in
|
|
223
|
+
* front of the listener can set it, so the set is named rather than inferred.
|
|
224
|
+
*/
|
|
225
|
+
allowedHosts() {
|
|
226
|
+
const hosts = [`127.0.0.1:${this.boundPort}`, `localhost:${this.boundPort}`, `[::1]:${this.boundPort}`];
|
|
227
|
+
// Exactly the host this tunnel answers on, and only while it is ours. An
|
|
228
|
+
// ephemeral port can be handed to a later listener, and a tunnel nobody
|
|
229
|
+
// cleaned up would then be pointed at that one — this list is what makes
|
|
230
|
+
// the zombie's requests 403 instead of reaching a stranger's page.
|
|
231
|
+
if (this.externalHost)
|
|
232
|
+
hosts.push(this.externalHost);
|
|
233
|
+
return hosts;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Whether the browser reached this over TLS.
|
|
237
|
+
*
|
|
238
|
+
* Derived from what this process knows it is serving, never from
|
|
239
|
+
* `X-Forwarded-Proto`: that header is set by whatever is in front, including
|
|
240
|
+
* an attacker, and a cookie's Secure attribute is not something to take on
|
|
241
|
+
* trust from a request.
|
|
242
|
+
*/
|
|
243
|
+
get endpointIsHttps() { return this.externalHost !== null; }
|
|
244
|
+
touch() {
|
|
245
|
+
if (this.idleTimer)
|
|
246
|
+
clearTimeout(this.idleTimer);
|
|
247
|
+
this.idleTimer = setTimeout(() => { void this.shutdown(false, "idle"); }, this.idleMs);
|
|
248
|
+
this.idleTimer.unref?.();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The answer for anything that is not this page.
|
|
252
|
+
*
|
|
253
|
+
* Byte-identical whatever the reason, because the difference between "wrong
|
|
254
|
+
* sid" and "no such path" is precisely how a sid gets found.
|
|
255
|
+
*/
|
|
256
|
+
notFound(res) {
|
|
257
|
+
res.setHeader("Content-Type", "application/json");
|
|
258
|
+
res.writeHead(404);
|
|
259
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
260
|
+
}
|
|
261
|
+
deny(res, status, error) {
|
|
262
|
+
res.setHeader("Content-Type", "application/json");
|
|
263
|
+
res.writeHead(status);
|
|
264
|
+
res.end(JSON.stringify({ error }));
|
|
265
|
+
}
|
|
266
|
+
hostAllowed(req) {
|
|
267
|
+
const host = req.headers.host;
|
|
268
|
+
return typeof host === "string" && this.allowedHosts().includes(host);
|
|
269
|
+
}
|
|
270
|
+
presentedCookie(req) {
|
|
271
|
+
return parseCookieHeader(req.headers.cookie).get(SETUP_COOKIE_NAME);
|
|
272
|
+
}
|
|
273
|
+
/** Spend an attempt's worth of consequence: at zero the page is over. */
|
|
274
|
+
afterFailure(res, kind, attemptsLeft) {
|
|
275
|
+
if (kind === "locked") {
|
|
276
|
+
this.deny(res, 410, "too many failed attempts — run `agend setup` again on the host");
|
|
277
|
+
setTimeout(() => { void this.shutdown(false, "lockout"); }, 10);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
this.deny(res, 401, `incorrect — ${attemptsLeft} attempt${attemptsLeft === 1 ? "" : "s"} left`);
|
|
281
|
+
}
|
|
282
|
+
handle(req, res) {
|
|
283
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
284
|
+
res.setHeader("Vary", "Cookie");
|
|
285
|
+
// On every response, not just the redirect that used to carry it: a shared
|
|
286
|
+
// cache in front of this — which is what a tunnel edge is — must never keep
|
|
287
|
+
// a copy of a page or an answer belonging to one setup session.
|
|
288
|
+
res.setHeader("Cache-Control", "no-store");
|
|
289
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${this.boundPort}`);
|
|
290
|
+
// Everything below the sid, and nothing else. Checked before the idle timer
|
|
291
|
+
// is touched, or a scanner that cannot find the page could still keep it
|
|
292
|
+
// open forever by knocking on the wrong paths.
|
|
293
|
+
const segments = url.pathname.split("/");
|
|
294
|
+
if (segments[1] !== "s" || !this.credentials.matchesSid(segments[2] ?? "")) {
|
|
295
|
+
this.notFound(res);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const rest = `/${segments.slice(3).join("/")}`;
|
|
299
|
+
if (!isSameOriginRequest(req) || !this.hostAllowed(req)) {
|
|
300
|
+
this.deny(res, 403, "cross-site request rejected");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// The two unauthenticated endpoints: the page that asks for the code, and
|
|
304
|
+
// the exchange itself. Everything else needs the cookie.
|
|
305
|
+
if (req.method === "GET" && (rest === "/" || rest === "")) {
|
|
306
|
+
const signedIn = this.credentials.checkCookie(this.presentedCookie(req)).kind === "ok";
|
|
307
|
+
// Only a session that exists keeps the page alive. Reloading the code
|
|
308
|
+
// prompt is not use of the page, and treating it as such would let
|
|
309
|
+
// anyone holding the link hold the window open indefinitely.
|
|
310
|
+
if (signedIn)
|
|
311
|
+
this.touch();
|
|
312
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
313
|
+
res.writeHead(200);
|
|
314
|
+
// The wizard itself is only served to a session that has the cookie; an
|
|
315
|
+
// unauthenticated visitor gets the code prompt and nothing about this
|
|
316
|
+
// machine.
|
|
317
|
+
res.end(signedIn
|
|
318
|
+
? SETUP_FORM_HTML
|
|
319
|
+
: SETUP_CODE_PAGE_HTML.replace("__AGEND_SETUP_MARKER__", this.credentials.readinessMarker));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (req.method === "POST" && rest === "/open") {
|
|
323
|
+
this.redeem(req, res);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const verdict = this.credentials.checkCookie(this.presentedCookie(req));
|
|
327
|
+
if (verdict.kind === "locked") {
|
|
328
|
+
this.afterFailure(res, "locked", 0);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (verdict.kind === "rejected") {
|
|
332
|
+
this.afterFailure(res, "rejected", verdict.attemptsLeft);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (verdict.kind === "unauthenticated") {
|
|
336
|
+
this.deny(res, 401, "enter the setup code first");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
// Only now: an authorized request is the only kind that should be able to
|
|
340
|
+
// keep this page alive.
|
|
341
|
+
this.touch();
|
|
342
|
+
if (req.method === "GET" && rest === "/setup/status") {
|
|
343
|
+
res.setHeader("Content-Type", "application/json");
|
|
344
|
+
res.writeHead(200);
|
|
345
|
+
res.end(JSON.stringify({ pre_fleet: true, ttl_ms: this.ttlMs }));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (req.method === "POST" && rest === "/setup/finish") {
|
|
349
|
+
res.setHeader("Content-Type", "application/json");
|
|
350
|
+
res.writeHead(202);
|
|
351
|
+
// `watch` is false behind a tunnel: this page is about to stop existing
|
|
352
|
+
// along with the hostname it is served on, so polling would only produce
|
|
353
|
+
// a false alarm. The dashboard is not an answer either — it binds
|
|
354
|
+
// loopback, so a link to it would be one a phone cannot open.
|
|
355
|
+
res.end(JSON.stringify({ starting: true, watch: this.externalHost === null }));
|
|
356
|
+
// Recorded before the timer, not inside it: the tunnel can die in
|
|
357
|
+
// between, and whichever shutdown runs first has to know a handover was
|
|
358
|
+
// asked for.
|
|
359
|
+
this.handoverRequested = true;
|
|
360
|
+
// After the response, so the browser has its answer before the port goes.
|
|
361
|
+
setTimeout(() => { void this.shutdown(true, "finished"); }, 10);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// The wizard API is mounted under the sid too, so its paths are rewritten
|
|
365
|
+
// rather than exposed at the root where a scanner could reach them.
|
|
366
|
+
const inner = new URL(rest + url.search, "http://127.0.0.1");
|
|
367
|
+
if (handleQuickstartRequest(req, res, inner, this.context()))
|
|
368
|
+
return;
|
|
369
|
+
this.notFound(res);
|
|
370
|
+
}
|
|
371
|
+
/** Exchange the typed code for a session cookie. The only way in. */
|
|
372
|
+
redeem(req, res) {
|
|
373
|
+
let body = "";
|
|
374
|
+
req.on("data", chunk => {
|
|
375
|
+
body += String(chunk);
|
|
376
|
+
if (body.length > 4096) {
|
|
377
|
+
req.destroy();
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
req.on("end", () => {
|
|
381
|
+
let provided = "";
|
|
382
|
+
try {
|
|
383
|
+
provided = String(JSON.parse(body || "{}").code ?? "");
|
|
384
|
+
}
|
|
385
|
+
catch { /* empty */ }
|
|
386
|
+
const verdict = this.credentials.redeem(provided);
|
|
387
|
+
if (verdict.kind === "ok" && verdict.secret) {
|
|
388
|
+
this.touch();
|
|
389
|
+
res.setHeader("Set-Cookie", buildSetupCookie(verdict.secret, {
|
|
390
|
+
path: this.basePath,
|
|
391
|
+
secure: this.endpointIsHttps,
|
|
392
|
+
maxAgeSeconds: Math.ceil(this.ttlMs / 1000),
|
|
393
|
+
}));
|
|
394
|
+
res.setHeader("Content-Type", "application/json");
|
|
395
|
+
res.writeHead(200);
|
|
396
|
+
res.end(JSON.stringify({ ok: true }));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (verdict.kind === "locked") {
|
|
400
|
+
this.afterFailure(res, "locked", 0);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (verdict.kind === "rejected") {
|
|
404
|
+
this.afterFailure(res, "rejected", verdict.attemptsLeft);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this.deny(res, 400, "enter the setup code");
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Let go of the port, then hand it over.
|
|
412
|
+
*
|
|
413
|
+
* `closeAllConnections()` first, and not as a nicety: a browser polling for
|
|
414
|
+
* the fleet holds a connection open, and `close()` waits for it. Measured, a
|
|
415
|
+
* single open poll stops the close from ever completing — so the form would
|
|
416
|
+
* hang at "starting…" and the fleet would never be spawned.
|
|
417
|
+
*/
|
|
418
|
+
async shutdown(spawnSuccessor, reason) {
|
|
419
|
+
if (this.stopping)
|
|
420
|
+
return;
|
|
421
|
+
this.stopping = true;
|
|
422
|
+
// A handover asked for by the wizard survives a shutdown that began for a
|
|
423
|
+
// different reason.
|
|
424
|
+
const handOver = spawnSuccessor || this.handoverRequested;
|
|
425
|
+
if (this.ttlTimer)
|
|
426
|
+
clearTimeout(this.ttlTimer);
|
|
427
|
+
if (this.idleTimer)
|
|
428
|
+
clearTimeout(this.idleTimer);
|
|
429
|
+
// 1. Capability first. Anything still in flight — including a request
|
|
430
|
+
// arriving through a tunnel that is still up — is already unauthorized
|
|
431
|
+
// from here, so the order below can take its time without a window.
|
|
432
|
+
this.credentials.revoke();
|
|
433
|
+
// 2. The listener, and its open connections. From this point the public URL
|
|
434
|
+
// reaches nothing, whatever the tunnel is doing.
|
|
435
|
+
const server = this.server;
|
|
436
|
+
this.server = null;
|
|
437
|
+
if (server) {
|
|
438
|
+
server.closeAllConnections?.();
|
|
439
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
440
|
+
}
|
|
441
|
+
// 3. The tunnel, with proof.
|
|
442
|
+
if (this.managedTunnel && this.tunnelHandle) {
|
|
443
|
+
const stopped = await this.managedTunnel.stop(reason);
|
|
444
|
+
this.tunnelStopResult = stopped.confirmed ? "confirmed" : "unconfirmed";
|
|
445
|
+
if (!stopped.confirmed) {
|
|
446
|
+
this.log(`The tunnel could not be confirmed closed${stopped.pid !== null ? ` (pid ${stopped.pid})` : ""}: ${stopped.reason}. `
|
|
447
|
+
+ "That URL may keep answering 'origin unavailable' for a while; nothing behind it is reachable. "
|
|
448
|
+
+ `To clean up by hand, kill ${stopped.pid !== null ? `pid ${stopped.pid}` : "the cloudflared process"}. `
|
|
449
|
+
+ "No new tunnel will be opened until this is resolved.");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
releaseFleetLock(this.lock);
|
|
453
|
+
this.lock = undefined;
|
|
454
|
+
if (!handOver) {
|
|
455
|
+
this.log(`Setup host stopped (${reason}). Run \`agend setup\` again to reopen it.`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (this.tunnelStopResult === "unconfirmed" && !this.mayHandOverWithUnconfirmedTunnel()) {
|
|
459
|
+
// Fail closed. The config is written; what is missing is a process this
|
|
460
|
+
// one is not willing to start while a tunnel it cannot account for could
|
|
461
|
+
// still be pointed at the port that process would bind.
|
|
462
|
+
this.log("Your configuration has been saved, but AgEnD was NOT started: a tunnel could not be confirmed closed "
|
|
463
|
+
+ "and this host is not on an ephemeral port, so starting the fleet could put its dashboard behind that tunnel. "
|
|
464
|
+
+ "Kill the cloudflared process, then run `agend start` on this machine.");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
markSetupComplete(this.opts.dataDir);
|
|
468
|
+
this.log("Setup complete — starting AgEnD.");
|
|
469
|
+
(this.opts.spawnFleet ?? defaultSpawnFleet)();
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Whether an unconfirmed tunnel may be left behind while the fleet starts.
|
|
473
|
+
*
|
|
474
|
+
* Ruled on by fable, and conditional by construction. The reasoning only
|
|
475
|
+
* holds because a leftover tunnel points at an ephemeral port this process
|
|
476
|
+
* owned and nothing will deliberately bind again — so the damage of being
|
|
477
|
+
* wrong is a URL that answers "unavailable", not a public dashboard.
|
|
478
|
+
*
|
|
479
|
+
* **If anyone puts this host back on the health port, this ruling is void**
|
|
480
|
+
* and the path must return to refusing the handover. That is the first
|
|
481
|
+
* condition below rather than a note in a comment, precisely so the change
|
|
482
|
+
* that breaks the reasoning also flips the behaviour.
|
|
483
|
+
*/
|
|
484
|
+
mayHandOverWithUnconfirmedTunnel() {
|
|
485
|
+
// Each of these reads the thing itself rather than a flag set alongside it.
|
|
486
|
+
// A boolean assigned on the same line that does the work is true whenever
|
|
487
|
+
// the line ran, which makes "all four conditions are checked" a sentence
|
|
488
|
+
// about nothing — remove the work and the flag goes with it.
|
|
489
|
+
const onEphemeralPort = this.opts.tunnel === true && this.opts.port === 0;
|
|
490
|
+
const listenerClosed = this.server === null;
|
|
491
|
+
const credentialsRevoked = this.credentials.revokedNow;
|
|
492
|
+
const leaseKept = existsSync(leasePath(this.opts.dataDir));
|
|
493
|
+
return onEphemeralPort && listenerClosed && credentialsRevoked && leaseKept;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
//# sourceMappingURL=setup-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-host.js","sourceRoot":"","sources":["../src/setup-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SetupRecord {
|
|
2
|
+
completedAt: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function isSetupComplete(dataDir: string): boolean;
|
|
6
|
+
export declare function markSetupComplete(dataDir: string, version?: string): void;
|
|
7
|
+
/** Only `agend setup --reset` calls this; no route may. */
|
|
8
|
+
export declare function clearSetupComplete(dataDir: string): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Setup has been done once."
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not fleet.yaml's existence: a user who deletes or moves
|
|
5
|
+
* fleet.yaml — or whose disk hands back an empty one — would otherwise reopen a
|
|
6
|
+
* pre-fleet setup host, which is an unauthenticated-by-default surface. The
|
|
7
|
+
* marker is its own file and only `agend setup --reset` clears it.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
const FILE = "setup-complete.json";
|
|
12
|
+
function path(dataDir) {
|
|
13
|
+
return join(dataDir, FILE);
|
|
14
|
+
}
|
|
15
|
+
export function isSetupComplete(dataDir) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(readFileSync(path(dataDir), "utf-8"));
|
|
18
|
+
return typeof parsed.completedAt === "string";
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Unreadable is not "not done". A corrupt marker must not reopen setup;
|
|
22
|
+
// `agend setup --reset` is the way back, and it is a local command.
|
|
23
|
+
return existsSync(path(dataDir));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function markSetupComplete(dataDir, version) {
|
|
27
|
+
writeFileSync(path(dataDir), JSON.stringify({ completedAt: new Date().toISOString(), version }) + "\n", { mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
/** Only `agend setup --reset` calls this; no route may. */
|
|
30
|
+
export function clearSetupComplete(dataDir) {
|
|
31
|
+
try {
|
|
32
|
+
unlinkSync(path(dataDir));
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=setup-marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-marker.js","sourceRoot":"","sources":["../src/setup-marker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,IAAI,GAAG,qBAAqB,CAAC;AAOnC,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAyB,CAAC;QACxF,OAAO,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,oEAAoE;QACpE,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,OAAgB;IACjE,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAwB,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACjJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,IAAI,CAAC;QAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The question asked before a setup page is put on the public internet.
|
|
3
|
+
*
|
|
4
|
+
* It is asked every single time. There is no configuration that turns it off
|
|
5
|
+
* and no flag that answers it in advance: `allow_public` defaults to true
|
|
6
|
+
* because opening from a phone is the product, not because the consequences
|
|
7
|
+
* stop mattering after the first time. And a pre-fleet host has no chat channel
|
|
8
|
+
* to put a confirmation button in, so the only place this can be asked is the
|
|
9
|
+
* terminal that ran the command.
|
|
10
|
+
*
|
|
11
|
+
* Which means a run with no terminal cannot be consented to, and is refused.
|
|
12
|
+
* Not "defaults to allow because nobody is there to object" — nobody being
|
|
13
|
+
* there is the reason to refuse.
|
|
14
|
+
*
|
|
15
|
+
* The wording is deliberately not sol's Web Terminal text. That one covers a
|
|
16
|
+
* terminal session that ends; this one carries a long-lived bot token and an
|
|
17
|
+
* admin id through somebody else's edge, which is a different thing to consent
|
|
18
|
+
* to and has to say so.
|
|
19
|
+
*/
|
|
20
|
+
export interface ConsentIo {
|
|
21
|
+
readonly isTTY: boolean;
|
|
22
|
+
ask(question: string): Promise<string>;
|
|
23
|
+
say(line: string): void;
|
|
24
|
+
}
|
|
25
|
+
export type ConsentResult = {
|
|
26
|
+
readonly granted: true;
|
|
27
|
+
} | {
|
|
28
|
+
readonly granted: false;
|
|
29
|
+
readonly reason: "declined" | "no-tty";
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Everything a person needs to decide, in the order they need it.
|
|
33
|
+
*
|
|
34
|
+
* Each line is here because leaving it out would make the answer uninformed:
|
|
35
|
+
* the token really does cross Cloudflare, the page really can be closed by a
|
|
36
|
+
* stranger, and the link really does work for anyone who sees it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function publicTunnelWarning(ttlMinutes: number): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Ask, and treat anything that is not a clear yes as a no.
|
|
41
|
+
*
|
|
42
|
+
* Including the empty answer: a person who presses enter to get past a prompt
|
|
43
|
+
* has not read it, and this is not a prompt to get past.
|
|
44
|
+
*/
|
|
45
|
+
export declare function confirmPublicTunnel(io: ConsentIo, ttlMinutes: number): Promise<ConsentResult>;
|
|
46
|
+
/**
|
|
47
|
+
* What the CLI adds when a tunnel was asked for and there is none.
|
|
48
|
+
*
|
|
49
|
+
* The host already says the link is local-only and a phone cannot open it —
|
|
50
|
+
* that is true wherever SetupHost is used. What only the CLI can usefully add
|
|
51
|
+
* is what to do about it, so it does not repeat the rest.
|
|
52
|
+
*/
|
|
53
|
+
export declare function noTunnelBinaryMessage(): string[];
|