import { spawn as nodeSpawn } from 'node:child_process'; const ID_RE = /^[a-z0-9-]+$/; // Runs `ssh voidact@ `. The host's authorized_keys pins a forced // wrapper (deploy/void-act) that maps the id → systemctl restart from // its OWN whitelist. We pass ONLY the id as a single argv element — no shell. export function restartService({ ip, actionId }, { keyPath = process.env.ACTIONS_SSH_KEY, user = process.env.ACTIONS_SSH_USER || 'voidact', spawnImpl = nodeSpawn } = {}) { if (!ID_RE.test(actionId || '')) return Promise.reject(new Error(`invalid action id: ${actionId}`)); const args = ['-i', keyPath, '-o', 'BatchMode=yes', '-o', 'StrictHostKeyChecking=accept-new', `${user}@${ip}`, actionId]; return new Promise((resolve, reject) => { const child = spawnImpl('ssh', args); let out = '', err = ''; child.stdout.on('data', (d) => { out += d; }); child.stderr.on('data', (d) => { err += d; }); child.on('close', (code) => code === 0 ? resolve({ ok: true, output: out.trim() }) : reject(new Error(`ssh ${actionId} → exit ${code}: ${err.trim()}`))); child.on('error', reject); }); }