~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-iroh

iroh transport for atlibs hosts: com.fedfork.exec.iroh.connect in Rust (atlibs-host) and JavaScript

0
0
main
84 lines · 3.5 KB Raw History
#!/usr/bin/env node
// Reach socialweb.computer guests and atirc over iroh. No deps; shells out to dumbpipe and ssh.
// The host pseudo-call (irohHost) is js/index.js.
//   node examples/iroh.mjs ssh <guest.json> <ssh-private-key> [command...]
//   ATIRC_TOKEN=... node examples/iroh.mjs irc <text>
import { spawn, spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

import { knownHosts, sshArgs, ticket } from "../js/index.js";
export const ATIRC_ALPN = "atirc/irc/1";

// The atirc token's `endpoint` claim is the server's iroh ticket.
export function atircTicket(token) {
	const claims = JSON.parse(Buffer.from(token.split(".")[1], "base64url"));
	return ticket(claims.endpoint);
}

function ssh(guestFile, key, command) {
	const guest = JSON.parse(readFileSync(guestFile, "utf8"));
	const dir = mkdtempSync(join(tmpdir(), "iroh-ssh-"));
	try {
		writeFileSync(join(dir, "known_hosts"), knownHosts("guest", guest.hostKeys));
		return spawnSync("ssh", sshArgs(key, guest.iroh ?? guest.address, join(dir, "known_hosts"), command), { stdio: "inherit" }).status ?? 1;
	} finally {
		rmSync(dir, { recursive: true, force: true });
	}
}

// IRC over the atirc server's iroh endpoint: register, post one line, PING/PONG round trip.
function irc(text) {
	const token = process.env.ATIRC_TOKEN;
	if (!token) throw new Error("ATIRC_TOKEN is required");
	const claims = JSON.parse(Buffer.from(token.split(".")[1], "base64url"));
	let nick = claims.nick;
	const channel = "#" + claims.handle.replace(/\./g, "_");
	const pipe = spawn("dumbpipe", ["connect", "--custom-alpn", `utf8:${ATIRC_ALPN}`, atircTicket(token)], { stdio: ["pipe", "pipe", "inherit"] });
	const send = (line) => pipe.stdin.write(line + "\r\n");
	const t0 = Date.now();
	const timer = setTimeout(() => { console.error("timeout"); pipe.kill(); process.exitCode = 1; }, 30000);
	let buf = "";
	send(`PASS ${token}`);
	send(`NICK ${nick}`);
	send(`USER ${nick} 0 * :${nick}`);
	pipe.stdout.on("data", (d) => {
		buf += d;
		let i;
		while ((i = buf.indexOf("\n")) >= 0) {
			const line = buf.slice(0, i).trim();
			buf = buf.slice(i + 1);
			const [, cmd] = line.startsWith(":") ? line.split(" ") : [null, line.split(" ")[0]];
			if (cmd === "PING") send("PONG " + line.split(" ").slice(line.startsWith(":") ? 2 : 1).join(" "));
			if (cmd === "433") send(`NICK ${(nick += "_")}`); // nick held by another session (e.g. atirc sub)
			if (cmd === "001") {
				console.log(`registered ${nick} in ${Date.now() - t0}ms`);
				send(`JOIN ${channel}`); // atirc drops PRIVMSG to a channel the session has not joined
			}
			if (cmd === "JOIN" && line.startsWith(`:${nick}!`)) {
				send(`PRIVMSG ${channel} :${text}`);
				send("PING :iroh-rt");
			}
			if (cmd === "PONG" && line.endsWith(":iroh-rt")) {
				console.log(`posted to ${channel}; PONG round trip ${Date.now() - t0}ms`);
				clearTimeout(timer);
				send("QUIT :done");
				pipe.stdin.end();
			}
			if (/^(ERROR|464|465)$/.test(cmd)) console.error(line);
		}
	});
	pipe.on("exit", () => clearTimeout(timer));
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const [cmd, ...args] = process.argv.slice(2);
	if (cmd === "ssh") process.exitCode = ssh(args[0], args[1], args.slice(2));
	else if (cmd === "irc") irc(args.join(" "));
	else {
		console.error("usage: iroh.mjs ssh <guest.json> <key> [cmd...] | irc <text>");
		process.exitCode = 2;
	}
}