Files
indiekit-server/scripts/send-move-activity.mjs
Sven 31d21ef3c2
Deploy Indiekit Server / deploy (push) Failing after 1m46s
Track existing scripts not yet in git
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).
2026-05-03 12:40:30 +02:00

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); });