init
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// This assumes an installation of Docker exists on the given host
|
||||
// and that host is configured to accept our SSH key
|
||||
import config from "../config.ts";
|
||||
import { VMManager, type VM } from "./base.ts";
|
||||
import { imageName } from "./utils.ts";
|
||||
import fs from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { NodeSSH } from "node-ssh";
|
||||
|
||||
export class Docker extends VMManager {
|
||||
// TODO support multiple Docker providers in the pool config with same region
|
||||
size = "";
|
||||
largeSize = "";
|
||||
minRetries = 0;
|
||||
reuseVMs = false;
|
||||
id = "Docker";
|
||||
ssh: NodeSSH | undefined = undefined;
|
||||
imageId = imageName;
|
||||
|
||||
getSSH = async () => {
|
||||
if (this.ssh && this.ssh.isConnected()) {
|
||||
return this.ssh;
|
||||
}
|
||||
const sshConfig = {
|
||||
username: config.DOCKER_VM_HOST_SSH_USER,
|
||||
host: this.hostname,
|
||||
// The private key the Docker host is configured to accept
|
||||
privateKey: config.DOCKER_VM_HOST_SSH_KEY_BASE64
|
||||
? Buffer.from(config.DOCKER_VM_HOST_SSH_KEY_BASE64, "base64").toString()
|
||||
: fs.readFileSync(homedir() + "/.ssh/id_rsa").toString(),
|
||||
};
|
||||
this.ssh = new NodeSSH();
|
||||
await this.ssh.connect(sshConfig);
|
||||
return this.ssh;
|
||||
};
|
||||
|
||||
startVM = async (name: string) => {
|
||||
const tag = this.getTag();
|
||||
const conn = await this.getSSH();
|
||||
// If in development, have neko share the same SSL cert as the other services
|
||||
// If in production, they are probably on different hosts and neko is behind a reverse proxy for SSL termination
|
||||
const sslEnv =
|
||||
config.NODE_ENV === "development" &&
|
||||
config.SSL_KEY_FILE &&
|
||||
config.SSL_CRT_FILE
|
||||
? `-e NEKO_KEY="${config.SSL_KEY_FILE}" -e NEKO_CERT="${config.SSL_CRT_FILE}"`
|
||||
: "";
|
||||
const { stdout, stderr } = await conn.execCommand(
|
||||
`
|
||||
#!/bin/bash
|
||||
set -e
|
||||
PORT=$(comm -23 <(seq 5000 5063 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | sort -n | head -n 1)
|
||||
INDEX=$(($PORT - 5000))
|
||||
UDP_START=$((59000+$INDEX*100))
|
||||
UDP_END=$((59099+$INDEX*100))
|
||||
docker run -d --rm --name=${name} --memory="2g" --cpus="2" -p $PORT:$PORT -p $UDP_START-$UDP_END:$UDP_START-$UDP_END/udp -v /etc/letsencrypt:/etc/letsencrypt -l ${tag} -l index=$INDEX --log-opt max-size=1g --shm-size=1g --cap-add="SYS_ADMIN" ${sslEnv} -e DISPLAY=":99.0" -e NEKO_PASSWORD=${name} -e NEKO_PASSWORD_ADMIN=${name} -e NEKO_ADMIN_KEY=${config.VBROWSER_ADMIN_KEY} -e NEKO_BIND=":$PORT" -e NEKO_EPR=":$UDP_START-$UDP_END" -e NEKO_H264="1" ${imageName}
|
||||
`,
|
||||
);
|
||||
console.log(stdout, stderr);
|
||||
return stdout.trim();
|
||||
};
|
||||
|
||||
terminateVM = async (id: string) => {
|
||||
const conn = await this.getSSH();
|
||||
const { stdout, stderr } = await conn.execCommand(`docker rm -fv ${id}`);
|
||||
console.log(stdout, stderr);
|
||||
return;
|
||||
};
|
||||
|
||||
rebootVM = async (id: string) => {
|
||||
// Docker containers aren't set to reuse, so do nothing (reset will terminate)
|
||||
};
|
||||
|
||||
reimageVM = async (id: string) => {
|
||||
const conn = await this.getSSH();
|
||||
const { stdout, stderr } = await conn.execCommand(
|
||||
`docker pull ${this.imageId}`,
|
||||
);
|
||||
console.log(stdout, stderr);
|
||||
// The container is out of date. Delete it
|
||||
this.terminateVMWrapper(id);
|
||||
return;
|
||||
};
|
||||
|
||||
getVM = async (id: string) => {
|
||||
const conn = await this.getSSH();
|
||||
const { stdout } = await conn.execCommand(`docker inspect ${id}`);
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(stdout)[0];
|
||||
if (!data) {
|
||||
throw new Error("no container with this ID found");
|
||||
}
|
||||
} catch {
|
||||
console.warn(stdout);
|
||||
throw new Error("failed to parse json");
|
||||
}
|
||||
let server = this.mapServerObject(data);
|
||||
return server;
|
||||
};
|
||||
|
||||
listVMs = async (filter: string) => {
|
||||
const conn = await this.getSSH();
|
||||
const listCmd = `docker inspect $(docker ps --filter label=${filter} --quiet --no-trunc)`;
|
||||
const { stdout } = await conn.execCommand(listCmd);
|
||||
if (!stdout) {
|
||||
return [];
|
||||
}
|
||||
let data = [];
|
||||
try {
|
||||
data = JSON.parse(stdout);
|
||||
} catch (e) {
|
||||
console.warn(stdout);
|
||||
throw new Error("failed to parse json");
|
||||
}
|
||||
return data.map(this.mapServerObject);
|
||||
};
|
||||
|
||||
powerOn = async (id: string) => {};
|
||||
|
||||
attachToNetwork = async (id: string) => {};
|
||||
|
||||
updateSnapshot = async () => {
|
||||
return "";
|
||||
};
|
||||
|
||||
mapServerObject = (server: any): VM => ({
|
||||
id: server.Id,
|
||||
host: `${this.hostname}:${5000 + Number(server.Config?.Labels?.index)}`,
|
||||
provider: this.id,
|
||||
large: this.isLarge,
|
||||
region: this.region,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user