feat: add baseos raw image builds

This commit is contained in:
2026-05-07 19:49:56 +00:00
parent f2fa041109
commit 75836f5591
3 changed files with 94 additions and 9 deletions
+89 -8
View File
@@ -9,6 +9,7 @@ import * as plugins from './plugins.js';
import type {
IBaseOsImageArtifactResult,
IBaseOsImageJob,
TBaseOsImageKind,
} from './types.js';
export interface IBaseOsImageBuilderOptions {
@@ -23,22 +24,22 @@ export class BaseOsImageBuilder {
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 imageKind = this.getImageKind(jobArg);
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 filename = this.getArtifactFilename(jobArg, imageKind);
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`);
const isoCreatorConfig = imageKind === 'balena-raw'
? this.createRawImageConfig(jobArg, outputDir, filename)
: this.createIsoCreatorConfig(jobArg, outputDir, filename);
await fsp.writeFile(configPath, `${JSON.stringify(isoCreatorConfig, null, 2)}\n`);
logs.push(`Starting isocreator for ${jobArg.architecture}`);
logs.push(`Starting isocreator for ${jobArg.architecture} ${imageKind}`);
await this.runIsoCreator(configPath, logs);
const stat = await fsp.stat(outputPath);
@@ -51,7 +52,7 @@ export class BaseOsImageBuilder {
bucketName: jobArg.s3Descriptor.bucketName,
key: jobArg.artifactKey,
filename,
contentType: 'application/x-iso9660-image',
contentType: this.getContentType(filename, imageKind),
size: stat.size,
sha256,
createdAt: Date.now(),
@@ -66,6 +67,7 @@ export class BaseOsImageBuilder {
const envFile = this.createBaseOsEnvFile(jobArg);
return {
version: '1.0',
imageKind: 'iso',
...(jobArg.sourceImageUrl
? {
source: {
@@ -136,6 +138,85 @@ export class BaseOsImageBuilder {
};
}
private createRawImageConfig(jobArg: IBaseOsImageJob, outputDirArg: string, filenameArg: string) {
if (!jobArg.sourceImageUrl) {
throw new Error('sourceImageUrl is required for balena-raw BaseOS image builds');
}
return {
version: '1.0',
imageKind: 'raw-image',
source: {
type: 'url',
url: jobArg.sourceImageUrl,
},
output: {
filename: filenameArg,
path: outputDirArg,
},
...(jobArg.wifi?.ssid
? {
network: {
wifi: jobArg.wifi,
},
}
: {}),
raw_image: {
sourceFormat: 'auto',
bootPartition: '/dev/sda1',
outputCompression: filenameArg.endsWith('.xz') ? 'xz' : 'none',
},
balena_os: {
hostname: jobArg.hostname || `baseos-${jobArg.id.slice(0, 8)}`,
sshPublicKeys: jobArg.sshPublicKey ? [jobArg.sshPublicKey] : [],
configJson: {
serveZone: {
baseos: {
buildId: jobArg.id,
cloudlyUrl: jobArg.cloudlyUrl,
provisioningToken: jobArg.provisioningToken,
},
},
},
baseOsEnv: {
BASEOS_CLOUDLY_URL: jobArg.cloudlyUrl,
BASEOS_JOIN_TOKEN: jobArg.provisioningToken,
BASEOS_STATE_PATH: '/data/baseos/state.json',
BASEOS_HEARTBEAT_INTERVAL_MS: '60000',
SERVEZONE_RUNTIME: 'baseos',
},
},
};
}
private getImageKind(jobArg: IBaseOsImageJob): TBaseOsImageKind {
if (jobArg.imageKind) {
return jobArg.imageKind;
}
if (jobArg.architecture === 'rpi') {
return 'balena-raw';
}
if (jobArg.sourceImageUrl && /\.(img|img\.xz|zip)(\?|$)/i.test(jobArg.sourceImageUrl)) {
return 'balena-raw';
}
return 'ubuntu-iso';
}
private getArtifactFilename(jobArg: IBaseOsImageJob, imageKindArg: TBaseOsImageKind) {
const architectureSuffix = jobArg.architecture === 'amd64' ? '' : `-${jobArg.architecture}`;
if (imageKindArg === 'balena-raw') {
return `baseos${architectureSuffix}.img.xz`;
}
return `baseos${architectureSuffix}.iso`;
}
private getContentType(filenameArg: string, imageKindArg: TBaseOsImageKind) {
if (imageKindArg === 'ubuntu-iso') {
return 'application/x-iso9660-image';
}
return filenameArg.endsWith('.xz') ? 'application/x-xz' : 'application/octet-stream';
}
private createBaseOsEnvFile(jobArg: IBaseOsImageJob) {
return [
`BASEOS_CLOUDLY_URL=${this.escapeEnvValue(jobArg.cloudlyUrl)}`,
+2 -1
View File
@@ -104,7 +104,8 @@ export class CoreBuildServer {
return {
workerId: this.options.workerId,
supportedBuildTypes: ['baseos-image'],
supportedArchitectures: ['amd64', 'arm64'],
supportedArchitectures: ['amd64', 'arm64', 'rpi'],
supportedImageKinds: ['ubuntu-iso', 'balena-raw'],
cpuCores: os.cpus().length,
memoryGb: Math.round(os.totalmem() / 1024 / 1024 / 1024),
workdir: this.options.workdir,
+3
View File
@@ -1,6 +1,7 @@
import type { Readable } from 'node:stream';
export type TBaseOsImageArchitecture = 'amd64' | 'arm64' | 'rpi';
export type TBaseOsImageKind = 'ubuntu-iso' | 'balena-raw';
export interface IS3Descriptor {
endpoint: string;
@@ -15,6 +16,7 @@ export interface IS3Descriptor {
export interface IBaseOsImageJob {
id: string;
architecture: TBaseOsImageArchitecture;
imageKind?: TBaseOsImageKind;
cloudlyUrl: string;
provisioningToken: string;
sourceImageUrl?: string;
@@ -55,6 +57,7 @@ export interface ICoreBuildCapabilities {
workerId: string;
supportedBuildTypes: string[];
supportedArchitectures: TBaseOsImageArchitecture[];
supportedImageKinds: TBaseOsImageKind[];
cpuCores: number;
memoryGb?: number;
workdir: string;