Skip to content

Commit 369bbe1

Browse files
authored
fix(os): polish elizaOS live branding and update materialization (#7797)
* fix elizaOS live polish and updater materialization * make elizaOS live app staging reproducible * vendor required Tails build data * fix homepage smoke scope on shallow PR history
1 parent 4425eae commit 369bbe1

15 files changed

Lines changed: 350 additions & 28 deletions

File tree

.github/workflows/quality.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,13 @@ jobs:
7777
exit 0
7878
fi
7979
80-
git fetch --no-tags --depth=1 origin "${{ github.base_ref }}"
81-
changed_files="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD")"
80+
git fetch --no-tags origin "${{ github.base_ref }}"
81+
if merge_base="$(git merge-base "origin/${{ github.base_ref }}" HEAD)"; then
82+
changed_files="$(git diff --name-only "${merge_base}" HEAD)"
83+
else
84+
echo "No merge base found with origin/${{ github.base_ref }}; falling back to direct base/head diff." >&2
85+
changed_files="$(git diff --name-only "origin/${{ github.base_ref }}" HEAD)"
86+
fi
8287
if grep -Eq '^(packages/homepage/|packages/shared-brand/|packages/app-core/scripts/write-homepage-release-data\.mjs|\.github/workflows/(deploy-homepage|release-electrobun|release-orchestrator)\.yml)' <<< "${changed_files}"; then
8388
echo "run=true" >> "$GITHUB_OUTPUT"
8489
else

packages/os/linux/variants/milady-tails/Justfile

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,69 @@ setup:
3737
milady-app:
3838
#!/usr/bin/env bash
3939
set -euo pipefail
40-
milady_root="$(cd ../../../../../.. && pwd)"
41-
app_out="${milady_root}/eliza/packages/app-core/platforms/electrobun/build/dev-linux-x64/Milady-dev"
40+
eliza_root="$(git rev-parse --show-toplevel)"
41+
outer_root="$(cd "${eliza_root}/.." && pwd)"
42+
app_out="${ELIZAOS_MILADY_APP_ARTIFACT:-${eliza_root}/packages/app-core/platforms/electrobun/build/dev-linux-x64/Milady-dev}"
4243
stage="tails/config/chroot_local-includes/usr/share/elizaos/milady-app"
44+
45+
ensure_plugin_runtime_dist() {
46+
local package_rel="$1"
47+
local mode="$2"
48+
local package_dir="${eliza_root}/${package_rel}"
49+
local dist_index="${package_dir}/dist/index.js"
50+
51+
if [ -s "${dist_index}" ]; then
52+
return 0
53+
fi
54+
55+
echo "Building ${package_rel} runtime dist for elizaOS Live"
56+
case "${mode}" in
57+
package-js)
58+
( cd "${package_dir}" && bun run build:js )
59+
;;
60+
tsup-index)
61+
( cd "${package_dir}" && bunx tsup src/index.ts --format esm --clean )
62+
;;
63+
*)
64+
echo "Unknown runtime package build mode: ${mode}" >&2
65+
exit 1
66+
;;
67+
esac
68+
69+
test -s "${dist_index}" || {
70+
echo "missing ${dist_index} after runtime package build" >&2
71+
exit 1
72+
}
73+
}
74+
4375
if [ ! -x "${app_out}/bin/launcher" ]; then
4476
if [ "${ELIZAOS_BUILD_MILADY_APP:-0}" != "1" ]; then
4577
echo "Milady app build not found at ${app_out}/bin/launcher"
46-
echo "Build it separately, or rerun with ELIZAOS_BUILD_MILADY_APP=1 to permit this recipe to build it."
78+
echo "Build it separately, set ELIZAOS_MILADY_APP_ARTIFACT, or rerun with ELIZAOS_BUILD_MILADY_APP=1 to permit this recipe to build it."
4779
exit 1
4880
fi
4981
echo "Milady app build not found; ELIZAOS_BUILD_MILADY_APP=1 so building it now"
50-
( cd "${milady_root}" && bun install --no-frozen-lockfile --ignore-scripts )
51-
( cd "${milady_root}" && bun install --cwd eliza --no-frozen-lockfile --ignore-scripts )
52-
( cd "${milady_root}" && MILADY_ELIZA_SOURCE=local node scripts/setup-upstreams.mjs )
53-
( cd "${milady_root}/eliza/packages/electrobun-carrots" && bun run build )
54-
( cd "${milady_root}" && MILADY_ELIZA_SOURCE=local bun run build:desktop )
82+
( cd "${eliza_root}" && bun install --no-frozen-lockfile --ignore-scripts )
83+
if [ -f "${outer_root}/package.json" ] && [ -d "${outer_root}/eliza" ]; then
84+
( cd "${outer_root}" && bun install --no-frozen-lockfile --ignore-scripts )
85+
( cd "${outer_root}" && bun install --cwd eliza --no-frozen-lockfile --ignore-scripts )
86+
( cd "${outer_root}" && MILADY_ELIZA_SOURCE=local node scripts/setup-upstreams.mjs )
87+
( cd "${outer_root}/eliza/packages/electrobun-carrots" && bun run build )
88+
( cd "${outer_root}" && MILADY_ELIZA_SOURCE=local bun run build:desktop )
89+
else
90+
( cd "${eliza_root}" && MILADY_ELIZA_SOURCE=local node packages/app-core/scripts/setup-upstreams.mjs )
91+
( cd "${eliza_root}/packages/electrobun-carrots" && bun run build )
92+
( cd "${eliza_root}" && \
93+
ELIZA_APP_NAME=Milady \
94+
ELIZA_APP_ID=ai.milady.milady \
95+
ELIZA_URL_SCHEME=milady \
96+
ELIZA_NAMESPACE=milady \
97+
bun run --cwd packages/app-core/platforms/electrobun build )
98+
fi
5599
fi
56100
test -x "${app_out}/bin/launcher" || { echo "missing ${app_out}/bin/launcher"; exit 1; }
101+
ensure_plugin_runtime_dist "plugins/plugin-health" package-js
102+
ensure_plugin_runtime_dist "plugins/plugin-calendly" tsup-index
57103
if [ -e "${stage}" ] && ! rm -rf "${stage}"; then
58104
sudo -n rm -rf "${stage}"
59105
fi

packages/os/linux/variants/milady-tails/scripts/security-smoke.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,12 @@ require_fixed 'contains unlisted file' "${update_manager}" \
301301
"update manager must reject files outside the signed runtime inventory"
302302
require_fixed 'runtime_store' "${update_manager}" \
303303
"update manager must materialize verified runtimes into a root-owned store"
304+
require_fixed 'os.O_NOFOLLOW' "${update_manager}" \
305+
"update manager must materialize files without following source symlinks"
306+
require_fixed 'tempfile.mkstemp' "${update_manager}" \
307+
"update manager must materialize files through temporary files"
308+
require_fixed 'verified runtime file changed while copying' "${update_manager}" \
309+
"update manager must re-check manifest digests during materialization"
304310
if grep -q 'ELIZAOS_ALLOW_RUNTIME_ENV_OVERRIDES' "${runtime_env}"; then
305311
fail "runtime selector must not expose caller-controlled runtime override escape hatches"
306312
fi

packages/os/linux/variants/milady-tails/scripts/static-smoke.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ stat_mode() {
2828
}
2929

3030
echo "==> shell syntax"
31+
test -f tails/data/debootstrap/scripts/debian-common.patch
32+
test -f tails/data/splash.png
33+
test -x tails/data/wrappers/apt-get
3134
bash -n build.sh build-iso.sh tails/auto/build \
3235
scripts/dev-sign-update-manifest.sh \
3336
scripts/usb-write.sh \
@@ -67,6 +70,9 @@ node --check scripts/generate-release-evidence.mjs
6770
node --check scripts/validate-model-catalog.mjs
6871
node --check scripts/validate-runtime-overlay.mjs
6972
node --check tails/config/chroot_local-includes/usr/local/lib/elizaos/renderer-server.mjs
73+
grep -q 'ELIZAOS_MILADY_APP_ARTIFACT' Justfile
74+
grep -q 'ensure_plugin_runtime_dist "plugins/plugin-health" package-js' Justfile
75+
grep -q 'ensure_plugin_runtime_dist "plugins/plugin-calendly" tsup-index' Justfile
7076
python3 -m json.tool schemas/update-manifest.schema.json >/dev/null
7177
python3 -m json.tool schemas/model-catalog.schema.json >/dev/null
7278
python3 - \
@@ -276,6 +282,17 @@ then
276282
echo "High-visibility inherited Tails strings still need elizaOS branding." >&2
277283
exit 1
278284
fi
285+
if rg -n \
286+
'Preparing Tails for first use|Checking the Tails system partition|Configuring Tails|Tails specific tools|Tails live user' \
287+
tails/config/chroot_local-includes/usr/share/initramfs-tools/scripts/init-premount/partitioning \
288+
tails/config/chroot_local-includes/usr/share/initramfs-tools/scripts/init-top/read-and-update-random-seed-sector \
289+
tails/config/chroot_local-includes/usr/lib/live/config/2000-aesthetics \
290+
tails/config/chroot_local-includes/usr/local/bin/milady \
291+
tails/config/chroot_local-includes/usr/share/desktop-directories/Tails.directory.in
292+
then
293+
echo "First-boot and launcher polish still exposes inherited Tails wording." >&2
294+
exit 1
295+
fi
279296
launcher_paths=(
280297
tails/config/chroot_local-includes/usr/share/applications/tails-documentation.desktop
281298
tails/config/chroot_local-includes/usr/share/applications/tails-backup.desktop
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/sh
22

33
echo ""
4-
echo "Configuring Tails"
4+
echo "Configuring elizaOS"

packages/os/linux/variants/milady-tails/tails/config/chroot_local-includes/usr/lib/live/config/2000-import-gnupg-key

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#!/bin/sh
22

33
Import_GnuPG_key() {
4-
echo "- importing Tails' GnuPG keys into the ${LIVE_USERNAME}'s keyring"
4+
echo "- importing upstream live-OS GnuPG keys into the ${LIVE_USERNAME}'s keyring"
55
sudo -H -u "${LIVE_USERNAME}" gpg --batch --import /usr/share/doc/tails/website/*.key
66

7-
echo "- importing Tails help desk's GnuPG key into WhisperBack's keyring"
7+
echo "- importing inherited feedback GnuPG key into WhisperBack's keyring"
88
gpg --batch --no-default-keyring \
99
--keyring /usr/share/keyrings/whisperback-keyring.gpg \
1010
--import /usr/share/doc/tails/website/tails-bugs.key

packages/os/linux/variants/milady-tails/tails/config/chroot_local-includes/usr/local/bin/milady

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ if [ "$(id -u)" -eq 0 ]; then
88
fi
99

1010
if [ "$(id -un)" != "amnesia" ]; then
11-
echo "elizaOS must run as the Tails live user 'amnesia'." >&2
11+
echo "elizaOS must run as the live user 'amnesia'." >&2
1212
exit 1
1313
fi
1414

packages/os/linux/variants/milady-tails/tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ import pathlib
149149
import re
150150
import shutil
151151
import shlex
152+
import stat
152153
import sys
153154
import tempfile
154155
@@ -242,11 +243,44 @@ def ensure_safe_dir(path, field):
242243
fail(f"{field} must not be group/world writable")
243244
return path
244245
245-
def copy_verified_file(src, dst):
246+
def copy_verified_file(src, dst, expected_sha256):
246247
dst.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
247-
shutil.copyfile(src, dst, follow_symlinks=False)
248-
mode = src.stat().st_mode
249-
os.chmod(dst, 0o755 if mode & 0o111 else 0o644)
248+
if dst.parent.is_symlink():
249+
fail(f"destination parent must not be a symlink: {dst.parent}")
250+
flags = os.O_RDONLY
251+
if hasattr(os, "O_NOFOLLOW"):
252+
flags |= os.O_NOFOLLOW
253+
try:
254+
src_fd = os.open(src, flags)
255+
except OSError as exc:
256+
fail(f"unable to open verified runtime file without following symlinks: {src}: {exc}")
257+
tmp_name = None
258+
try:
259+
src_stat = os.fstat(src_fd)
260+
if not stat.S_ISREG(src_stat.st_mode):
261+
fail(f"verified runtime path is not a regular file: {src}")
262+
tmp_fd, tmp_name = tempfile.mkstemp(prefix=f"{dst.name}.", dir=str(dst.parent))
263+
digest = hashlib.sha256()
264+
with os.fdopen(src_fd, "rb", closefd=True) as src_handle, os.fdopen(
265+
tmp_fd,
266+
"wb",
267+
closefd=True,
268+
) as dst_handle:
269+
src_fd = -1
270+
for chunk in iter(lambda: src_handle.read(1024 * 1024), b""):
271+
digest.update(chunk)
272+
dst_handle.write(chunk)
273+
actual = digest.hexdigest()
274+
if actual.lower() != expected_sha256.lower():
275+
fail(f"verified runtime file changed while copying: {src}")
276+
os.chmod(tmp_name, 0o755 if src_stat.st_mode & 0o111 else 0o644)
277+
os.replace(tmp_name, dst)
278+
tmp_name = None
279+
finally:
280+
if src_fd >= 0:
281+
os.close(src_fd)
282+
if tmp_name and os.path.exists(tmp_name):
283+
os.unlink(tmp_name)
250284
251285
def chown_tree_root(path):
252286
if os.geteuid() != 0:
@@ -350,6 +384,7 @@ if runtime.get("filesComplete") is not True:
350384
fail("runtime.filesComplete must be true")
351385
hashed_entrypoint_paths = set()
352386
declared_files = {}
387+
declared_hashes = {}
353388
for item in files:
354389
if not isinstance(item, dict):
355390
fail("runtime.files entries must be objects")
@@ -368,6 +403,7 @@ for item in files:
368403
if rel_text in declared_files:
369404
fail(f"runtime.files contains duplicate path: {rel_text}")
370405
declared_files[rel_text] = path
406+
declared_hashes[rel_text] = expected.lower()
371407
372408
for candidate in bundle_dir.rglob("*"):
373409
if candidate.is_symlink():
@@ -403,6 +439,7 @@ if floor_path.exists():
403439
fail("manifest sequence is below the stored channel floor")
404440
405441
model_catalog_path = ""
442+
model_catalog_digest = ""
406443
model_catalog = manifest.get("modelCatalog")
407444
if model_catalog is not None:
408445
if not isinstance(model_catalog, dict):
@@ -417,6 +454,7 @@ if model_catalog is not None:
417454
actual = file_sha256(catalog_path)
418455
if actual.lower() != expected.lower():
419456
fail("model catalog hash mismatch")
457+
model_catalog_digest = expected.lower()
420458
catalog = read_json(catalog_path, "modelCatalog")
421459
if catalog.get("schemaVersion") != 1 or catalog.get("kind") != "elizaos.modelCatalog":
422460
fail("model catalog kind/schemaVersion mismatch")
@@ -432,14 +470,22 @@ else:
432470
tmp_runtime = tmp_store / "runtime"
433471
try:
434472
for rel_text, src in declared_files.items():
435-
copy_verified_file(src, tmp_runtime / pathlib.PurePosixPath(rel_text))
473+
copy_verified_file(
474+
src,
475+
tmp_runtime / pathlib.PurePosixPath(rel_text),
476+
declared_hashes[rel_text],
477+
)
436478
node_modules_rel = pathlib.Path(
437479
os.path.relpath(resolved_entrypoints["nodeModules"], bundle_dir)
438480
)
439481
(tmp_runtime / node_modules_rel).mkdir(mode=0o755, parents=True, exist_ok=True)
440-
copy_verified_file(manifest_path, tmp_store / "manifest.json")
482+
copy_verified_file(manifest_path, tmp_store / "manifest.json", manifest_digest)
441483
if model_catalog_path:
442-
copy_verified_file(pathlib.Path(model_catalog_path), tmp_store / "model-catalog.json")
484+
copy_verified_file(
485+
pathlib.Path(model_catalog_path),
486+
tmp_store / "model-catalog.json",
487+
model_catalog_digest,
488+
)
443489
model_catalog_path = str(tmp_store / "model-catalog.json")
444490
chown_tree_root(tmp_store)
445491
for root, dirs, files in os.walk(tmp_store):
@@ -463,7 +509,7 @@ if not materialized_manifest_path.is_file():
463509
if file_sha256(materialized_manifest_path) != manifest_digest:
464510
fail("materialized update manifest hash mismatch")
465511
for rel_text, src in declared_files.items():
466-
expected = file_sha256(src)
512+
expected = declared_hashes[rel_text]
467513
materialized_file = materialized_runtime / pathlib.PurePosixPath(rel_text)
468514
if materialized_file.is_symlink():
469515
fail(f"materialized runtime contains unsupported symlink: {rel_text}")
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[Desktop Entry]
2-
_Name=Tails
3-
_Comment=Tails specific tools
2+
_Name=elizaOS
3+
_Comment=elizaOS live tools
44
Icon=preferences-system
55
Type=Directory

packages/os/linux/variants/milady-tails/tails/config/chroot_local-includes/usr/share/initramfs-tools/scripts/init-premount/partitioning

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ GUID=$(sgdisk --print "${PARENT_DEVICE}" |
9090

9191
if [ "${GUID}" = "17B81DA0-8B1E-4269-9C39-FE5C7B9B58A3" ]; then
9292
log "This is the first boot, so repartitioning"
93-
PLYMOUTH_MSG="Preparing Tails for first use..."
93+
PLYMOUTH_MSG="Preparing elizaOS for first use..."
9494
plymouth display-message --text="${PLYMOUTH_MSG}"
9595
if ! /scripts/lib/first_boot_repartition "${PARENT_DEVICE}" "${SYSTEM_PARTITION}"; then
9696
log "Repartitioning failed"
@@ -109,7 +109,7 @@ else
109109
verify_partition_table
110110

111111
log "This is not the first boot, so repairing filesystem"
112-
PLYMOUTH_MSG="Checking the Tails system partition for errors..."
112+
PLYMOUTH_MSG="Checking the elizaOS system partition for errors..."
113113
plymouth display-message --text="${PLYMOUTH_MSG}"
114114
repair_system_partition
115115
# `plymouth hide-message` doesn't work (#20401)

0 commit comments

Comments
 (0)