feat: add orphan EC2 cleanup on startup, fix SetupWizard routing, fix SSH key gen

- On startup, scan for EC2 instances with pp:managed=true and terminate any orphans
- SetupWizard now renders as full-page (no Layout wrapper)
- Fixed SSH RSA key generation (generateKeyPairSync with spki format)
- Removed unused import in orphanCleanup.ts
- Frontend rebuild with App.tsx routing fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 00:20:36 +02:00
parent 40d484bede
commit 6434be8bed
13 changed files with 1151 additions and 93 deletions
+37 -42
View File
@@ -84,41 +84,29 @@ async function validateAwsCredentials(user) {
}
}
function generateSshKeyPair() {
const { privateKey, publicKey } = (0, import_crypto.generateKeyPairSync)("rsa", {
const { privateKey: privKeyPem, publicKey: pubKeyPem } = (0, import_crypto.generateKeyPairSync)("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "pem" },
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs1", format: "pem" }
});
const pubKeyOpenSsh = rsaPemToOpenSsh(publicKey);
return { privateKey, publicKey: pubKeyOpenSsh };
}
function rsaPemToOpenSsh(pem) {
const { publicKeyEncoding } = (0, import_crypto.generateKeyPairSync)("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs8", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" }
});
void publicKeyEncoding;
const der = Buffer.from(
pem.replace(/-----BEGIN RSA PUBLIC KEY-----/, "").replace(/-----END RSA PUBLIC KEY-----/, "").replace(/\n/g, ""),
"base64"
);
const type = Buffer.from("ssh-rsa");
function encodeBuffer(buf) {
const len = Buffer.allocUnsafe(4);
len.writeUInt32BE(buf.length, 0);
return Buffer.concat([len, buf]);
const pubKeyObj = (0, import_crypto.createPublicKey)(pubKeyPem);
const pubKeyDer = pubKeyObj.export({ type: "spki", format: "der" });
function sshEncodeBuffer(buf) {
const lenBuf = Buffer.allocUnsafe(4);
lenBuf.writeUInt32BE(buf.length, 0);
return Buffer.concat([lenBuf, buf]);
}
const typeEncoded = encodeBuffer(type);
const rsaKeyData = der;
const base64Key = Buffer.concat([typeEncoded, rsaKeyData]).toString("base64");
return `ssh-rsa ${base64Key} pp-generated`;
const keyTypeStr = Buffer.from("ssh-rsa");
const keyTypeEncoded = sshEncodeBuffer(keyTypeStr);
const base64Encoded = pubKeyDer.toString("base64");
const openSshKey = `ssh-rsa ${Buffer.concat([keyTypeEncoded]).toString("base64")} pp-generated`;
return { privateKey: privKeyPem, publicKeyOpenssh: `ssh-rsa ${base64Encoded} pp-generated` };
}
async function generateAndImportKeyPair(ec2, keyName) {
const { privateKey, publicKey } = generateSshKeyPair();
const { privateKey, publicKeyOpenssh } = generateSshKeyPair();
await ec2.send(new import_client_ec2.ImportKeyPairCommand({
KeyName: keyName,
PublicKeyMaterial: Buffer.from(publicKey)
PublicKeyMaterial: Buffer.from(publicKeyOpenssh)
}));
return { privateKey };
}
@@ -134,22 +122,25 @@ async function createPreviewSecurityGroup(ec2, groupName, port) {
Description: `PP Preview security group: ${groupName}`
}));
const groupId = res.GroupId;
const ingress = [
{
IpProtocol: "tcp",
FromPort: 22,
ToPort: 22,
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
}
];
if (port !== 22) {
ingress.push({
IpProtocol: "tcp",
FromPort: port,
ToPort: port,
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
});
}
await ec2.send(new import_client_ec2.AuthorizeSecurityGroupIngressCommand({
GroupId: groupId,
IpPermissions: [
{
IpProtocol: "tcp",
FromPort: 22,
ToPort: 22,
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
},
{
IpProtocol: "tcp",
FromPort: port,
ToPort: port,
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
}
]
IpPermissions: ingress
}));
return groupId;
}
@@ -216,6 +207,7 @@ async function deleteSecurityGroupAws(ec2, groupName) {
}));
const groupId = describe.SecurityGroups?.[0]?.GroupId;
if (groupId) {
await sleep(5e3);
await ec2.send(new import_client_ec2.DeleteSecurityGroupCommand({ GroupId: groupId }));
}
} catch (e) {
@@ -224,7 +216,10 @@ async function deleteSecurityGroupAws(ec2, groupName) {
}
async function describeAllManagedInstances(ec2) {
const res = await ec2.send(new import_client_ec2.DescribeInstancesCommand({
Filters: [{ Name: "tag:pp:managed", Values: ["true"] }, { Name: "instance-state-name", Values: ["running", "pending", "stopping", "stopped"] }]
Filters: [
{ Name: "tag:pp:managed", Values: ["true"] },
{ Name: "instance-state-name", Values: ["running", "pending", "stopping"] }
]
}));
return (res.Reservations ?? []).flatMap((r) => r.Instances ?? []);
}