426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
320 lines
10 KiB
JavaScript
320 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Supports app-core build, packaging, or development orchestration for package electrobun linux mjs. */
|
|
|
|
import { execFileSync } from "node:child_process";
|
|
import {
|
|
copyFileSync,
|
|
existsSync,
|
|
mkdirSync,
|
|
readdirSync,
|
|
readFileSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { cp } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = path.resolve(scriptDir, "..", "..", "..");
|
|
const electrobunRoot = path.join(
|
|
repoRoot,
|
|
"packages/app-core/platforms/electrobun",
|
|
);
|
|
const buildRoot = path.join(electrobunRoot, "build");
|
|
const artifactRoot = path.join(electrobunRoot, "artifacts");
|
|
const iconPath = path.join(electrobunRoot, "assets/appIcon.png");
|
|
const cleanupHelperScript = path.join(
|
|
repoRoot,
|
|
"packages",
|
|
"scripts",
|
|
"rm-path-recursive.mjs",
|
|
);
|
|
|
|
const args = new Map(
|
|
process.argv.slice(2).map((arg) => {
|
|
const [key, ...rest] = arg.replace(/^--/, "").split("=");
|
|
return [key, rest.join("=") || "true"];
|
|
}),
|
|
);
|
|
|
|
const version =
|
|
args.get("version") ??
|
|
JSON.parse(readFileSync(path.join(electrobunRoot, "package.json"), "utf8"))
|
|
.version;
|
|
const channel = args.get("channel") ?? "stable";
|
|
const arch = args.get("arch") ?? "x64";
|
|
const debArch = arch === "arm64" ? "arm64" : "amd64";
|
|
const rpmArch = arch === "arm64" ? "aarch64" : "x86_64";
|
|
|
|
// App identity is env-driven (mirroring the Electrobun shell's brand-config
|
|
// resolution) and defaults to the existing elizaOS values when unset, so the
|
|
// produced packages stay byte-identical unless the brand env is provided.
|
|
const displayName = (process.env.ELIZA_APP_NAME ?? "").trim() || "Eliza";
|
|
// Lowercase slug used for install paths, the launcher wrapper, the .desktop
|
|
// file, the icon, and (suffixed with `-app`) the deb/rpm package name.
|
|
// Defaults to "eliza". It must satisfy Debian package-name policy — start with
|
|
// an alphanumeric, then only [a-z0-9.+-] — or dpkg-deb/rpmbuild reject the
|
|
// control metadata with an opaque error, so validate the env value up front.
|
|
const namespace = (process.env.ELIZA_NAMESPACE ?? "").trim() || "eliza";
|
|
if (!/^[a-z0-9][a-z0-9.+-]+$/.test(namespace)) {
|
|
throw new Error(
|
|
`ELIZA_NAMESPACE "${namespace}" is not a valid Debian/RPM package name. ` +
|
|
"Use lowercase letters, digits, '.', '+', or '-', at least two characters, " +
|
|
'starting with a letter or digit (e.g. "acme" or "acme-desktop").',
|
|
);
|
|
}
|
|
// System package name. Not derivable from the existing literal, so keep the
|
|
// literal fallback and derive from the namespace only when the env is set.
|
|
const packageName = (process.env.ELIZA_NAMESPACE ?? "").trim()
|
|
? `${namespace}-app`
|
|
: "elizaos-app";
|
|
const optDir = `opt/${namespace}`;
|
|
const optPath = `/opt/${namespace}`;
|
|
|
|
function sh(command, commandArgs, options = {}) {
|
|
execFileSync(command, commandArgs, {
|
|
stdio: "inherit",
|
|
cwd: repoRoot,
|
|
...options,
|
|
});
|
|
}
|
|
|
|
function removePathRecursive(targetPath) {
|
|
sh(process.execPath, [cleanupHelperScript, targetPath]);
|
|
}
|
|
|
|
function latestBuildDir() {
|
|
const explicit = args.get("build-dir");
|
|
if (explicit) return path.resolve(repoRoot, explicit);
|
|
|
|
const candidates = readdirSync(buildRoot, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => path.join(buildRoot, entry.name))
|
|
.filter((dir) => /linux/i.test(path.basename(dir)))
|
|
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
|
|
|
|
if (!candidates[0]) {
|
|
throw new Error(
|
|
`No Linux Electrobun build directory found under ${buildRoot}`,
|
|
);
|
|
}
|
|
|
|
return candidates[0];
|
|
}
|
|
|
|
function findExecutable(root) {
|
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
const fullPath = path.join(root, entry.name);
|
|
if (!entry.isFile()) continue;
|
|
const mode = statSync(fullPath).mode;
|
|
if ((mode & 0o111) !== 0 && !/\.(so|dylib|dll)$/i.test(entry.name)) {
|
|
return fullPath;
|
|
}
|
|
}
|
|
|
|
const queue = [root];
|
|
const ignored = new Set(["node_modules", "Resources", "locales"]);
|
|
while (queue.length > 0) {
|
|
const dir = queue.shift();
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
if (ignored.has(entry.name)) continue;
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
queue.push(fullPath);
|
|
continue;
|
|
}
|
|
const mode = statSync(fullPath).mode;
|
|
if ((mode & 0o111) !== 0 && !/\.(so|dylib|dll)$/i.test(entry.name)) {
|
|
return fullPath;
|
|
}
|
|
}
|
|
}
|
|
throw new Error(`Could not find executable under ${root}`);
|
|
}
|
|
|
|
function writeDesktopFile(dest, execName = namespace) {
|
|
writeFileSync(
|
|
dest,
|
|
[
|
|
"[Desktop Entry]",
|
|
"Type=Application",
|
|
`Name=${displayName}`,
|
|
`Comment=Your ${displayName}, everywhere.`,
|
|
`Exec=${execName}`,
|
|
`Icon=${namespace}`,
|
|
"Terminal=false",
|
|
"Categories=Utility;Network;",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
async function stagePackageRoot(buildDir, destRoot) {
|
|
removePathRecursive(destRoot);
|
|
mkdirSync(path.join(destRoot, optDir), { recursive: true });
|
|
mkdirSync(path.join(destRoot, "usr/bin"), { recursive: true });
|
|
mkdirSync(path.join(destRoot, "usr/share/applications"), { recursive: true });
|
|
mkdirSync(path.join(destRoot, "usr/share/icons/hicolor/512x512/apps"), {
|
|
recursive: true,
|
|
});
|
|
|
|
await cp(buildDir, path.join(destRoot, optDir), {
|
|
recursive: true,
|
|
force: true,
|
|
dereference: true,
|
|
});
|
|
|
|
const executable = findExecutable(path.join(destRoot, optDir));
|
|
const relativeExecutable = path.relative(
|
|
path.join(destRoot, optDir),
|
|
executable,
|
|
);
|
|
writeFileSync(
|
|
path.join(destRoot, `usr/bin/${namespace}`),
|
|
`#!/usr/bin/env sh\nexec ${optPath}/${relativeExecutable} "$@"\n`,
|
|
{ mode: 0o755 },
|
|
);
|
|
writeDesktopFile(
|
|
path.join(destRoot, `usr/share/applications/${namespace}.desktop`),
|
|
);
|
|
if (existsSync(iconPath)) {
|
|
copyFileSync(
|
|
iconPath,
|
|
path.join(
|
|
destRoot,
|
|
`usr/share/icons/hicolor/512x512/apps/${namespace}.png`,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
async function buildDeb(buildDir) {
|
|
const root = path.join(os.tmpdir(), `${namespace}-deb-${process.pid}`);
|
|
await stagePackageRoot(buildDir, root);
|
|
const controlDir = path.join(root, "DEBIAN");
|
|
mkdirSync(controlDir, { recursive: true });
|
|
writeFileSync(
|
|
path.join(controlDir, "control"),
|
|
[
|
|
`Package: ${packageName}`,
|
|
`Version: ${version.replace(/-/g, "~")}`,
|
|
"Section: utils",
|
|
"Priority: optional",
|
|
`Architecture: ${debArch}`,
|
|
"Maintainer: elizaOS <hello@elizaos.ai>",
|
|
`Description: ${displayName} desktop app`,
|
|
` The consumer ${displayName} app for desktop chat, account setup, and connected devices.`,
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
const out = path.join(
|
|
artifactRoot,
|
|
`${packageName}_${version}_${debArch}.deb`,
|
|
);
|
|
sh("dpkg-deb", ["--build", root, out]);
|
|
removePathRecursive(root);
|
|
return out;
|
|
}
|
|
|
|
async function buildRpm(buildDir) {
|
|
const top = path.join(os.tmpdir(), `${namespace}-rpm-${process.pid}`);
|
|
const buildroot = path.join(top, `BUILDROOT/${packageName}`);
|
|
await stagePackageRoot(buildDir, buildroot);
|
|
for (const dir of ["BUILD", "RPMS", "SOURCES", "SPECS", "SRPMS"]) {
|
|
mkdirSync(path.join(top, dir), { recursive: true });
|
|
}
|
|
const rpmVersion = version.replace(/-.*/, "");
|
|
const rpmRelease = version.includes("-")
|
|
? version.replace(/^[^-]+-/, "").replace(/[^A-Za-z0-9.]/g, ".")
|
|
: "1";
|
|
const spec = path.join(top, `SPECS/${packageName}.spec`);
|
|
writeFileSync(
|
|
spec,
|
|
[
|
|
`Name: ${packageName}`,
|
|
`Version: ${rpmVersion}`,
|
|
`Release: ${rpmRelease}%{?dist}`,
|
|
`Summary: ${displayName} desktop app`,
|
|
"License: MIT",
|
|
`BuildArch: ${rpmArch}`,
|
|
"",
|
|
"%description",
|
|
`The consumer ${displayName} app for desktop chat, account setup, and connected devices.`,
|
|
"",
|
|
"%install",
|
|
"mkdir -p %{buildroot}",
|
|
`cp -a ${buildroot}/* %{buildroot}/`,
|
|
"",
|
|
"%files",
|
|
optPath,
|
|
`/usr/bin/${namespace}`,
|
|
`/usr/share/applications/${namespace}.desktop`,
|
|
`/usr/share/icons/hicolor/512x512/apps/${namespace}.png`,
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
sh("rpmbuild", ["--define", `_topdir ${top}`, "-bb", spec]);
|
|
const rpmDir = path.join(top, "RPMS", rpmArch);
|
|
const rpm = readdirSync(rpmDir).find((name) => name.endsWith(".rpm"));
|
|
if (!rpm) throw new Error("rpmbuild did not produce an rpm");
|
|
const out = path.join(
|
|
artifactRoot,
|
|
`${packageName}-${version}.${rpmArch}.rpm`,
|
|
);
|
|
copyFileSync(path.join(rpmDir, rpm), out);
|
|
removePathRecursive(top);
|
|
return out;
|
|
}
|
|
|
|
async function buildAppImage(buildDir) {
|
|
const appDir = path.join(os.tmpdir(), `${displayName}.AppDir-${process.pid}`);
|
|
await stagePackageRoot(buildDir, appDir);
|
|
copyFileSync(
|
|
path.join(appDir, `usr/share/applications/${namespace}.desktop`),
|
|
path.join(appDir, `${namespace}.desktop`),
|
|
);
|
|
if (existsSync(iconPath))
|
|
copyFileSync(iconPath, path.join(appDir, `${namespace}.png`));
|
|
writeFileSync(
|
|
path.join(appDir, "AppRun"),
|
|
`#!/usr/bin/env sh\nHERE="$(dirname "$(readlink -f "$0")")"\nexec "$HERE/usr/bin/${namespace}" "$@"\n`,
|
|
{ mode: 0o755 },
|
|
);
|
|
|
|
const tool = path.join(os.tmpdir(), "appimagetool-x86_64.AppImage");
|
|
if (!existsSync(tool)) {
|
|
sh("curl", [
|
|
"-fsSL",
|
|
"https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage",
|
|
"-o",
|
|
tool,
|
|
]);
|
|
sh("chmod", ["+x", tool]);
|
|
}
|
|
const out = path.join(
|
|
artifactRoot,
|
|
`${displayName}-${version}-linux-${arch}.AppImage`,
|
|
);
|
|
sh(tool, [appDir, out], {
|
|
env: { ...process.env, ARCH: rpmArch, APPIMAGE_EXTRACT_AND_RUN: "1" },
|
|
});
|
|
removePathRecursive(appDir);
|
|
return out;
|
|
}
|
|
|
|
mkdirSync(artifactRoot, { recursive: true });
|
|
const buildDir = latestBuildDir();
|
|
console.log(`Packaging Linux Electrobun build: ${buildDir}`);
|
|
console.log(`Version: ${version}; channel: ${channel}; arch: ${arch}`);
|
|
|
|
const outputs = [];
|
|
outputs.push(await buildDeb(buildDir));
|
|
outputs.push(await buildRpm(buildDir));
|
|
outputs.push(await buildAppImage(buildDir));
|
|
|
|
for (const output of outputs) {
|
|
console.log(`Wrote ${path.relative(repoRoot, output)}`);
|
|
}
|