rspace-online/modules/forum/lib/hetzner.ts

81 lines
1.9 KiB
TypeScript

/**
* Hetzner Cloud API client for VPS provisioning.
*/
const HETZNER_API = "https://api.hetzner.cloud/v1";
function headers(): Record<string, string> {
const token = process.env.HETZNER_API_TOKEN;
if (!token) throw new Error("HETZNER_API_TOKEN not set");
return {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
};
}
export interface HetznerServer {
id: number;
name: string;
status: string;
public_net: {
ipv4: { ip: string };
ipv6: { ip: string };
};
server_type: { name: string };
datacenter: { name: string };
}
export async function createServer(opts: {
name: string;
serverType: string;
region: string;
userData: string;
}): Promise<{ serverId: string; ip: string }> {
const res = await fetch(`${HETZNER_API}/servers`, {
method: "POST",
headers: headers(),
body: JSON.stringify({
name: opts.name,
server_type: opts.serverType,
location: opts.region,
image: "ubuntu-22.04",
user_data: opts.userData,
start_after_create: true,
}),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Hetzner create failed: ${res.status} ${err}`);
}
const data = await res.json();
return {
serverId: String(data.server.id),
ip: data.server.public_net.ipv4.ip,
};
}
export async function getServer(serverId: string): Promise<HetznerServer | null> {
const res = await fetch(`${HETZNER_API}/servers/${serverId}`, { headers: headers() });
if (!res.ok) return null;
const data = await res.json();
return data.server;
}
export async function deleteServer(serverId: string): Promise<boolean> {
const res = await fetch(`${HETZNER_API}/servers/${serverId}`, {
method: "DELETE",
headers: headers(),
});
return res.ok;
}
export async function serverAction(serverId: string, action: "poweron" | "poweroff" | "reboot"): Promise<boolean> {
const res = await fetch(`${HETZNER_API}/servers/${serverId}/actions/${action}`, {
method: "POST",
headers: headers(),
});
return res.ok;
}