From 6dba59db26ab203ed15db81a139f4186288cd986 Mon Sep 17 00:00:00 2001 From: valerie Date: Sun, 12 Mar 2023 09:27:31 -0400 Subject: [PATCH] update build, kill dist --- Dockerfile | 12 +-- dist/actions/deploy.js | 41 -------- dist/apis/docker.js | 225 ----------------------------------------- dist/apis/npm.js | 148 --------------------------- dist/config.js | 22 ---- dist/main.js | 15 --- dist/routes.js | 32 ------ package.json | 4 +- src/apis/docker.ts | 5 +- src/routes.ts | 6 ++ 10 files changed, 12 insertions(+), 498 deletions(-) delete mode 100644 dist/actions/deploy.js delete mode 100644 dist/apis/docker.js delete mode 100644 dist/apis/npm.js delete mode 100644 dist/config.js delete mode 100644 dist/main.js delete mode 100644 dist/routes.js diff --git a/Dockerfile b/Dockerfile index 4943043..8d05eae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,11 @@ -FROM archlinux:latest - -RUN pacman -Syu --noconfirm -RUN pacman -S --noconfirm nodejs npm -RUN node --version -RUN npm --version -RUN npm install -g npm yarn +FROM node:lts-alpine3.17 WORKDIR /app COPY ./dist /app COPY ./package.json /app/package.json COPY ./node_modules /app/node_modules -RUN find /app - -EXPOSE 6969 +EXPOSE 42069 CMD node /app/main.js diff --git a/dist/actions/deploy.js b/dist/actions/deploy.js deleted file mode 100644 index 5ad39c9..0000000 --- a/dist/actions/deploy.js +++ /dev/null @@ -1,41 +0,0 @@ -import { buildDockerImage, createContainer } from "../apis/docker.js"; -import { createCertificate, createProxyHost, deleteProxyHostByDomain, getCertificateByDomains, hasCerficateByDomains, isDomainRegistered } from "../apis/npm.js"; -import { BASE_DOMAIN, INTERNAL_ADDRESS } from "../config.js"; -export default async function deploy(options) { - const { cloneUrl, branch } = options; - const [, user, name] = /http[s]{0,1}:\/\/.*\/(.*)\/(.*)\.git/.exec(cloneUrl); - console.log(`deploying ${user}/${name}`); - const imageName = await buildDockerImage({ - user, - name, - cloneUrl, - branch - }); - const containerName = `deploy-${user}-${name}-${branch}`; - const { port } = await createContainer(imageName, containerName); - const domain = `${branch}.${name}.${user}.${BASE_DOMAIN}`; - await ensureDomainConfig(domain, port); - console.log(`${user}/${name} available at https://${domain}/`); -} -async function ensureDomainConfig(domain, port) { - // === certificate info - let certId = null; - if (await hasCerficateByDomains([domain])) { - certId = (await getCertificateByDomains([domain])).id; - console.log('certificate for', domain, 'already exists'); - } - else { - const newCert = await createCertificate([domain]); - certId = newCert.id; - } - if (await isDomainRegistered(domain)) { - await deleteProxyHostByDomain(domain); - } - await createProxyHost([domain], port, INTERNAL_ADDRESS, { - ssl_forced: true, - block_exploits: true, - allow_websocket_upgrade: true, - http2_support: true, - certificate_id: certId - }); -} diff --git a/dist/apis/docker.js b/dist/apis/docker.js deleted file mode 100644 index 9583627..0000000 --- a/dist/apis/docker.js +++ /dev/null @@ -1,225 +0,0 @@ -import Docker from "dockerode"; -import { writeFileSync } from "fs"; -import * as process from "process"; -import rimraf from "rimraf"; -import * as tmp from 'tmp'; -import getPort, { portNumbers } from 'get-port'; -import { INTERNAL_IP } from "../config.js"; -const docker = new Docker(); -export async function createContainer(imageName, containerName) { - const port = await getPort({ host: INTERNAL_IP, port: portNumbers(52300, 52399) }); - if (await isContainerNameInUse(containerName)) { - await stopAndRemoveContainerByName(containerName); - } - const containerOptions = { - Image: imageName, - name: containerName, - WorkingDir: '/app', - HostConfig: { - AutoRemove: false, - RestartPolicy: { - Name: "always" - }, - PortBindings: { - '3000/tcp': [{ HostPort: '' + port }] - } - }, - }; - try { - const container = await docker.createContainer(containerOptions); - await container.start(); - console.log("Started", containerName, "with port", port, '> 3000'); - await checkContainerStatus(container.id); - return { - id: container.id, - port - }; - } - catch (err) { - console.error("Error creating container:", err); - throw err; - } -} -export async function listContainers() { - return (await docker.listContainers()) - .map(v => v.Names[0]) - .filter((full) => { - return full.startsWith('deploy-'); - }); -} -export async function getContainerInfo(containerId) { - const docker = new Docker(); - const container = docker.getContainer(containerId); - const containerInfo = await container.inspect(); - return containerInfo; -} -export async function checkImageExists(imageName) { - const images = await docker.listImages(); - const imageExists = images.some(image => { - if (image.RepoTags) { - return image.RepoTags.includes(imageName); - } - else { - return false; - } - }); - return imageExists; -} -export async function pullImage(imageName) { - const imageExists = await checkImageExists(imageName); - if (!imageExists) { - console.log(`Pulling image ${imageName}`); - return new Promise((resolve, reject) => { - docker.pull(imageName, (err, stream) => { - if (err) { - reject(err); - } - docker.modem.followProgress(stream, (err, output) => { - if (err) { - reject(err); - } - console.log(`Image ${imageName} has been pulled`); - resolve(); - }); - }); - }); - } - else { - console.log(`Image ${imageName} already exists`); - return Promise.resolve(); - } -} -export async function attachLogs(containerId) { - const container = docker.getContainer(containerId); - const logsStream = await container.logs({ - follow: true, - stdout: true, - stderr: true, - }); - return logsStream; -} -export async function isContainerNameInUse(name) { - const containers = await docker.listContainers({ all: true }); - return containers.some((container) => container.Names.includes(`/${name}`)); -} -async function stopAndRemoveContainerByName(name) { - const containers = await docker.listContainers({ all: true }); - const matchingContainers = containers.filter(container => container.Names.includes(`/${name}`)); - if (matchingContainers.length === 0) { - throw new Error(`No container with name '${name}' was found`); - } - const stoppedContainer = docker.getContainer(matchingContainers[0].Id); - try { - await stoppedContainer.stop(); - } - catch (error) { - console.error(`Failed to stop container ${name}: ${error.message}`); - } - try { - await stoppedContainer.remove(); - console.log(`Container ${name} removed`); - } - catch (error) { - console.error(`Failed to remove container ${name}: ${error.message}`); - } -} -async function checkContainerStatus(containerId) { - const container = docker.getContainer(containerId); - const containerInfo = await container.inspect(); - const status = containerInfo.State.Status; - console.log(`Container status: ${status}`); - return status; -} -export async function runCommandInContainer(containerId, command) { - console.log('$', ...command); - const container = await docker.getContainer(containerId); - const exec = await container.exec({ - Cmd: command, - AttachStdout: true, - AttachStderr: true, - }); - const stream = await exec.start({}); - await new Promise((resolve, reject) => { - container.modem.demuxStream(stream, process.stdout, process.stderr); - stream.on('end', resolve); - stream.on('error', reject); - }); -} -export async function restartContainer(containerId) { - const docker = new Docker(); - const container = await docker.getContainer(containerId); - console.log(`Restarting container ${containerId}...`); - await container.restart(); - await checkContainerStatus(containerId); -} -export async function stopContainer(id) { - const docker = new Docker(); - const container = await docker.getContainer(id); - await container.stop(); - await checkContainerStatus(id); -} -export async function startContainer(id) { - const docker = new Docker(); - const container = await docker.getContainer(id); - await container.start(); - await checkContainerStatus(id); -} -function createDockerfile(cloneUrl, branch) { - return ` - FROM node:lts-alpine3.17 - - RUN apk add git - - WORKDIR /app - - RUN git clone "${cloneUrl}" /app --depth 1 -b ${branch} - - RUN yarn && yarn build - - EXPOSE 3000 - - CMD yarn start`.trim(); -} -export async function buildDockerImage(options) { - const { name: context } = tmp.dirSync(); - const { user, name, branch, cloneUrl } = options; - const imageName = `${user}/${name}:${branch}`; - let id = null; - try { - writeFileSync(context + '/Dockerfile', createDockerfile(cloneUrl, branch)); - const stream = await docker.buildImage({ - src: ['Dockerfile'], - context, - }, { - //@ts-ignore - nocache: true, - t: imageName - }); - //@ts-ignore - stream.on('data', d => { - const data = JSON.parse(d); - const text = data.stream ?? ''; - process.stdout.write(text); - if (text.startsWith("Successfully built")) { - const potentialId = text.split(" ")[2]; - id = potentialId; - } - }); - await new Promise((resolve) => { - //@ts-ignore - docker.modem.followProgress(stream, () => { - resolve(void 0); - }); - }); - if (id === null) - throw new Error("Unable to generate image " + imageName); - console.log("Built Image", imageName); - } - catch (e) { - console.log('do we get here?'); - await rimraf(context); - throw e; - } - await rimraf(context); - return imageName; -} diff --git a/dist/apis/npm.js b/dist/apis/npm.js deleted file mode 100644 index 23d78d5..0000000 --- a/dist/apis/npm.js +++ /dev/null @@ -1,148 +0,0 @@ -import * as console from "console"; -import { NPM_BASE_URL, NPM_EMAIL, NPM_PASSWORD } from "../config.js"; -let bearer = null; -const headers = () => { - const headers = { - Authorization: "Bearer " + bearer, - "Content-Type": "application/json" - }; - return headers; -}; -async function _post(url, body) { - const res = await fetch(NPM_BASE_URL + url, { - method: 'POST', - body: JSON.stringify(body), - headers: headers() - }); - if (Math.floor(res.status / 100) !== 2) { - throw new Error(JSON.stringify(await res.json(), null, 2)); - } - return await res.json(); -} -async function _get(url) { - const res = await fetch(NPM_BASE_URL + url, { - method: 'GET', - headers: headers() - }); - return await res.json(); -} -async function _delete(url) { - const res = await fetch(NPM_BASE_URL + url, { - method: 'DELETE', - headers: headers() - }); - return await res.json(); -} -function setToHappen(fn, date) { - var now = new Date().getTime(); - var diff = date.getTime() - now; - return setTimeout(fn, diff); -} -async function auth() { - console.log("Attempting authentication against nginx"); - const { token, expires } = await _post('/tokens', { - identity: NPM_EMAIL, - secret: NPM_PASSWORD - }); - setToHappen(auth, new Date(new Date(expires).getTime() - 60 * 1000)); - bearer = token; -} -export function getProxyHosts() { - return _get('/nginx/proxy-hosts'); -} -export const ready = auth(); -export async function isDomainRegistered(domain) { - const hosts = await getProxyHosts(); - return hosts.map(h => h.domain_names).flat().some(d => d === domain); -} -export async function getProxyHostIdByDomain(domain) { - const hosts = await getProxyHosts(); - for (const host of hosts) { - for (const d of host.domain_names) { - if (domain === d) { - return host.id; - } - } - } - return null; -} -export async function createProxyHost(domains, port, host, additionalConfig) { - const registered = (await Promise.all(domains.map(d => isDomainRegistered(d)))).some(v => v); - if (registered) { - throw new Error("Domain is already registered as a proxy host"); - } - const res = await _post('/nginx/proxy-hosts', { - domain_names: domains, - forward_scheme: "http", - forward_host: host, - forward_port: port, - block_exploits: false, - allow_websocket_upgrade: false, - access_list_id: "0", - certificate_id: 0, - ssl_forced: false, - http2_support: false, - meta: { - letsencrypt_agree: false, - dns_challenge: false - }, - advanced_config: "", - locations: [], - caching_enabled: false, - hsts_enabled: false, - hsts_subdomains: false, - ...additionalConfig - }); - console.log('Created proxy host for', domains.join(', ')); - return res; -} -export async function createCertificate(domains) { - const certReq = { - domain_names: domains, - meta: { - dns_challenge: false, - letsencrypt_agree: true, - letsencrypt_email: NPM_EMAIL - }, - provider: "letsencrypt" - }; - const res = await _post('/nginx/certificates', certReq); - return res; -} -export async function getCertificates() { - return await _get('/nginx/certificates'); -} -function compareSets(a, b) { - if (a.length !== b.length) - return false; - const _a = a.sort(); - const _b = b.sort(); - for (let i = 0; i < _a.length; i++) { - if (_a[i] !== _b[i]) - return false; - } - return true; -} -export async function deleteCertificate(id) { - const success = await _delete(`/nginx/certificate/${id}`); - if (success !== 'true') { - throw Error('Failed to delete certificate ' + id); - } -} -export async function getCertificateByDomains(domains) { - const certs = await getCertificates(); - for (const cert of certs) { - if (compareSets(cert.domain_names, domains)) { - return cert; - } - } - return null; -} -export async function hasCerficateByDomains(domains) { - return (await getCertificateByDomains(domains)) !== null; -} -export async function deleteProxyHostByDomain(domain) { - const id = await getProxyHostIdByDomain(domain); - await _delete(`/nginx/proxy-hosts/${id}`); - console.log('deleted proxy host for', domain); -} diff --git a/dist/config.js b/dist/config.js deleted file mode 100644 index 37caa6e..0000000 --- a/dist/config.js +++ /dev/null @@ -1,22 +0,0 @@ -import dotenv from 'dotenv'; -dotenv.config(); -const getFlag = (name) => { - return (!!process.env[name] && /^true$/i.test(process.env[name])); -}; -const getNumber = (name, fallback) => { - const n = parseInt(process.env[name]); - if (Number.isNaN(n) || typeof n !== 'number') - return fallback; - return n; -}; -export const URL_AUTH = getFlag('URL_AUTH'); -export const BASE_DOMAIN = process.env.BASE_DOMAIN; -export const DEPLOY_TOKEN = process.env.DEPLOY_TOKEN; -export const NPM_EMAIL = process.env.NPM_EMAIL; -export const NPM_PASSWORD = process.env.NPM_PASSWORD; -export const NPM_BASE_URL = process.env.NPM_BASE_URL; -export const INTERNAL_IP = process.env.INTERNAL_IP; -// generally you should use host unless DNS is skipped, then IP as fallback. -// but an ip is an address, so fallback to it here. -export const INTERNAL_ADDRESS = process.env.INTERNAL_ADDRESS ?? INTERNAL_IP; -export const PORT = getNumber('PORT', 6969); diff --git a/dist/main.js b/dist/main.js deleted file mode 100644 index 0559045..0000000 --- a/dist/main.js +++ /dev/null @@ -1,15 +0,0 @@ -import { listen } from './routes.js'; -import { PORT } from './config.js'; -import * as npm from './apis/npm.js'; -// const id = await createContainer('hello-world'); -// const stream = await attachLogs(id) -// stream.pipe(process.stdout); -// await new Promise(res => { -// stream.on('close', () => { -// res(void 0); -// }) -// }) -await npm.ready; -listen(PORT); -// console.log(await getProxyHosts()); -// console.log((await listContainers()).map(container => container.Names)); diff --git a/dist/routes.js b/dist/routes.js deleted file mode 100644 index 29084ca..0000000 --- a/dist/routes.js +++ /dev/null @@ -1,32 +0,0 @@ -import express from 'express'; -import deploy from './actions/deploy.js'; -import { DEPLOY_TOKEN } from './config.js'; -export function listen(port) { - const app = express(); - app.use((req, res, next) => { - let givenToken = req.query.token; - if (givenToken !== DEPLOY_TOKEN) { - res.statusCode = 403; - res.end(); - return; - } - next(); - }); - app.post('/deploy', async (req, res) => { - try { - const { cloneUrl, branch = 'HEAD' } = req.query; - res.json({ status: "Acknowledged", cloneUrl, branch }); - await deploy({ - cloneUrl: cloneUrl, - branch: branch - }); - } - catch (e) { - console.error(e); - // res.json(e); - } - }); - app.listen(port, () => { - console.log('Listening on port', port); - }); -} diff --git a/package.json b/package.json index 4d56ee8..8ce6ee8 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "type": "module", "scripts": { "dev": "nodemon -e ts,env,json -x yarn -- ts-node --esm ./src/main.ts", - "deploy": "docker-compose down && docker-compose build && docker-compose up -d", - "build": "DOCKER_BUILDKIT=0 docker-compose build" + "deploy": "docker-compose down && yarn build && docker-compose up", + "build": "yarn tsc && docker-compose build --no-cache" }, "dependencies": { "@types/dockerode": "^3.3.14", diff --git a/src/apis/docker.ts b/src/apis/docker.ts index 2bc389c..d7e627e 100644 --- a/src/apis/docker.ts +++ b/src/apis/docker.ts @@ -51,9 +51,8 @@ export async function createContainer(imageName: string, containerName: string): export async function listContainers() { return (await docker.listContainers()) - .map(v => v.Names[0]) - .filter((full: string) => { - return full.startsWith('deploy-'); + .filter((container) => { + return container.Names[0].startsWith('/deploy-'); }); } diff --git a/src/routes.ts b/src/routes.ts index cd1dd6d..c0a9c7f 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,9 +1,11 @@ import express from 'express'; import deploy from './actions/deploy.js'; +import { listContainers } from './apis/docker.js'; import { DEPLOY_TOKEN } from './config.js'; export function listen(port: number) { const app = express(); + app.use((req, res, next) => { console.log('incomming request', req.query, req.url, req.body); let givenToken = req.query.token @@ -29,6 +31,10 @@ export function listen(port: number) { } }) + app.get('/containers', async (req, res) => { + res.json(await listContainers()); + }) + app.listen(port, () => { console.log('Listening on port', port); });