Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436// Conductor, 24.03.31.14.01// Orchestrates Cloudlfare DNS updates and deployment of unikernel servers// on GCP that they point to.
// Nanos ops docs: https://docs.ops.city/ops
/* #region π TODO - [π ] Add support for the `sotce-chat` secondary instance. - [] Future Multi-Instance Management / Check to see if an instance exists under the label. If it does, then just return that instance. Otherwise make a new instance or request one to be unpaused. - [] Adapt conductor to be an http api. - [] Does the instance already exist at the requested url? - [] Yes! - [] Return information that the client can connect / success. - [] No... - [] Unpause an instance / boot an instance from the pool. - [] Assign DNS to it. - [] Return information that the client can connect / success. - [] How will HTTP traffic be blocked / the ip be masked? + Done - [x] Add crash / instance logs to GCP. - [x] Deploy `system-chat`. - [x] A. Chat System - [x] Can be a single instance for now. - [x] Sub is prefixed with chat-system. - [x] The database connects using the sub to make a record. - [x] It's a simple websocket connection.#endregion */
import { spawn } from "child_process";import { MongoClient } from "mongodb";
import "dotenv/config";
import dotenv from "dotenv";dotenv.config({ path: "./conductor.env" });
// #region π Configurationconst CLOUDFLARE_EMAIL = process.env.CLOUDFLARE_EMAIL;const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;const CLOUDFLARE_BASE_URL = "https://api.cloudflare.com/client/v4";
const MONGODB_CONNECTION_STRING = process.env.MONGODB_CONNECTION_STRING;const MONGODB_NAME = process.env.MONGODB_NAME;
const headers = { "X-Auth-Email": CLOUDFLARE_EMAIL, "X-Auth-Key": CLOUDFLARE_API_TOKEN, "Content-Type": "application/json",};
const instances = { "chat-system": { name: "chat-system", subdomain: "chat-system.aesthetic.computer", }, "chat-sotce": { name: "chat-sotce", subdomain: "chat.sotce.net", }, "chat-clock": { name: "chat-clock", subdomain: "chat-clock.aesthetic.computer", },};
const instance = instances[process.argv[2]];// #endregion
async function deploy() { const out = await gcpDeploy( process.argv[2], process.argv[3] === "false" ? false : true, );
const parsed = JSON.parse(out); let currentInstance; console.log(parsed.length, "Instance:", instance); for (let i = 0; i < parsed.length; i += 1) { console.log("Parsed name:", parsed[i].Name, parsed[i]); if (parsed[i].Name === instance.name) { currentInstance = parsed[i]; break; } } const ip = currentInstance?.PublicIps?.[0]; if (!ip) return;
// π¦ Get the deployed instances public IP and map it to a subdomain based // on the CLI argument. console.log("π§² Instance IP:", ip); const sub = instance.subdomain; //`${process.argv[2] || "chat"}.aesthetic.computer`;
// Update the A record for the subdomain. if (await createOrUpdateARecord(sub, ip)) { // Retry `curl` for about 5 seconds
async function attempt(command) { const maxAttempts = 15; let attempts = 0, success = false;
while (attempts < maxAttempts && !success) { try { await command(); success = true; // If curl succeeds, set success to true console.log("\n"); } catch (error) { attempts += 1; console.error("π΄ Attempt", attempts, "failed:", error); if (attempts < maxAttempts) { await new Promise((resolve) => setTimeout(resolve, 1000)); } } }
if (!success) { console.error("π΄ Failed after", maxAttempts, "attempts"); return; } }
console.log("πΈ Waiting for server to boot..."); console.log("π» Testing HTTPS connection...");
await attempt(async () => { const response = await fetch(`https://${sub}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } else { console.log(await response.text()); } });
console.log("𧦠Testing WebSocket connection...");
await attempt(async () => { const websocatCommand = `echo "hello" | websocat -1 wss://${sub}`; await run("bash", ["-c", websocatCommand]); }); } else { console.error("π΄ Failed to update A record for instance:", sub, "->", ip); // This should also maybe destroy the instance in certain conditions. return; }}
// π Initialization
try { const start = new Date(); const startMillis = performance.now(); console.log("-> Starting deployment:", start); await deploy(); const end = new Date(); const endMillis = performance.now(); console.log("-> Completed:", end); console.log("-> Elapsed:", (endMillis - startMillis) / 1000, "seconds");} catch (err) { console.error("π΄ Deploy failed:", err);}
// #region π₯οΈ Cloudflare API
async function fetchZones() { const response = await fetch(`${CLOUDFLARE_BASE_URL}/zones`, { headers }); return response.json();}
async function fetchZone(zoneName) { const zones = await fetchZones(); // console.log("Zones:", zones); return zones.result?.find((zone) => zone.name === zoneName);}
async function fetchARecord(zoneId, recordName) { const response = await fetch( `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records?type=A&name=${recordName}`, { headers }, ); return response.json();}
async function updateDNSRecord(zoneId, recordId, data) { const response = await fetch( `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records/${recordId}`, { method: "PUT", headers, body: JSON.stringify(data) }, ); return response.json();}
async function createDNSRecord(zoneId, data) { const response = await fetch( `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records`, { method: "POST", headers, body: JSON.stringify(data) }, ); return response.json();}
async function createOrUpdateARecord( sub = "chat-system.aesthetic.computer", ip = "3.3.3.3",) { const rootDomain = sub.split(".").slice(-2).join("."); const zoneId = (await fetchZone(rootDomain))?.id;
if (!zoneId) { console.error(`π΄ Zone ID not found for ${rootDomain}. Halting.`); return; }
const recordResponse = await fetchARecord(zoneId, sub); const record = recordResponse.result?.[0];
const data = { type: "A", name: sub, content: ip, ttl: 1, proxied: true, };
let response;
if (record) { console.log(`π‘ Updating existing record for ${sub}`); response = await updateDNSRecord(zoneId, record.id, data); } else { console.log(`π‘ Creating new record for ${sub}`); response = await createDNSRecord(zoneId, data); }
if (response.success) { console.log( `π’ Success: ${record ? "Updated" : "Created"} the record for ${sub}`, ); return true; } else { console.log( `π΄ Failed to ${record ? "update" : "create"} the record for ${sub}`, ); return false; }}
// #endregion
// #region πΊοΈ MongoDB
async function makeMongoConnection() { const client = new MongoClient(MONGODB_CONNECTION_STRING); await client.connect(); const db = client.db(MONGODB_NAME); return { client, db };}
async function storeImageNameInMongo(imageId) { const { client, db } = await makeMongoConnection();
// Use an upsert operation to ensure only one record is stored const result = await db .collection("servers") .updateOne( { _id: "lastImage" }, { $set: { name: imageId } }, { upsert: true }, );
console.log(result); console.log("π₯ Image name cached in MongoDB:", imageId); await client.close();}
async function getImageIdFromMongo() { const { client, db } = await makeMongoConnection();
const result = await db.collection("servers").findOne({ _id: "lastImage" });
await client.close(); return result ? result.name : null;}
// #endregion
// #region π Google Cloud
async function gcpDeploy(instanceName, newImage) { // A. Image Deployment // Come up with a unique timestamp identifier string for the "current" image.
// Destroy any running instances. (Will cause down-time) // await gcpDestroyInstance(instanceName); let imageName;
if (newImage) { imageName = "aesthetic-" + instanceName + "-" + new Date().getTime(); await gcpPublishImage(imageName, instanceName); // Upload new image. await storeImageNameInMongo(imageName); // Store the image name in MongoDB. } else { imageName = await getImageIdFromMongo(); // Try and retrieve from MongoDB... if (!imageName) { console.log("π‘ No existing image found... publishing a new one."); return await gcpDeploy(instanceName); } console.log("πΊοΈ Retrieved image identifier from MongoDB:", imageName); }
console.log("π‘ Deploying instance...", instanceName); // Create this new instance. // https://docs.ops.city/ops await run("ops", [ "instance", "create", imageName, "-t", "gcp", "-c", "config-gcp.json", "-i", instanceName, ]);
console.log("π‘ Deploy success:", instanceName);
// List instances. // https://docs.ops.city/ops return await run("ops", [ "instance", "list", "-j", "-t", "gcp", "-c", "config-gcp.json", ]);}
async function gcpPublishImage(imageName, instanceName) { // console.log("π‘ Deleting existing image..."); // Delete existing image. // try { // await run("ops", [ // "image", // "delete", // "aesthetic-chat", // "-t", // "gcp", // "-c", // "config-gcp.json", // "--assume-yes", // ]); // } catch (err) { // console.log("π΄ Could not delete image:", err); // }
// Upload new image. console.log("π₯οΈ Building & publishing new image:", imageName); await run("ops", [ "image", "create", "-c", "config-gcp.json", "--package", "eyberg/node:20.5.0", "-a", "chat.mjs", "-i", imageName, "-t", "gcp", "-e", "CHAT_INSTANCE=" + instanceName, ]);
// List images. await run("ops", ["image", "list", "-t", "gcp", "-c", "config-gcp.json"]);}
async function gcpDestroyInstance(instanceName) { console.log("π‘ Deleting instance:", instanceName); try { await run("ops", [ "instance", "delete", instanceName, "-t", "gcp", "-c", "config-gcp.json", ]); } catch (err) { console.error("π΄ Could not delete instance:", err); }}
// async function rebootInstances() {// TODO: I would need to find all instances running this image, and then// re-initialize / rebuild them?// }
//#endregion
// βοΈ Utilities
function run(command, args = []) { return new Promise((resolve, reject) => { const childProcess = spawn(command, args, { shell: true, env: { ...process.env }, // Inherit parent environment variables });
// console.log("Inherited environment from:", process.env);
let lastOut = "";
childProcess.stdout.on("data", (data) => { process.stdout.write(data); lastOut += data.toString(); // Append the data to lastOut });
childProcess.stderr.on("data", (data) => { console.error(`π΄ ${data}`); });
childProcess.on("close", (code) => { if (code === 0) { resolve(lastOut); // Resolve the promise with lastOut } else { reject(`Child process exit code ${code}`); } }); });}