feat(actions): configurable SSH user + insecure-TLS for PVE; real action whitelist + Z wrapper

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-04 21:56:33 +10:00
parent 80ad482d82
commit 169e3b6d5c
4 changed files with 50 additions and 13 deletions

View File

@@ -1,3 +1,14 @@
import { Agent } from 'undici';
// PVE uses a self-signed cert on the LAN; opt into skipping verification with
// PROXMOX_INSECURE_TLS=1 (homelab). Cached dispatcher; tests inject fetchImpl.
let insecure;
function tlsDispatcher() {
if (process.env.PROXMOX_INSECURE_TLS !== '1') return undefined;
insecure ??= new Agent({ connect: { rejectUnauthorized: false } });
return insecure;
}
// Proxmox guest power via a SCOPED PVEAPIToken (VM.PowerMgmt on whitelisted guests
// only). PVE enforces permissions server-side; this adapter never builds shell commands.
export async function powerGuest({ node, vmid, op, kindPath = 'lxc' }, {
@@ -8,7 +19,8 @@ export async function powerGuest({ node, vmid, op, kindPath = 'lxc' }, {
const url = `${apiUrl}/api2/json/nodes/${node}/${kindPath}/${vmid}/status/${op}`;
const res = await fetchImpl(url, {
method: 'POST',
headers: { Authorization: `PVEAPIToken=${token}` }
headers: { Authorization: `PVEAPIToken=${token}` },
dispatcher: tlsDispatcher()
});
if (!res.ok) throw new Error(`proxmox ${op} ${vmid}${res.status} ${await res.text?.() ?? ''}`);
const body = await res.json();

View File

@@ -7,7 +7,7 @@ const ID_RE = /^[a-z0-9-]+$/;
// 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 = 'voidact',
user = process.env.ACTIONS_SSH_USER || 'voidact',
spawnImpl = nodeSpawn
} = {}) {
if (!ID_RE.test(actionId || '')) return Promise.reject(new Error(`invalid action id: ${actionId}`));