31d21ef3c2
Deploy Indiekit Server / deploy (push) Failing after 1m46s
patch-actor-aliases-successor.mjs: broken — ERR_PACKAGE_PATH_NOT_EXPORTED (./lib/federation-setup.js not exported by current package version). Surfaced by run-patches.mjs. Needs fix or removal. send-move-activity.mjs: utility script for AP actor migration (Move activity).
131 lines
4.0 KiB
JavaScript
131 lines
4.0 KiB
JavaScript
/**
|
|
* Send AP Move activity: blog.giersig.eu/activitypub/users/svemagie
|
|
* → svemagie.net/activitypub/users/svemagie
|
|
*
|
|
* Run AFTER:
|
|
* 1. ap_profile updated with movedTo + alsoKnownAs
|
|
* 2. indiekit restarted so new actor doc is live at svemagie.net
|
|
*
|
|
* Usage: node send-move-activity.mjs [--dry-run]
|
|
*/
|
|
|
|
import { createSign, createHash } from "node:crypto";
|
|
import { MongoClient } from "mongodb";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const DRY_RUN = process.argv.includes("--dry-run");
|
|
|
|
const OLD_ACTOR = "https://blog.giersig.eu/activitypub/users/svemagie";
|
|
const NEW_ACTOR = "https://svemagie.net/activitypub/users/svemagie";
|
|
const KEY_ID = `${OLD_ACTOR}#main-key`;
|
|
|
|
// MongoDB connection (same as indiekit .env)
|
|
const MONGO_URL = "mongodb://indiekit:hasag@10.100.0.20:27017/indiekit?authSource=admin";
|
|
|
|
async function signRequest(method, url, body, privateKeyPem) {
|
|
const parsed = new URL(url);
|
|
const date = new Date().toUTCString();
|
|
const bodyBytes = Buffer.from(body, "utf-8");
|
|
const digest = "SHA-256=" + createHash("sha256").update(bodyBytes).digest("base64");
|
|
|
|
const signingString = [
|
|
`(request-target): ${method.toLowerCase()} ${parsed.pathname}`,
|
|
`host: ${parsed.host}`,
|
|
`date: ${date}`,
|
|
`digest: ${digest}`,
|
|
].join("\n");
|
|
|
|
const signer = createSign("RSA-SHA256");
|
|
signer.update(signingString);
|
|
const signature = signer.sign(privateKeyPem, "base64");
|
|
|
|
const sigHeader = [
|
|
`keyId="${KEY_ID}"`,
|
|
`algorithm="rsa-sha256"`,
|
|
`headers="(request-target) host date digest"`,
|
|
`signature="${signature}"`,
|
|
].join(",");
|
|
|
|
return { date, digest, signature: sigHeader };
|
|
}
|
|
|
|
async function deliver(inbox, body, privateKeyPem) {
|
|
const { date, digest, signature } = await signRequest("POST", inbox, body, privateKeyPem);
|
|
const parsed = new URL(inbox);
|
|
|
|
if (DRY_RUN) {
|
|
console.log(`[dry-run] POST ${inbox}`);
|
|
return { ok: true, status: 0 };
|
|
}
|
|
|
|
const res = await fetch(inbox, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/activity+json",
|
|
"Accept": "application/activity+json",
|
|
"Host": parsed.host,
|
|
"Date": date,
|
|
"Digest": digest,
|
|
"Signature": signature,
|
|
},
|
|
body,
|
|
signal: AbortSignal.timeout(15000),
|
|
});
|
|
return { ok: res.ok, status: res.status };
|
|
}
|
|
|
|
async function main() {
|
|
const client = new MongoClient(MONGO_URL);
|
|
await client.connect();
|
|
const db = client.db("indiekit");
|
|
|
|
// Get RSA private key
|
|
const keyDoc = await db.collection("ap_keys").findOne({ type: "rsa" });
|
|
if (!keyDoc?.privateKeyPem) throw new Error("RSA private key not found in ap_keys");
|
|
const privateKeyPem = keyDoc.privateKeyPem;
|
|
|
|
// Get unique inboxes
|
|
const followers = await db.collection("ap_followers").find({}).toArray();
|
|
const inboxMap = new Map();
|
|
for (const f of followers) {
|
|
const target = f.sharedInbox || f.inbox;
|
|
if (target && !inboxMap.has(target)) inboxMap.set(target, f.handle);
|
|
}
|
|
|
|
console.log(`Delivering Move to ${inboxMap.size} unique inboxes${DRY_RUN ? " [DRY RUN]" : ""}...`);
|
|
|
|
const activity = JSON.stringify({
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
id: `${NEW_ACTOR}#move-${Date.now()}`,
|
|
type: "Move",
|
|
actor: OLD_ACTOR,
|
|
object: OLD_ACTOR,
|
|
target: NEW_ACTOR,
|
|
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
});
|
|
|
|
let ok = 0, fail = 0;
|
|
for (const [inbox, handle] of inboxMap) {
|
|
try {
|
|
const result = await deliver(inbox, activity, privateKeyPem);
|
|
if (result.ok || result.status === 202) {
|
|
console.log(` ✓ ${inbox} (via ${handle})`);
|
|
ok++;
|
|
} else {
|
|
console.log(` ✗ ${inbox} — HTTP ${result.status}`);
|
|
fail++;
|
|
}
|
|
} catch (e) {
|
|
console.log(` ✗ ${inbox} — ${e.message}`);
|
|
fail++;
|
|
}
|
|
// Small delay between deliveries
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
|
|
await client.close();
|
|
console.log(`\nDone: ${ok} ok, ${fail} failed of ${inboxMap.size} targets`);
|
|
}
|
|
|
|
main().catch(e => { console.error(e); process.exit(1); });
|