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).
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Patch @rmdes/indiekit-endpoint-activitypub federation-setup.js:
|
||||
* - personOptions.alsoKnownAs → personOptions.aliases (Fedify uses 'aliases')
|
||||
* - Add movedTo → personOptions.successor (Fedify uses 'successor')
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const pkg = require.resolve("@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js");
|
||||
let src = readFileSync(pkg, "utf-8");
|
||||
|
||||
const MARKER = "// patch-actor-aliases-successor: applied";
|
||||
if (src.includes(MARKER)) {
|
||||
console.log("[patch-actor-aliases-successor] Already applied");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Fix 1: alsoKnownAs → aliases
|
||||
const OLD_ALIASES = ` if (profile.alsoKnownAs?.length > 0) {
|
||||
personOptions.alsoKnownAs = profile.alsoKnownAs.map((u) => new URL(u));
|
||||
}`;
|
||||
const NEW_ALIASES = ` if (profile.alsoKnownAs?.length > 0) {
|
||||
personOptions.aliases = profile.alsoKnownAs.map((u) => new URL(u));
|
||||
}
|
||||
if (profile.movedTo) {
|
||||
personOptions.successor = new URL(profile.movedTo);
|
||||
}
|
||||
${MARKER}`;
|
||||
|
||||
if (!src.includes(OLD_ALIASES)) {
|
||||
console.error("[patch-actor-aliases-successor] Could not find target block — check federation-setup.js version");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
src = src.replace(OLD_ALIASES, NEW_ALIASES);
|
||||
writeFileSync(pkg, src, "utf-8");
|
||||
console.log("[patch-actor-aliases-successor] Applied: alsoKnownAs→aliases, added movedTo→successor");
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 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); });
|
||||
Reference in New Issue
Block a user