battlelog/createService.ts

167 lines
4.6 KiB
TypeScript

import {parse, stringify} from 'yaml';
(async () => {
const serviceName = prompt('Service name? (This is used by the router to determine the route prefix)')?.replace('-service', '');
if (!serviceName) return;
type permissionShorts = 'r' | 'w' | 'x' | 'n';
type permissionNames = 'read' | 'write' | 'execute' | 'run' | 'net';
const getPermissionByNameOrShort = (name: permissionNames | permissionShorts | string) => {
switch (name) {
case "read":
case "r":
return {
name: 'read',
denoPerm: '--allow-read'
};
case "write":
case "w":
return {
name: 'write',
denoPerm: '--allow-write'
};
case "execute":
case "run":
case "x":
return {
name: 'run',
denoPerm: '--allow-run'
};
case "net":
case "n":
return {
name: 'net',
denoPerm: '--allow-net'
};
default:
return {
name: 'custom-' + name,
denoPerm: '--allow-' + name
}
}
}
const perms = [];
if (Deno.args[0]) {
const permShort = Deno.args[0].split('') as permissionShorts[];
for (const short of permShort) {
perms.push(getPermissionByNameOrShort(short));
}
} else if (confirm('Permissions not specified, would you like to add specific permissions?')) {
let permsPrompt: string | null = '';
while (!permsPrompt) {
permsPrompt = prompt('Please enter the permission short code or names of permissions you would like');
if (!permsPrompt) continue
let permNames;
if (permsPrompt?.match(' ')) {
permNames = permsPrompt.split(' ');
} else {
permNames = permsPrompt.split('');
}
for (const perm of permNames) {
perms.push(getPermissionByNameOrShort(perm));
}
}
}
console.log(`Service will run with following permissions: ${perms.map(p => p.name).join()}`);
if (!perms.find(p => p.name === 'net')) {
if (!confirm('Service does not have permissions to "net", is this correct?'))
perms.push(getPermissionByNameOrShort('net'));
}
const serviceFile = `./${serviceName}-service/`;
let port;
await Deno.mkdir(serviceFile);
port = await getAvailablePort();
if (perms.find(p => p.name === 'net')) {
await Deno.writeTextFile(serviceFile + 'port', port)
}
await Deno.writeTextFile(serviceFile + 'main.ts', `
import { CGGService } from 'cgg/Application.ts';
import { Router } from 'oak';
const app = new CGGService({ prefix: '/${serviceName}' });
app.route(new Router()
.get('/', ctx => ctx.response.body = '${serviceName} service')
);
app.start();
console.log('${serviceName} service running on ' + Deno.args.at(0));
`);
await Deno.writeTextFile(serviceFile + 'perms', perms.map(p => p.denoPerm).join('\n'));
await Deno.writeTextFile(serviceFile + 'prefix', serviceName);
if (confirm('Containerize this service?')) {
await Deno.writeTextFile(serviceFile + 'Dockerfile', `
FROM denoland/deno:1.33.2
${port ? 'EXPOSE ' + port : ''}
WORKDIR /${serviceName}
ADD ${serviceFile} .
ADD ./deno.jsonc .
ADD ./secrets.json .
ADD ./key.txt .
ADD ./common ./common
ADD ./lib ./lib
ADD ./middleware ./middleware
CMD ["run", "${perms.join('", "')}", main.ts${port ? `, "${port}"` : ''}]
`);
const dockerCompose: any = parse(await Deno.readTextFile('./docker-compose.yml'));
dockerCompose['services'][serviceName] = {
build: {
context: "./",
dockerfile: serviceFile + 'Dockerfile'
}
}
await Deno.writeTextFile('./docker-compose.yml', stringify(dockerCompose))
}
if (confirm('Does this service need DB access?'))
await Deno.writeTextFile(serviceFile + 'data.ts', `
import mongoose from 'mongoose';
import { configDatabase } from 'lib/data.ts';
configDatabase(mongoose);
`)
})();
async function getAvailablePort() {
let start = 6900;
const missingPorts = [];
const takenPorts = [];
for await (const dirEntry of Deno.readDir('.')) {
if (dirEntry.isFile || !dirEntry.name.includes('-service')) continue;
const dir = './' + dirEntry.name + '/';
if (!Array.from(Deno.readDirSync(dir)).find(e => e.name === 'port')) {
missingPorts.push(dir);
continue;
}
takenPorts.push(Number(await Deno.readTextFile(dir + 'port')));
}
takenPorts.sort();
for (const port of takenPorts) {
if (start === port) start++;
}
for (const missing of missingPorts) {
Deno.writeTextFile(missing + 'port', start.toString());
takenPorts.push(start);
while(takenPorts.includes(start)) start++;
}
return start.toString();
}