feat: add corebuild worker
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
.nogit/
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
dist_*/
|
||||||
|
*.log
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Lossless GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "@serve.zone/corebuild",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": false,
|
||||||
|
"description": "Build worker for serve.zone image and ISO artifact generation.",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./dist_ts/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsbuild tsfolders --allowimplicitany",
|
||||||
|
"start": "node dist_ts/index.js",
|
||||||
|
"startTs": "tsrun ts/index.ts",
|
||||||
|
"test": "pnpm run build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@push.rocks/smartbucket": "^3.3.10",
|
||||||
|
"@tsclass/tsclass": "^9.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@git.zone/tsbuild": "^4.4.0",
|
||||||
|
"@git.zone/tsrun": "^2.0.2",
|
||||||
|
"@types/node": "^25.6.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"dist_ts/**/*",
|
||||||
|
"readme.md",
|
||||||
|
"license.md"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://git@code.foss.global:29419/serve.zone/corebuild.git"
|
||||||
|
},
|
||||||
|
"author": "Lossless GmbH",
|
||||||
|
"license": "MIT",
|
||||||
|
"packageManager": "pnpm@10.28.2"
|
||||||
|
}
|
||||||
Generated
+4085
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
# @serve.zone/corebuild
|
||||||
|
|
||||||
|
CoreBuild is the serve.zone worker service for heavy artifact generation jobs such as BaseOS ISO builds.
|
||||||
|
|
||||||
|
Cloudly owns orchestration and user-facing downloads. CoreBuild runs on suitable builder nodes, executes `isocreator`, uploads artifacts to S3-compatible storage, and returns artifact metadata to Cloudly.
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
Required environment:
|
||||||
|
|
||||||
|
- `COREBUILD_PORT`: HTTP port, defaults to `3060`.
|
||||||
|
- `COREBUILD_TOKEN`: shared worker token expected from Cloudly.
|
||||||
|
- `COREBUILD_WORKDIR`: temp workspace, defaults to `.nogit/workdir`.
|
||||||
|
- `ISO_CREATOR_COMMAND`: command used to run isocreator, defaults to `isocreator`.
|
||||||
|
|
||||||
|
For local development against the workspace checkout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ISO_CREATOR_COMMAND="deno run --allow-all ../isocreator/mod.ts" pnpm run startTs
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- `GET /health`
|
||||||
|
- `GET /corebuild/v1/capabilities`
|
||||||
|
- `POST /corebuild/v1/jobs/baseos-image`
|
||||||
|
|
||||||
|
The BaseOS image job expects Cloudly to provide the S3 descriptor and a one-time provisioning token. CoreBuild never stores those values beyond the build workspace.
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import * as crypto from 'node:crypto';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as fsp from 'node:fs/promises';
|
||||||
|
import * as os from 'node:os';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import type {
|
||||||
|
IBaseOsImageArtifactResult,
|
||||||
|
IBaseOsImageJob,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
|
export interface IBaseOsImageBuilderOptions {
|
||||||
|
workdir: string;
|
||||||
|
isoCreatorCommand: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseOsImageBuilder {
|
||||||
|
constructor(private options: IBaseOsImageBuilderOptions) {}
|
||||||
|
|
||||||
|
public async build(jobArg: IBaseOsImageJob): Promise<{
|
||||||
|
artifact: IBaseOsImageArtifactResult;
|
||||||
|
logs: string[];
|
||||||
|
}> {
|
||||||
|
if (jobArg.architecture === 'rpi') {
|
||||||
|
throw new Error('Raspberry Pi image builds require a raw-image preset and are not supported by the current isocreator ISO pipeline yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const logs: string[] = [];
|
||||||
|
const jobDir = path.join(this.options.workdir, jobArg.id);
|
||||||
|
const outputDir = path.join(jobDir, 'output');
|
||||||
|
await fsp.rm(jobDir, { recursive: true, force: true });
|
||||||
|
await fsp.mkdir(outputDir, { recursive: true });
|
||||||
|
|
||||||
|
const filename = jobArg.architecture === 'amd64' ? 'baseos.iso' : 'baseos-arm64.iso';
|
||||||
|
const outputPath = path.join(outputDir, filename);
|
||||||
|
const configPath = path.join(jobDir, 'isocreator.config.json');
|
||||||
|
await fsp.writeFile(configPath, `${JSON.stringify(this.createIsoCreatorConfig(jobArg, outputDir, filename), null, 2)}\n`);
|
||||||
|
|
||||||
|
logs.push(`Starting isocreator for ${jobArg.architecture}`);
|
||||||
|
await this.runIsoCreator(configPath, logs);
|
||||||
|
|
||||||
|
const stat = await fsp.stat(outputPath);
|
||||||
|
const sha256 = await this.sha256File(outputPath);
|
||||||
|
await this.uploadArtifact(jobArg, outputPath, logs);
|
||||||
|
|
||||||
|
await fsp.rm(jobDir, { recursive: true, force: true }).catch(() => undefined);
|
||||||
|
return {
|
||||||
|
artifact: {
|
||||||
|
bucketName: jobArg.s3Descriptor.bucketName,
|
||||||
|
key: jobArg.artifactKey,
|
||||||
|
filename,
|
||||||
|
contentType: 'application/x-iso9660-image',
|
||||||
|
size: stat.size,
|
||||||
|
sha256,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
},
|
||||||
|
logs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createIsoCreatorConfig(jobArg: IBaseOsImageJob, outputDirArg: string, filenameArg: string) {
|
||||||
|
const installScript = this.createBaseOsInstallScript();
|
||||||
|
const serviceFile = this.createBaseOsServiceFile();
|
||||||
|
const envFile = this.createBaseOsEnvFile(jobArg);
|
||||||
|
return {
|
||||||
|
version: '1.0',
|
||||||
|
...(jobArg.sourceImageUrl
|
||||||
|
? {
|
||||||
|
source: {
|
||||||
|
type: 'url',
|
||||||
|
url: jobArg.sourceImageUrl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
iso: {
|
||||||
|
ubuntu_version: jobArg.ubuntuVersion || '24.04',
|
||||||
|
architecture: jobArg.architecture === 'amd64' ? 'amd64' : 'arm64',
|
||||||
|
flavor: 'server',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: filenameArg,
|
||||||
|
path: outputDirArg,
|
||||||
|
},
|
||||||
|
...(jobArg.wifi?.ssid
|
||||||
|
? {
|
||||||
|
network: {
|
||||||
|
wifi: jobArg.wifi,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
cloud_init: {
|
||||||
|
hostname: jobArg.hostname || `baseos-${jobArg.id.slice(0, 8)}`,
|
||||||
|
users: jobArg.sshPublicKey
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'baseos',
|
||||||
|
ssh_authorized_keys: [jobArg.sshPublicKey],
|
||||||
|
sudo: 'ALL=(ALL) NOPASSWD:ALL',
|
||||||
|
shell: '/bin/bash',
|
||||||
|
groups: ['sudo'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
|
package_update: true,
|
||||||
|
packages: ['curl', 'git', 'ca-certificates'],
|
||||||
|
write_files: [
|
||||||
|
{
|
||||||
|
path: '/etc/baseos/baserunner.env',
|
||||||
|
owner: 'root:root',
|
||||||
|
permissions: '0600',
|
||||||
|
content: envFile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/etc/systemd/system/baseos-baserunner.service',
|
||||||
|
owner: 'root:root',
|
||||||
|
permissions: '0644',
|
||||||
|
content: serviceFile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/usr/local/bin/install-baseos.sh',
|
||||||
|
owner: 'root:root',
|
||||||
|
permissions: '0755',
|
||||||
|
content: installScript,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
runcmd: [
|
||||||
|
'mkdir -p /var/lib/baseos /opt/baseos',
|
||||||
|
'/usr/local/bin/install-baseos.sh',
|
||||||
|
'systemctl daemon-reload',
|
||||||
|
'systemctl enable baseos-baserunner.service',
|
||||||
|
'systemctl start baseos-baserunner.service',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBaseOsEnvFile(jobArg: IBaseOsImageJob) {
|
||||||
|
return [
|
||||||
|
`BASEOS_CLOUDLY_URL=${this.escapeEnvValue(jobArg.cloudlyUrl)}`,
|
||||||
|
`BASEOS_JOIN_TOKEN=${this.escapeEnvValue(jobArg.provisioningToken)}`,
|
||||||
|
'BASEOS_STATE_PATH=/var/lib/baseos/state.json',
|
||||||
|
'BASEOS_HEARTBEAT_INTERVAL_MS=60000',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBaseOsServiceFile() {
|
||||||
|
return `[Unit]
|
||||||
|
Description=BaseOS Runner
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=/etc/baseos/baserunner.env
|
||||||
|
WorkingDirectory=/opt/baseos
|
||||||
|
ExecStart=/usr/local/bin/deno run --allow-all /opt/baseos/mod.ts start
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBaseOsInstallScript() {
|
||||||
|
return `#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if ! command -v /usr/local/bin/deno >/dev/null 2>&1; then
|
||||||
|
curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /opt/baseos/.git ]; then
|
||||||
|
rm -rf /opt/baseos
|
||||||
|
git clone https://code.foss.global/serve.zone/baseos.git /opt/baseos
|
||||||
|
else
|
||||||
|
git -C /opt/baseos pull --ff-only || true
|
||||||
|
fi
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private escapeEnvValue(valueArg: string) {
|
||||||
|
return JSON.stringify(valueArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runIsoCreator(configPathArg: string, logsArg: string[]) {
|
||||||
|
const command = `${this.options.isoCreatorCommand} build --config ${this.shellQuote(configPathArg)}`;
|
||||||
|
await this.runShellCommand(command, logsArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runShellCommand(commandArg: string, logsArg: string[]) {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const child = spawn(commandArg, {
|
||||||
|
shell: true,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
});
|
||||||
|
child.stdout.on('data', (chunk) => this.collectLog(logsArg, chunk));
|
||||||
|
child.stderr.on('data', (chunk) => this.collectLog(logsArg, chunk));
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Command failed with exit code ${code}: ${commandArg}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectLog(logsArg: string[], chunkArg: Buffer) {
|
||||||
|
const text = chunkArg.toString('utf8');
|
||||||
|
for (const line of text.split('\n')) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed) {
|
||||||
|
logsArg.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logsArg.length > 500) {
|
||||||
|
logsArg.splice(0, logsArg.length - 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadArtifact(jobArg: IBaseOsImageJob, outputPathArg: string, logsArg: string[]) {
|
||||||
|
logsArg.push(`Uploading artifact to ${jobArg.s3Descriptor.bucketName}/${jobArg.artifactKey}`);
|
||||||
|
const smartbucket = new plugins.smartbucket.SmartBucket({
|
||||||
|
...jobArg.s3Descriptor,
|
||||||
|
port: Number(jobArg.s3Descriptor.port || 443),
|
||||||
|
} as any);
|
||||||
|
const bucket = await smartbucket.getBucketByName(jobArg.s3Descriptor.bucketName);
|
||||||
|
await bucket.fastPutStream({
|
||||||
|
path: jobArg.artifactKey,
|
||||||
|
readableStream: fs.createReadStream(outputPathArg),
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sha256File(filePathArg: string) {
|
||||||
|
return await new Promise<string>((resolve, reject) => {
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
const stream = fs.createReadStream(filePathArg);
|
||||||
|
stream.on('error', reject);
|
||||||
|
stream.on('data', (chunk) => hash.update(chunk));
|
||||||
|
stream.on('end', () => resolve(hash.digest('hex')));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private shellQuote(valueArg: string) {
|
||||||
|
return `'${valueArg.replace(/'/g, `'\\''`)}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDefaultWorkdir() {
|
||||||
|
return path.join(process.cwd(), '.nogit', 'workdir');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDefaultWorkerId() {
|
||||||
|
return `${os.hostname()}-${process.pid}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import * as http from 'node:http';
|
||||||
|
import * as os from 'node:os';
|
||||||
|
|
||||||
|
import { BaseOsImageBuilder } from './classes.baseosimagebuilder.js';
|
||||||
|
import type {
|
||||||
|
IBaseOsImageJobRequest,
|
||||||
|
IBaseOsImageJobResponse,
|
||||||
|
ICoreBuildCapabilities,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
|
export interface ICoreBuildServerOptions {
|
||||||
|
port: number;
|
||||||
|
token?: string;
|
||||||
|
workdir: string;
|
||||||
|
isoCreatorCommand: string;
|
||||||
|
workerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreBuildServer {
|
||||||
|
private server?: http.Server;
|
||||||
|
private builder: BaseOsImageBuilder;
|
||||||
|
|
||||||
|
public static fromEnv() {
|
||||||
|
return new CoreBuildServer({
|
||||||
|
port: Number(process.env.COREBUILD_PORT || '3060'),
|
||||||
|
token: process.env.COREBUILD_TOKEN,
|
||||||
|
workdir: process.env.COREBUILD_WORKDIR || BaseOsImageBuilder.getDefaultWorkdir(),
|
||||||
|
isoCreatorCommand: process.env.ISO_CREATOR_COMMAND || 'isocreator',
|
||||||
|
workerId: process.env.COREBUILD_WORKER_ID || BaseOsImageBuilder.getDefaultWorkerId(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private options: ICoreBuildServerOptions) {
|
||||||
|
this.builder = new BaseOsImageBuilder({
|
||||||
|
workdir: options.workdir,
|
||||||
|
isoCreatorCommand: options.isoCreatorCommand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
this.server = http.createServer(async (req, res) => {
|
||||||
|
await this.handleRequest(req, res).catch((error) => {
|
||||||
|
this.sendJson(res, 500, {
|
||||||
|
success: false,
|
||||||
|
errorText: (error as Error).message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await new Promise<void>((resolve) => this.server!.listen(this.options.port, resolve));
|
||||||
|
console.log(`corebuild listening on ${this.options.port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop() {
|
||||||
|
if (!this.server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
this.server!.close((error) => error ? reject(error) : resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||||
|
const url = new URL(req.url || '/', 'http://localhost');
|
||||||
|
if (req.method === 'GET' && url.pathname === '/health') {
|
||||||
|
this.sendJson(res, 200, { ok: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.method === 'GET' && url.pathname === '/corebuild/v1/capabilities') {
|
||||||
|
this.sendJson(res, 200, this.getCapabilities());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.method === 'POST' && url.pathname === '/corebuild/v1/jobs/baseos-image') {
|
||||||
|
const requestBody = await this.readJson<IBaseOsImageJobRequest>(req);
|
||||||
|
this.validateToken(req, requestBody.apiToken);
|
||||||
|
const response = await this.handleBaseOsImageJob(requestBody);
|
||||||
|
this.sendJson(res, response.success ? 200 : 500, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sendJson(res, 404, { success: false, errorText: 'not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleBaseOsImageJob(
|
||||||
|
requestArg: IBaseOsImageJobRequest,
|
||||||
|
): Promise<IBaseOsImageJobResponse> {
|
||||||
|
const logs: string[] = [];
|
||||||
|
try {
|
||||||
|
const result = await this.builder.build(requestArg.job);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
artifact: result.artifact,
|
||||||
|
logs: result.logs,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logs.push((error as Error).message);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
logs,
|
||||||
|
errorText: (error as Error).message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCapabilities(): ICoreBuildCapabilities {
|
||||||
|
return {
|
||||||
|
workerId: this.options.workerId,
|
||||||
|
supportedBuildTypes: ['baseos-image'],
|
||||||
|
supportedArchitectures: ['amd64', 'arm64'],
|
||||||
|
cpuCores: os.cpus().length,
|
||||||
|
memoryGb: Math.round(os.totalmem() / 1024 / 1024 / 1024),
|
||||||
|
workdir: this.options.workdir,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateToken(reqArg: http.IncomingMessage, bodyTokenArg?: string) {
|
||||||
|
if (!this.options.token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const authHeader = reqArg.headers.authorization;
|
||||||
|
const headerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice('Bearer '.length) : undefined;
|
||||||
|
const token = bodyTokenArg || headerToken || reqArg.headers['x-corebuild-token'];
|
||||||
|
if (token !== this.options.token) {
|
||||||
|
throw new Error('corebuild token is invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async readJson<T>(reqArg: http.IncomingMessage): Promise<T> {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
for await (const chunk of reqArg) {
|
||||||
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||||
|
}
|
||||||
|
const body = Buffer.concat(chunks).toString('utf8').trim();
|
||||||
|
return body ? JSON.parse(body) as T : {} as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendJson(resArg: http.ServerResponse, statusCodeArg: number, bodyArg: object) {
|
||||||
|
resArg.statusCode = statusCodeArg;
|
||||||
|
resArg.setHeader('Content-Type', 'application/json');
|
||||||
|
resArg.end(JSON.stringify(bodyArg));
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { CoreBuildServer } from './classes.corebuildserver.js';
|
||||||
|
|
||||||
|
export * from './classes.corebuildserver.js';
|
||||||
|
export * from './classes.baseosimagebuilder.js';
|
||||||
|
export * from './types.js';
|
||||||
|
|
||||||
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
|
const server = CoreBuildServer.fromEnv();
|
||||||
|
await server.start();
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as smartbucket from '@push.rocks/smartbucket';
|
||||||
|
|
||||||
|
export { smartbucket };
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
import type { Readable } from 'node:stream';
|
||||||
|
|
||||||
|
export type TBaseOsImageArchitecture = 'amd64' | 'arm64' | 'rpi';
|
||||||
|
|
||||||
|
export interface IS3Descriptor {
|
||||||
|
endpoint: string;
|
||||||
|
accessKey: string;
|
||||||
|
accessSecret: string;
|
||||||
|
port?: number | string;
|
||||||
|
useSsl?: boolean;
|
||||||
|
bucketName: string;
|
||||||
|
region?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBaseOsImageJob {
|
||||||
|
id: string;
|
||||||
|
architecture: TBaseOsImageArchitecture;
|
||||||
|
cloudlyUrl: string;
|
||||||
|
provisioningToken: string;
|
||||||
|
sourceImageUrl?: string;
|
||||||
|
ubuntuVersion?: string;
|
||||||
|
hostname?: string;
|
||||||
|
wifi?: {
|
||||||
|
ssid: string;
|
||||||
|
password?: string;
|
||||||
|
};
|
||||||
|
sshPublicKey?: string;
|
||||||
|
s3Descriptor: IS3Descriptor;
|
||||||
|
artifactKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBaseOsImageJobRequest {
|
||||||
|
apiToken?: string;
|
||||||
|
job: IBaseOsImageJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBaseOsImageArtifactResult {
|
||||||
|
bucketName: string;
|
||||||
|
key: string;
|
||||||
|
filename: string;
|
||||||
|
contentType: string;
|
||||||
|
size: number;
|
||||||
|
sha256: string;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBaseOsImageJobResponse {
|
||||||
|
success: boolean;
|
||||||
|
artifact?: IBaseOsImageArtifactResult;
|
||||||
|
logs: string[];
|
||||||
|
errorText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICoreBuildCapabilities {
|
||||||
|
workerId: string;
|
||||||
|
supportedBuildTypes: string[];
|
||||||
|
supportedArchitectures: TBaseOsImageArchitecture[];
|
||||||
|
cpuCores: number;
|
||||||
|
memoryGb?: number;
|
||||||
|
workdir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TReadableSource = Readable | ReadableStream;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist_*/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user