/** * Sandbox cleanup — deletes all non-protected sandboxes from the APIX registry. * * Protected sandboxes (never deleted): * - tier = DEMO (server-enforced, returns 403) * - name matching KEEP_NAMES below * * Usage: * APIX_ADMIN_KEY= node scripts/cleanup-sandboxes.mjs [--dry-run] * * The admin key is in /opt/apix/.env on the VPS (APIX_API_KEY=...). */ const REGISTRY_ROOT = 'https://api-index.org'; const KEEP_NAMES = new Set(['apix-demo-ecosystem']); const DRY_RUN = process.argv.includes('--dry-run'); const ADMIN_KEY = process.env.APIX_ADMIN_KEY; if (!ADMIN_KEY) { console.error('Error: set APIX_ADMIN_KEY environment variable'); process.exit(1); } async function adminGet(path) { const res = await fetch(`${REGISTRY_ROOT}${path}`, { headers: { Accept: 'application/json', 'X-Admin-Key': ADMIN_KEY }, }); if (!res.ok) throw new Error(`GET ${path} → ${res.status}: ${await res.text()}`); return res.json(); } async function adminDelete(path) { const res = await fetch(`${REGISTRY_ROOT}${path}`, { method: 'DELETE', headers: { 'X-Admin-Key': ADMIN_KEY }, }); if (res.status === 204) return; throw new Error(`DELETE ${path} → ${res.status}: ${await res.text()}`); } async function main() { console.log(`\n APIX Sandbox Cleanup${DRY_RUN ? ' [DRY RUN — nothing will be deleted]' : ''}\n`); let page = 0; const toDelete = []; const keep = []; while (true) { const data = await adminGet(`/admin/sandboxes?page=${page}&size=50`); const sandboxes = data.sandboxes ?? data.items ?? data; if (!Array.isArray(sandboxes) || sandboxes.length === 0) break; for (const sb of sandboxes) { const name = sb.name ?? sb.sandboxName ?? '—'; const tier = sb.tier ?? '?'; const id = sb.sandboxId ?? sb.id; if (KEEP_NAMES.has(name) || tier === 'DEMO') { keep.push({ id, name, tier }); } else { toDelete.push({ id, name, tier }); } } if (sandboxes.length < 50) break; page++; } console.log(` Keeping (${keep.length}):`); for (const sb of keep) console.log(` ✓ ${sb.name.padEnd(36)} ${sb.tier} ${sb.id}`); console.log(`\n Deleting (${toDelete.length}):`); for (const sb of toDelete) { const label = `${sb.name.padEnd(36)} ${sb.tier} ${sb.id}`; if (DRY_RUN) { console.log(` ○ ${label} [skipped — dry run]`); } else { try { await adminDelete(`/admin/sandboxes/${sb.id}`); console.log(` ✗ ${label} deleted`); } catch (err) { console.log(` ! ${label} ERROR: ${err.message}`); } } } console.log('\n Done.\n'); } main().catch(err => { console.error('\n Error:', err.message); process.exit(1); });