fedipod 1.0.0 → 1.3.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/README.md +4 -4
- package/bin/fedipod.mjs +45 -2253
- package/gateway.md +1 -1
- package/lib/{c2s.mjs → client/c2s.mjs} +10 -3
- package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
- package/lib/client/masto/accounts.mjs +264 -0
- package/lib/client/masto/body.mjs +69 -0
- package/lib/client/masto/index.mjs +183 -0
- package/lib/client/masto/instance.mjs +104 -0
- package/lib/client/masto/media.mjs +133 -0
- package/lib/client/masto/oauth.mjs +599 -0
- package/lib/client/masto/render.mjs +459 -0
- package/lib/client/masto/statuses.mjs +331 -0
- package/lib/client/masto/timelines.mjs +316 -0
- package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
- package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
- package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
- package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
- package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
- package/lib/{import.mjs → connections/import.mjs} +1 -1
- package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +3 -3
- package/lib/connections/vault.mjs +114 -0
- package/lib/core/as2.mjs +170 -0
- package/lib/core/contexts/activitystreams.json +379 -0
- package/lib/core/contexts/did-v1.json +57 -0
- package/lib/core/contexts/fep-5711.json +36 -0
- package/lib/core/contexts/gotosocial.json +86 -0
- package/lib/core/contexts/identity-v1.json +152 -0
- package/lib/core/contexts/index.mjs +45 -0
- package/lib/core/contexts/join-lemmy.json +33 -0
- package/lib/core/contexts/joinmastodon.json +28 -0
- package/lib/core/contexts/map.json +16 -0
- package/lib/core/contexts/miscellany.json +19 -0
- package/lib/core/contexts/schemaorg.json +8845 -0
- package/lib/core/contexts/security-data-integrity-v1.json +78 -0
- package/lib/core/contexts/security-data-integrity-v2.json +81 -0
- package/lib/core/contexts/security-multikey-v1.json +35 -0
- package/lib/core/contexts/security-v1.json +74 -0
- package/lib/core/contexts/webfinger.json +10 -0
- package/lib/{deliver.mjs → core/deliver.mjs} +2 -2
- package/lib/core/graphview.mjs +269 -0
- package/lib/core/intake/activities.mjs +437 -0
- package/lib/core/intake/activity.mjs +240 -0
- package/lib/core/intake/channel.mjs +144 -0
- package/lib/core/intake/group.mjs +222 -0
- package/lib/core/intake/index.mjs +629 -0
- package/lib/core/intake/notes.mjs +288 -0
- package/lib/core/intake/verify.mjs +142 -0
- package/lib/{keys.mjs → core/keys.mjs} +1 -1
- package/lib/core/publisher/collections.mjs +229 -0
- package/lib/core/publisher/index.mjs +421 -0
- package/lib/core/publisher/notes.mjs +188 -0
- package/lib/core/publisher/questions.mjs +233 -0
- package/lib/core/publisher/restore.mjs +199 -0
- package/lib/core/shapes/activitystreams.ttl +129 -0
- package/lib/core/shapes/index.mjs +107 -0
- package/lib/core/shapes/shapes-text.mjs +13 -0
- package/lib/{social.mjs → core/social.mjs} +2 -2
- package/lib/{store.mjs → core/store.mjs} +4 -0
- package/lib/{wire.mjs → core/wire.mjs} +2 -2
- package/lib/device/admin/index.mjs +13 -0
- package/lib/device/admin/origins.mjs +35 -0
- package/lib/device/admin/routes/connections.mjs +144 -0
- package/lib/device/admin/routes/gateway.mjs +199 -0
- package/lib/device/admin/routes/lifecycle.mjs +191 -0
- package/lib/device/admin/routes/owner.mjs +322 -0
- package/lib/device/admin/routes/setup.mjs +393 -0
- package/lib/device/admin/routes/social.mjs +188 -0
- package/lib/device/admin/server.mjs +95 -0
- package/lib/device/admin/static.mjs +244 -0
- package/lib/device/admin/surface.mjs +274 -0
- package/lib/device/cli/commands/account.mjs +586 -0
- package/lib/device/cli/commands/run.mjs +278 -0
- package/lib/device/cli/commands/service.mjs +221 -0
- package/lib/device/cli/commands/setup.mjs +410 -0
- package/lib/device/cli/commands/state.mjs +559 -0
- package/lib/device/cli/context.mjs +288 -0
- package/lib/{migrate.mjs → device/migrate.mjs} +1 -1
- package/lib/{remote.mjs → device/remote.mjs} +3 -3
- package/lib/{setup.mjs → device/setup.mjs} +3 -3
- package/lib/{update.mjs → device/update.mjs} +1 -1
- package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
- package/lib/{front-core.mjs → gateway/front-core.mjs} +3 -3
- package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +1 -1
- package/lib/{httpsig.mjs → gateway/httpsig.mjs} +1 -1
- package/lib/server/embed.mjs +405 -0
- package/lib/{links.mjs → shared/links.mjs} +1 -1
- package/lib/{ua.mjs → shared/ua.mjs} +1 -1
- package/package.json +11 -1
- package/run-agent.mjs +33 -25
- package/scripts/build-app.mjs +4 -0
- package/scripts/check-pod-calls.mjs +8 -2
- package/scripts/refresh-contexts.mjs +39 -0
- package/web/admin/actors.js +145 -0
- package/web/admin/common.js +23 -0
- package/web/admin/connections.js +112 -0
- package/web/admin/gateway.js +111 -0
- package/web/admin/group.js +258 -0
- package/web/admin/index.html +7 -1
- package/web/admin/record.js +378 -0
- package/web/admin/setup/index.html +1 -0
- package/web/admin/setup/setup.js +2 -13
- package/web/admin/upkeep.js +170 -0
- package/web/app/README.md +6 -6
- package/web/app/admin-facade.mjs +3 -3
- package/web/app/agent.mjs +14 -16
- package/web/app/atproto-browser.mjs +1 -1
- package/web/app/boot.mjs +2 -3
- package/web/app/deliver-relay.mjs +1 -1
- package/web/app/dist/boot.js +22 -3
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +21913 -5446
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/fediacct-browser.mjs +1 -1
- package/web/app/keys-browser.mjs +27 -4
- package/web/app/shims/shapes-text.mjs +8 -0
- package/web/app/signup.mjs +2 -3
- package/web/app/site/admin/actors.js +145 -0
- package/web/app/site/admin/common.js +23 -0
- package/web/app/site/admin/connections.js +112 -0
- package/web/app/site/admin/gateway.js +111 -0
- package/web/app/site/admin/group.js +258 -0
- package/web/app/site/admin/index.html +7 -1
- package/web/app/site/admin/record.js +378 -0
- package/web/app/site/admin/setup/index.html +1 -0
- package/web/app/site/admin/setup/setup.js +2 -13
- package/web/app/site/admin/upkeep.js +170 -0
- package/web/app/site/boot.js +22 -3
- package/web/app/site/sw.js +21913 -5446
- package/web/app/sw-src.mjs +17 -2
- package/lib/admin.mjs +0 -1913
- package/lib/embed.mjs +0 -220
- package/lib/intake.mjs +0 -1981
- package/lib/mastoapi.mjs +0 -2284
- package/lib/publisher.mjs +0 -1192
- package/web/admin/admin.js +0 -1181
- package/web/app/site/admin/admin.js +0 -1181
- /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
- /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
- /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
- /package/lib/{lease.mjs → core/lease.mjs} +0 -0
- /package/lib/{polls.mjs → core/polls.mjs} +0 -0
- /package/lib/{proof.mjs → core/proof.mjs} +0 -0
- /package/lib/{storage.mjs → core/storage.mjs} +0 -0
- /package/lib/{account.mjs → device/account.mjs} +0 -0
- /package/lib/{certs.mjs → device/certs.mjs} +0 -0
- /package/lib/{export-collections.mjs → device/export-collections.mjs} +0 -0
- /package/lib/{home.mjs → device/home.mjs} +0 -0
- /package/lib/{ports.mjs → device/ports.mjs} +0 -0
- /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
- /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// context.mjs — what every command starts from: the arguments, which
|
|
2
|
+
// identity (HOME) and port (PORT) a command means, and the helpers that ask,
|
|
3
|
+
// probe and finish. HOME, PORT and DEFAULT_ISSUE are live bindings: useProfile()
|
|
4
|
+
// reassigns them and every command module sees the new value.
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { localFetch } from '../../../lib/client/localapi.mjs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import net from 'node:net';
|
|
11
|
+
import readline from 'node:readline';
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
14
|
+
import { apRoot, profilesDir, identityHomes, isLegacyRoot, CURRENT_ROOT, tildify, rootOf,
|
|
15
|
+
readRoot, writeRoot, defaultProfile, profileHome, rootHoldsIdentity, ROOT_FILE,
|
|
16
|
+
recordLastUsed, writeJsonAtomic } from '../../../lib/device/home.mjs';
|
|
17
|
+
import { insecureUrlReason } from '../../../lib/shared/safefetch.mjs';
|
|
18
|
+
import { portFree, freePortFrom } from '../../../lib/device/ports.mjs';
|
|
19
|
+
|
|
20
|
+
export const args = process.argv.slice(2);
|
|
21
|
+
export const cmd = args[0];
|
|
22
|
+
export const flag = (name, dflt) => {
|
|
23
|
+
const i = args.indexOf('--' + name);
|
|
24
|
+
return i >= 0 ? args[i + 1] : dflt;
|
|
25
|
+
};
|
|
26
|
+
export const has = (name) => args.includes('--' + name);
|
|
27
|
+
// The port can also be given bare — `npm start 8081` reaches us as `up 8081`,
|
|
28
|
+
// because npm passes positionals through but eats `--port`. Bare form only for
|
|
29
|
+
// the start-style commands, where a lone number cannot mean anything else.
|
|
30
|
+
export const barePort = () => {
|
|
31
|
+
if (!['up', 'start', 'run'].includes(cmd)) return null;
|
|
32
|
+
for (let i = 1; i < args.length; i++) {
|
|
33
|
+
if (/^\d+$/.test(args[i]) && !args[i - 1].startsWith('--')) return args[i];
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
};
|
|
37
|
+
export const portFlag = () => flag('port', null) || barePort();
|
|
38
|
+
// One identity per home, and EVERY identity is `<root>/profiles/<name>/`. There
|
|
39
|
+
// is no privileged unnamed one: `root.json` names which you get when you do not
|
|
40
|
+
// say, and that is a pointer you can change rather than a directory you have to
|
|
41
|
+
// move a private key out of. lib/home.mjs decides the root.
|
|
42
|
+
//
|
|
43
|
+
// An explicit --home / AP_HOME still wins and is taken literally: you named a
|
|
44
|
+
// directory, so that directory is the identity, root.json unread. That is what
|
|
45
|
+
// `rootOf` has always documented for a custom home.
|
|
46
|
+
export const PROFILE = flag('profile', process.env.AP_PROFILE || null);
|
|
47
|
+
export const AP_ROOT = apRoot();
|
|
48
|
+
export const PROFILES_DIR = profilesDir(AP_ROOT);
|
|
49
|
+
|
|
50
|
+
// `let`, because setup does not know which identity it is until it has asked for
|
|
51
|
+
// the handle — the home is named after it. Everything below reads HOME at call
|
|
52
|
+
// time, so reassigning it once, early, is enough; PORT is the exception and is
|
|
53
|
+
// recomputed with it.
|
|
54
|
+
export let DEFAULT_ISSUE = null; // set when the pointer is unusable
|
|
55
|
+
export let HOME = flag('home', process.env.AP_HOME || (() => {
|
|
56
|
+
if (PROFILE) return profileHome(AP_ROOT, PROFILE);
|
|
57
|
+
const d = defaultProfile(AP_ROOT);
|
|
58
|
+
if (typeof d === 'string') return profileHome(AP_ROOT, d);
|
|
59
|
+
DEFAULT_ISSUE = d?.missing
|
|
60
|
+
? `${ROOT_FILE} names "${d.missing}", which is not an identity here`
|
|
61
|
+
: 'there is more than one identity here and none is the default';
|
|
62
|
+
return profileHome(AP_ROOT, d?.missing || '');
|
|
63
|
+
})());
|
|
64
|
+
|
|
65
|
+
// The port chosen at setup is remembered, so `start`/`stop`/`status` need no
|
|
66
|
+
// flags afterwards. Precedence: --port (or a bare port number, `npm start
|
|
67
|
+
// 8081`) > AP_PORT > the recorded choice > 8030.
|
|
68
|
+
// The handle is remembered alongside it, for the named origin: the agent also
|
|
69
|
+
// answers at <handle>.localhost:<port>, and that has to work from the first
|
|
70
|
+
// request, before pod state has been read.
|
|
71
|
+
export function recordedAgent() {
|
|
72
|
+
try { return JSON.parse(fs.readFileSync(path.join(HOME, 'agent.json'), 'utf8')) || {}; }
|
|
73
|
+
catch { return {}; }
|
|
74
|
+
}
|
|
75
|
+
export function recordedPort() { return Number(recordedAgent().port) || null; }
|
|
76
|
+
export function recordAgent(fields) {
|
|
77
|
+
try {
|
|
78
|
+
fs.mkdirSync(HOME, { recursive: true, mode: 0o700 });
|
|
79
|
+
const rec = { ...recordedAgent(), ...fields };
|
|
80
|
+
writeJsonAtomic(path.join(HOME, 'agent.json'), rec, { mode: 0o644 });
|
|
81
|
+
} catch { /* the flag still works, it just isn't remembered */ }
|
|
82
|
+
}
|
|
83
|
+
export let PORT = Number(portFlag() || process.env.AP_PORT || recordedPort() || 8030);
|
|
84
|
+
|
|
85
|
+
// Setup is the one command that cannot know its home in advance: the identity is
|
|
86
|
+
// named after the handle, and the handle is the first thing it asks. Everything
|
|
87
|
+
// that reads HOME does so at call time, so pointing it at the right directory as
|
|
88
|
+
// soon as the name exists is enough — PORT is recomputed because it was read
|
|
89
|
+
// from the old home's agent.json.
|
|
90
|
+
export function useProfile(name) {
|
|
91
|
+
HOME = flag('home', process.env.AP_HOME || profileHome(AP_ROOT, name));
|
|
92
|
+
PORT = Number(portFlag() || process.env.AP_PORT || recordedPort() || 8030);
|
|
93
|
+
DEFAULT_ISSUE = null;
|
|
94
|
+
return HOME;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A handle becomes a directory name, so it is checked before it is one. Same
|
|
98
|
+
// rule the admin API applies before creating an actor (admin.mjs) — without it
|
|
99
|
+
// a handle containing a slash or `..` climbs out of profiles/.
|
|
100
|
+
const HANDLE_RE = /^[a-z0-9][a-z0-9_-]{0,30}$/;
|
|
101
|
+
export function requireHandle(handle) {
|
|
102
|
+
if (HANDLE_RE.test(handle)) return handle;
|
|
103
|
+
console.error(`"${handle}" cannot be a handle: letters, digits, hyphens and underscores,`);
|
|
104
|
+
console.error('starting with a letter or digit, at most 31 characters.');
|
|
105
|
+
process.exit(2);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Commands that act on an identity need one decided. Says which of the three
|
|
109
|
+
// ways it failed, because "no identity" and "which identity" are different
|
|
110
|
+
// problems with different fixes.
|
|
111
|
+
export function requireIdentity() {
|
|
112
|
+
// An explicit AP_HOME / --home is an explicit identity directory. Nothing
|
|
113
|
+
// about the machine's root applies to it — including whether that root has
|
|
114
|
+
// been restructured, which is somebody else's install's problem.
|
|
115
|
+
if (process.env.AP_HOME || flag('home')) return;
|
|
116
|
+
if (rootHoldsIdentity(AP_ROOT)) {
|
|
117
|
+
console.error(`${tildify(AP_ROOT)} still keeps an identity at its top level.`);
|
|
118
|
+
console.error('Every identity lives in profiles/<name>/ now. Move this one down with:\n');
|
|
119
|
+
console.error(` ${process.argv[1]} home --restructure\n`);
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
if (!DEFAULT_ISSUE) return;
|
|
123
|
+
const homes = identityHomes(AP_ROOT).filter(h => fs.existsSync(path.join(h.dir, 'credential.json')));
|
|
124
|
+
console.error(DEFAULT_ISSUE + '.');
|
|
125
|
+
if (homes.length) {
|
|
126
|
+
console.error(`\n ${process.argv[1]} --profile <name> start\n`);
|
|
127
|
+
console.error('Whichever you start is remembered, so plain commands mean that one afterwards.');
|
|
128
|
+
console.error(`here: ${homes.map(h => h.name).join(', ')}`);
|
|
129
|
+
} else {
|
|
130
|
+
console.error(`\nThere are no identities yet — ${process.argv[1]} setup`);
|
|
131
|
+
}
|
|
132
|
+
process.exit(2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function askHidden(prompt) {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
138
|
+
const onData = (c) => { if (String(c) !== '\n' && String(c) !== '\r') readline.moveCursor(process.stdout, -1, 0), process.stdout.write('*'); };
|
|
139
|
+
process.stdin.on('data', onData);
|
|
140
|
+
rl.question(prompt, (answer) => { process.stdin.off('data', onData); rl.close(); process.stdout.write('\n'); resolve(answer); });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Plain prompt with a default: Enter accepts it. Non-interactive runs
|
|
145
|
+
// (scripts, CI) take the default silently, so flags remain sufficient.
|
|
146
|
+
let sharedRl = null; // one interface: a new one per
|
|
147
|
+
export function ask(prompt, dflt = '') { // question would drop buffered input
|
|
148
|
+
if (!process.stdin.isTTY) return Promise.resolve(dflt);
|
|
149
|
+
sharedRl ||= readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
sharedRl.question(dflt ? `${prompt} [${dflt}]: ` : `${prompt}: `, (answer) => {
|
|
152
|
+
resolve(String(answer).trim() || dflt);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export function endAsking() { sharedRl?.close(); sharedRl = null; }
|
|
157
|
+
|
|
158
|
+
// "Occupied" means "cannot be bound", not "does not answer HTTP": something
|
|
159
|
+
// holding a port without speaking HTTP reads as free to a GET, and then the
|
|
160
|
+
// agent dies on EADDRINUSE.
|
|
161
|
+
|
|
162
|
+
// The first port from `first` upward that binds. Walking always ends in one,
|
|
163
|
+
// so this is a step rather than a condition.
|
|
164
|
+
|
|
165
|
+
// Is anything at all on this port? For the operations that MOVE data, "it did
|
|
166
|
+
// not answer as one of ours" is not the same as "nothing is there": an agent
|
|
167
|
+
// started with AP_GATE_TOKEN answers 401 to an un-tokened /status, so agentOn
|
|
168
|
+
// reads a perfectly live agent as stopped — and a sweep that trusted it would
|
|
169
|
+
// copy the state out from under one, which the next write then overwrites.
|
|
170
|
+
// Bind to find out, and fail closed.
|
|
171
|
+
export async function somethingOn(port) {
|
|
172
|
+
const mine = await agentOn(port);
|
|
173
|
+
if (mine) return 'running';
|
|
174
|
+
return (await portFree(port)) ? null : 'something is on the port and did not answer as ours';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Whatever is on the port — is it one of ours?
|
|
178
|
+
export async function agentOn(port) {
|
|
179
|
+
try {
|
|
180
|
+
const res = await localFetch(HOME, port, `/status`, { signal: AbortSignal.timeout(2000) });
|
|
181
|
+
const body = await res.json();
|
|
182
|
+
return typeof body?.configured === 'boolean' ? body : null;
|
|
183
|
+
} catch { return null; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const isInside = (root, p) => {
|
|
187
|
+
const rel = path.relative(root, p);
|
|
188
|
+
return !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export function openBrowser(url) {
|
|
192
|
+
try {
|
|
193
|
+
const win = process.platform === 'win32';
|
|
194
|
+
const cmd = win ? 'cmd' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
195
|
+
const child = spawn(cmd, win ? ['/c', 'start', '', url] : [url], { detached: true, stdio: 'ignore' });
|
|
196
|
+
// A box with no opener at all (a server, a bare container) emits this
|
|
197
|
+
// asynchronously, where the try/catch cannot reach it — and an unhandled
|
|
198
|
+
// 'error' event on a child process ends the agent.
|
|
199
|
+
child.on('error', () => {});
|
|
200
|
+
child.unref();
|
|
201
|
+
} catch { /* best-effort — the URL is printed anyway */ }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// A one-shot command holds the lease only for its own duration: exiting without
|
|
205
|
+
// releasing it leaves the next `start` as a read-only viewer until the lease
|
|
206
|
+
// expires, which is 5 minutes of doing nothing.
|
|
207
|
+
export async function finish(agent, code = 0) {
|
|
208
|
+
await agent.lease?.release().catch(() => {});
|
|
209
|
+
await agent.store?.flush?.().catch(() => {});
|
|
210
|
+
process.exit(code);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Anything that decides the identity. Given even one of these, setup stays on
|
|
214
|
+
// the command line exactly as it always did — scripts, CI and the tarball's
|
|
215
|
+
// unpack-and-go line depend on that. At a terminal with none of them, setup
|
|
216
|
+
// asks the two things it needs to open a browser and asks the rest there.
|
|
217
|
+
export const IDENTITY_FLAGS = ['new-account', 'pod', 'issuer', 'email', 'name', 'pod-name',
|
|
218
|
+
'group', 'approve-joins', 'summary', 'icon', 'root', 'keys', 'rotate-key'];
|
|
219
|
+
|
|
220
|
+
// Refuse before anything is asked, let alone typed: setup used to overwrite
|
|
221
|
+
// credential.json in place, and a minted credential is only shown once — so
|
|
222
|
+
// the identity it belonged to could not be recovered afterwards.
|
|
223
|
+
export function refuseExistingIdentity() {
|
|
224
|
+
const credPath = path.join(HOME, 'credential.json');
|
|
225
|
+
if (!fs.existsSync(credPath) || has('force')) return;
|
|
226
|
+
let held = '(unreadable)';
|
|
227
|
+
try { held = JSON.parse(fs.readFileSync(credPath, 'utf8')).remotePod; } catch {}
|
|
228
|
+
console.error(`${HOME} already holds an identity: ${held}`);
|
|
229
|
+
console.error('For another identity: fedipod setup --profile <name>');
|
|
230
|
+
console.error('To list what exists: fedipod profiles');
|
|
231
|
+
console.error('To replace this one: add --force (the old credential is lost)');
|
|
232
|
+
console.error('');
|
|
233
|
+
console.error('If a setup died half-way, do NOT re-run it — the credential it already');
|
|
234
|
+
console.error('minted cannot be minted twice. Run `fedipod start` and');
|
|
235
|
+
console.error('finish at /admin/setup/ in the browser.');
|
|
236
|
+
process.exit(2);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Ask the handle (permanent, and it names the origin) and the port, start
|
|
240
|
+
// serving, and hand over to the page at /admin/setup/. Nothing is created here: the
|
|
241
|
+
// agent's own POST /setup does all of it, so a closed tab cannot lose a
|
|
242
|
+
// credential that only exists in an HTTP response.
|
|
243
|
+
export async function runBrowserSetup() {
|
|
244
|
+
const handle = flag('handle') || await ask('handle (the name in your address; permanent)');
|
|
245
|
+
if (!handle) { console.error('a handle is required'); process.exit(2); }
|
|
246
|
+
// The handle names the home, so nothing can be decided before it — including
|
|
247
|
+
// which identity would be overwritten, and which port was remembered.
|
|
248
|
+
requireHandle(handle);
|
|
249
|
+
useProfile(PROFILE || handle);
|
|
250
|
+
refuseExistingIdentity();
|
|
251
|
+
const port = portFlag() ? PORT : (Number(await ask('port', String(PORT))) || PORT);
|
|
252
|
+
endAsking();
|
|
253
|
+
|
|
254
|
+
// Recorded before the server starts, so `stop`/`status` work while the
|
|
255
|
+
// browser flow is still open — it used to be written only after the mint.
|
|
256
|
+
recordAgent({ port, handle });
|
|
257
|
+
|
|
258
|
+
const { Agent } = await import(new URL('../../../run-agent.mjs', import.meta.url));
|
|
259
|
+
const { startAdmin } = await import(new URL('../../../lib/device/admin/index.mjs', import.meta.url));
|
|
260
|
+
const { hostLabel } = await import(new URL('../../../lib/shared/guard.mjs', import.meta.url));
|
|
261
|
+
const agent = new Agent({ home: HOME, log: (...a) => console.log('[ap]', ...a) });
|
|
262
|
+
startAdmin({
|
|
263
|
+
port, handle, agent,
|
|
264
|
+
gateToken: process.env.AP_GATE_TOKEN || '',
|
|
265
|
+
log: (...a) => console.log('[ap]', ...a),
|
|
266
|
+
});
|
|
267
|
+
const shutdown = () => {
|
|
268
|
+
setTimeout(() => process.exit(0), 1500).unref();
|
|
269
|
+
try { fs.rmSync(path.join(HOME, 'agent.pid'), { force: true }); } catch {}
|
|
270
|
+
Promise.allSettled([agent.store.flush(), agent.lease?.release()]).finally(() => process.exit(0));
|
|
271
|
+
};
|
|
272
|
+
process.on('SIGINT', shutdown);
|
|
273
|
+
process.on('SIGTERM', shutdown);
|
|
274
|
+
|
|
275
|
+
const label = hostLabel(handle);
|
|
276
|
+
const named = label ? `https://${label}.localhost:${port}/` : null;
|
|
277
|
+
const plain = `https://localhost:${port}/`;
|
|
278
|
+
const pad = Math.max(named?.length || 0, plain.length);
|
|
279
|
+
console.log('');
|
|
280
|
+
if (named) {
|
|
281
|
+
console.log(` ${named.padEnd(pad)} <- opening this`);
|
|
282
|
+
console.log(` ${plain.padEnd(pad)} <- the same agent, if your browser cannot find that name`);
|
|
283
|
+
} else {
|
|
284
|
+
console.log(` ${plain}`);
|
|
285
|
+
}
|
|
286
|
+
console.log('\nsetup continues in the browser — Ctrl-C to stop\n');
|
|
287
|
+
openBrowser(named || plain);
|
|
288
|
+
}
|
|
@@ -99,7 +99,7 @@ export function classifyRemoteState(urls, base) {
|
|
|
99
99
|
// a failure part-way leaves the source authoritative.
|
|
100
100
|
// Used by both `state --to` and the record page's Move private data.
|
|
101
101
|
export async function copyPrivateHalf({ from, to, log = () => {} }) {
|
|
102
|
-
const { PodStore } = await import('
|
|
102
|
+
const { PodStore } = await import('../core/store.mjs');
|
|
103
103
|
const src = new PodStore({ log: () => {} });
|
|
104
104
|
src.attach(from.state);
|
|
105
105
|
await src.load();
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { PodTransport } from '
|
|
15
|
+
import { PodTransport } from '../pod/transport.mjs';
|
|
16
16
|
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
|
-
const vendorDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '
|
|
18
|
+
const vendorDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../vendor');
|
|
19
19
|
const { mintCredential, discoverTokenEndpoint, createGrantSession, revokeCredentialViaAccount } =
|
|
20
20
|
require(path.join(vendorDir, 'idp-grant.cjs'));
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ export { mintCredential, discoverTokenEndpoint, revokeCredentialViaAccount };
|
|
|
23
23
|
|
|
24
24
|
// Re-exported so the callers that guard their own deletions — run-agent, the
|
|
25
25
|
// server package's live test, the smoke suite — keep importing it from here.
|
|
26
|
-
export { protectedFromDeletion } from '
|
|
26
|
+
export { protectedFromDeletion } from '../pod/transport.mjs';
|
|
27
27
|
|
|
28
28
|
export class RemotePod extends PodTransport {
|
|
29
29
|
constructor(credential, { log = () => {}, home = null, session = null, role = 'agent' } = {}) {
|
|
@@ -15,10 +15,10 @@ import * as $rdf from 'rdflib';
|
|
|
15
15
|
|
|
16
16
|
import { createAccountWithPod as realCreateAccount } from './account.mjs';
|
|
17
17
|
import { mintCredential as realMint } from './remote.mjs';
|
|
18
|
-
import { hashPassword } from '
|
|
19
|
-
import { webfingerHost } from '
|
|
18
|
+
import { hashPassword } from '../client/masto/index.mjs';
|
|
19
|
+
import { webfingerHost } from '../core/wire.mjs';
|
|
20
20
|
import { rootOf, recordLastUsed, writeJsonAtomic } from './home.mjs';
|
|
21
|
-
import { insecureUrlReason } from '
|
|
21
|
+
import { insecureUrlReason } from '../shared/safefetch.mjs';
|
|
22
22
|
import { CURRENT_LAYOUT, isCurrent } from './migrate.mjs';
|
|
23
23
|
|
|
24
24
|
const SOLID = $rdf.Namespace('http://www.w3.org/ns/solid/terms#');
|
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
|
|
14
14
|
export const LATEST_URL = 'https://raw.githubusercontent.com/jeff-zucker/FediPod/main/package.json';
|
|
15
15
|
|
|
16
|
-
export const repoRoot = () => path.resolve(path.dirname(fileURLToPath(import.meta.url)), '
|
|
16
|
+
export const repoRoot = () => path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
17
17
|
|
|
18
18
|
export const localVersion = (root = repoRoot()) => {
|
|
19
19
|
try { return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version || null; }
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// whole mechanism (the test suite sets it, so throwaway agents do not fight
|
|
14
14
|
// over a real machine's door).
|
|
15
15
|
import https from 'node:https';
|
|
16
|
-
import { localFetch } from '
|
|
16
|
+
import { localFetch } from '../client/localapi.mjs';
|
|
17
17
|
|
|
18
18
|
export const DIRECTORY_PORT = 8030;
|
|
19
19
|
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
import crypto from 'node:crypto';
|
|
19
19
|
import { handleDelivery } from './gateway-core.mjs';
|
|
20
|
-
import { readCapped, safeFetch, isLoopbackHost } from '
|
|
21
|
-
import * as podRoot from '
|
|
22
|
-
import * as podPolicy from '
|
|
20
|
+
import { readCapped, safeFetch, isLoopbackHost } from '../shared/safefetch.mjs';
|
|
21
|
+
import * as podRoot from '../pod/root.mjs';
|
|
22
|
+
import * as podPolicy from '../pod/policy.mjs';
|
|
23
23
|
|
|
24
24
|
// The one WebFinger document, spelled out here rather than imported from
|
|
25
25
|
// wire.mjs: wire drags the agent's whole HTML pipeline (sanitize-html and
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import crypto from 'node:crypto';
|
|
16
16
|
import { verifyHttpSignature, makeSafeLoader, makeReceipt, signReceipt } from './httpsig.mjs';
|
|
17
|
-
import * as inbox from '
|
|
17
|
+
import * as inbox from '../pod/inbox.mjs';
|
|
18
18
|
|
|
19
19
|
const DEFAULT_MAX_BYTES = 512 * 1024; // mirror intake.mjs MAX_ITEM_BYTES
|
|
20
20
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import crypto from 'node:crypto';
|
|
13
13
|
import { verifyRequestDetailed } from '@fedify/fedify/sig';
|
|
14
|
-
import { assertPublicUrl, safeFetch, readCapped } from '
|
|
14
|
+
import { assertPublicUrl, safeFetch, readCapped } from '../shared/safefetch.mjs';
|
|
15
15
|
|
|
16
16
|
// An SSRF-safe JSON-LD document loader for Fedify's key fetch. The keyId in a
|
|
17
17
|
// delivery is chosen by the sender, so every deref it triggers is untrusted
|