mirror your GitHub repos to tangled.org automatically
1

Configure Feed

Select the types of activity you want to include in your feed.

1import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' 2import os from 'node:os' 3import path from 'node:path' 4import { sql } from 'drizzle-orm' 5import { sshKey } from '../db/schema' 6import { useDb } from './db' 7import { decrypt } from './encryption' 8import { pkcs8ToOpenSshPrivate } from './ssh-keypair' 9 10/** 11 * Materialise the install's SSH private key as an OpenSSH-format file on disk 12 * and return: 13 * - `args`: the ssh option list (`-i <key> -o ...`) ready to splice into a 14 * `spawn('ssh', [...args, target, command])` call 15 * - a `cleanup()` callback that synchronously removes the temp dir 16 * 17 * The key file lives in `os.tmpdir()` with 0600 perms, has a random filename 18 * (collision-resistant for concurrent worker invocations on the same instance), 19 * and is removed in `cleanup()`. Callers must invoke `cleanup()` in a `finally` 20 * — leaking the key on disk is the worst failure mode here. 21 * 22 * Host key checking: tangled knots are addressed by hostname; v1 uses 23 * `StrictHostKeyChecking=accept-new` (TOFU) with a per-call empty known_hosts, 24 * which is effectively "trust the DNS for the configured knot". A future 25 * commit can ship pinned host keys for the canonical knots once we know what 26 * those are. 27 */ 28export async function loadSshArgsForInstall(installationId: number): Promise<{ 29 args: string[] 30 cleanup: () => void 31}> { 32 const db = useDb() 33 const rows = await db.select({ 34 privateKeyCiphertext: sshKey.privateKeyCiphertext, 35 privateKeyNonce: sshKey.privateKeyNonce, 36 }) 37 .from(sshKey) 38 .where(sql`${sshKey.installationId} = ${installationId}`) 39 .limit(1) 40 41 if (rows.length === 0) { 42 throw new Error(`no ssh key for installation ${installationId}`) 43 } 44 const row = rows[0]! 45 46 const pem = decrypt(row.privateKeyCiphertext, row.privateKeyNonce) 47 const openSsh = pkcs8ToOpenSshPrivate(pem, `synchub.to/${installationId}`) 48 49 // Distinct dir per call so concurrent pushes within one process don't race. 50 const dir = mkdtempSync(path.join(os.tmpdir(), 'synchub-ssh-')) 51 const keyPath = path.join(dir, 'id_ed25519') 52 const knownHostsPath = path.join(dir, 'known_hosts') 53 54 writeFileSync(keyPath, openSsh, { mode: 0o600 }) 55 chmodSync(keyPath, 0o600) 56 writeFileSync(knownHostsPath, '', { mode: 0o600 }) 57 58 const args = [ 59 '-i', keyPath, 60 '-o', `UserKnownHostsFile=${knownHostsPath}`, 61 '-o', 'StrictHostKeyChecking=accept-new', 62 '-o', 'IdentitiesOnly=yes', 63 '-o', 'BatchMode=yes', 64 '-o', 'ConnectTimeout=15', 65 ] 66 67 return { 68 args, 69 cleanup: () => { 70 try { 71 rmSync(dir, { recursive: true, force: true }) 72 } 73 catch { 74 // best-effort; the temp dir will be cleaned up on process restart. 75 } 76 }, 77 } 78}