Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@ jobs:
exit 0
fi

git fetch --no-tags --depth=1 origin "${{ github.base_ref }}"
changed_files="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD")"
git fetch --no-tags origin "${{ github.base_ref }}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unbounded fetch may pull full branch history on every PR run. Without --depth, GitHub Actions' initial shallow clone is expanded to the complete history of the base branch, which can be hundreds of megabytes on an active repo. A bounded deepen (e.g. --deepen=100) is usually enough to resolve the merge-base while keeping fetch fast; the fallback path already handles the rare case where no common ancestor is found.

Suggested change
git fetch --no-tags origin "${{ github.base_ref }}"
git fetch --no-tags --deepen=100 origin "${{ github.base_ref }}"

if merge_base="$(git merge-base "origin/${{ github.base_ref }}" HEAD)"; then
changed_files="$(git diff --name-only "${merge_base}" HEAD)"
else
echo "No merge base found with origin/${{ github.base_ref }}; falling back to direct base/head diff." >&2
changed_files="$(git diff --name-only "origin/${{ github.base_ref }}" HEAD)"
fi
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
echo "run=true" >> "$GITHUB_OUTPUT"
else
Expand Down
62 changes: 54 additions & 8 deletions packages/os/linux/variants/milady-tails/Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,69 @@ setup:
milady-app:
#!/usr/bin/env bash
set -euo pipefail
milady_root="$(cd ../../../../../.. && pwd)"
app_out="${milady_root}/eliza/packages/app-core/platforms/electrobun/build/dev-linux-x64/Milady-dev"
eliza_root="$(git rev-parse --show-toplevel)"
outer_root="$(cd "${eliza_root}/.." && pwd)"
app_out="${ELIZAOS_MILADY_APP_ARTIFACT:-${eliza_root}/packages/app-core/platforms/electrobun/build/dev-linux-x64/Milady-dev}"
stage="tails/config/chroot_local-includes/usr/share/elizaos/milady-app"

ensure_plugin_runtime_dist() {
local package_rel="$1"
local mode="$2"
local package_dir="${eliza_root}/${package_rel}"
local dist_index="${package_dir}/dist/index.js"

if [ -s "${dist_index}" ]; then
return 0
fi

echo "Building ${package_rel} runtime dist for elizaOS Live"
case "${mode}" in
package-js)
( cd "${package_dir}" && bun run build:js )
;;
tsup-index)
( cd "${package_dir}" && bunx tsup src/index.ts --format esm --clean )
;;
*)
echo "Unknown runtime package build mode: ${mode}" >&2
exit 1
;;
esac

test -s "${dist_index}" || {
echo "missing ${dist_index} after runtime package build" >&2
exit 1
}
}

if [ ! -x "${app_out}/bin/launcher" ]; then
if [ "${ELIZAOS_BUILD_MILADY_APP:-0}" != "1" ]; then
echo "Milady app build not found at ${app_out}/bin/launcher"
echo "Build it separately, or rerun with ELIZAOS_BUILD_MILADY_APP=1 to permit this recipe to build it."
echo "Build it separately, set ELIZAOS_MILADY_APP_ARTIFACT, or rerun with ELIZAOS_BUILD_MILADY_APP=1 to permit this recipe to build it."
exit 1
fi
echo "Milady app build not found; ELIZAOS_BUILD_MILADY_APP=1 so building it now"
( cd "${milady_root}" && bun install --no-frozen-lockfile --ignore-scripts )
( cd "${milady_root}" && bun install --cwd eliza --no-frozen-lockfile --ignore-scripts )
( cd "${milady_root}" && MILADY_ELIZA_SOURCE=local node scripts/setup-upstreams.mjs )
( cd "${milady_root}/eliza/packages/electrobun-carrots" && bun run build )
( cd "${milady_root}" && MILADY_ELIZA_SOURCE=local bun run build:desktop )
( cd "${eliza_root}" && bun install --no-frozen-lockfile --ignore-scripts )
if [ -f "${outer_root}/package.json" ] && [ -d "${outer_root}/eliza" ]; then
( cd "${outer_root}" && bun install --no-frozen-lockfile --ignore-scripts )
( cd "${outer_root}" && bun install --cwd eliza --no-frozen-lockfile --ignore-scripts )
( cd "${outer_root}" && MILADY_ELIZA_SOURCE=local node scripts/setup-upstreams.mjs )
( cd "${outer_root}/eliza/packages/electrobun-carrots" && bun run build )
( cd "${outer_root}" && MILADY_ELIZA_SOURCE=local bun run build:desktop )
else
( cd "${eliza_root}" && MILADY_ELIZA_SOURCE=local node packages/app-core/scripts/setup-upstreams.mjs )
( cd "${eliza_root}/packages/electrobun-carrots" && bun run build )
( cd "${eliza_root}" && \
ELIZA_APP_NAME=Milady \
ELIZA_APP_ID=ai.milady.milady \
ELIZA_URL_SCHEME=milady \
ELIZA_NAMESPACE=milady \
bun run --cwd packages/app-core/platforms/electrobun build )
fi
fi
test -x "${app_out}/bin/launcher" || { echo "missing ${app_out}/bin/launcher"; exit 1; }
ensure_plugin_runtime_dist "plugins/plugin-health" package-js
ensure_plugin_runtime_dist "plugins/plugin-calendly" tsup-index
if [ -e "${stage}" ] && ! rm -rf "${stage}"; then
sudo -n rm -rf "${stage}"
fi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ require_fixed 'contains unlisted file' "${update_manager}" \
"update manager must reject files outside the signed runtime inventory"
require_fixed 'runtime_store' "${update_manager}" \
"update manager must materialize verified runtimes into a root-owned store"
require_fixed 'os.O_NOFOLLOW' "${update_manager}" \
"update manager must materialize files without following source symlinks"
require_fixed 'tempfile.mkstemp' "${update_manager}" \
"update manager must materialize files through temporary files"
require_fixed 'verified runtime file changed while copying' "${update_manager}" \
"update manager must re-check manifest digests during materialization"
if grep -q 'ELIZAOS_ALLOW_RUNTIME_ENV_OVERRIDES' "${runtime_env}"; then
fail "runtime selector must not expose caller-controlled runtime override escape hatches"
fi
Expand Down
17 changes: 17 additions & 0 deletions packages/os/linux/variants/milady-tails/scripts/static-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ stat_mode() {
}

echo "==> shell syntax"
test -f tails/data/debootstrap/scripts/debian-common.patch
test -f tails/data/splash.png
test -x tails/data/wrappers/apt-get
bash -n build.sh build-iso.sh tails/auto/build \
scripts/dev-sign-update-manifest.sh \
scripts/usb-write.sh \
Expand Down Expand Up @@ -67,6 +70,9 @@ node --check scripts/generate-release-evidence.mjs
node --check scripts/validate-model-catalog.mjs
node --check scripts/validate-runtime-overlay.mjs
node --check tails/config/chroot_local-includes/usr/local/lib/elizaos/renderer-server.mjs
grep -q 'ELIZAOS_MILADY_APP_ARTIFACT' Justfile
grep -q 'ensure_plugin_runtime_dist "plugins/plugin-health" package-js' Justfile
grep -q 'ensure_plugin_runtime_dist "plugins/plugin-calendly" tsup-index' Justfile
python3 -m json.tool schemas/update-manifest.schema.json >/dev/null
python3 -m json.tool schemas/model-catalog.schema.json >/dev/null
python3 - \
Expand Down Expand Up @@ -276,6 +282,17 @@ then
echo "High-visibility inherited Tails strings still need elizaOS branding." >&2
exit 1
fi
if rg -n \
'Preparing Tails for first use|Checking the Tails system partition|Configuring Tails|Tails specific tools|Tails live user' \
tails/config/chroot_local-includes/usr/share/initramfs-tools/scripts/init-premount/partitioning \
tails/config/chroot_local-includes/usr/share/initramfs-tools/scripts/init-top/read-and-update-random-seed-sector \
tails/config/chroot_local-includes/usr/lib/live/config/2000-aesthetics \
tails/config/chroot_local-includes/usr/local/bin/milady \
tails/config/chroot_local-includes/usr/share/desktop-directories/Tails.directory.in
then
echo "First-boot and launcher polish still exposes inherited Tails wording." >&2
exit 1
fi
launcher_paths=(
tails/config/chroot_local-includes/usr/share/applications/tails-documentation.desktop
tails/config/chroot_local-includes/usr/share/applications/tails-backup.desktop
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh

echo ""
echo "Configuring Tails"
echo "Configuring elizaOS"
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/bin/sh

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

echo "- importing Tails help desk's GnuPG key into WhisperBack's keyring"
echo "- importing inherited feedback GnuPG key into WhisperBack's keyring"
gpg --batch --no-default-keyring \
--keyring /usr/share/keyrings/whisperback-keyring.gpg \
--import /usr/share/doc/tails/website/tails-bugs.key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ if [ "$(id -u)" -eq 0 ]; then
fi

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ import pathlib
import re
import shutil
import shlex
import stat
import sys
import tempfile

Expand Down Expand Up @@ -242,11 +243,44 @@ def ensure_safe_dir(path, field):
fail(f"{field} must not be group/world writable")
return path

def copy_verified_file(src, dst):
def copy_verified_file(src, dst, expected_sha256):
dst.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
shutil.copyfile(src, dst, follow_symlinks=False)
mode = src.stat().st_mode
os.chmod(dst, 0o755 if mode & 0o111 else 0o644)
if dst.parent.is_symlink():
fail(f"destination parent must not be a symlink: {dst.parent}")
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
src_fd = os.open(src, flags)
except OSError as exc:
fail(f"unable to open verified runtime file without following symlinks: {src}: {exc}")
tmp_name = None
try:
src_stat = os.fstat(src_fd)
if not stat.S_ISREG(src_stat.st_mode):
fail(f"verified runtime path is not a regular file: {src}")
tmp_fd, tmp_name = tempfile.mkstemp(prefix=f"{dst.name}.", dir=str(dst.parent))
digest = hashlib.sha256()
with os.fdopen(src_fd, "rb", closefd=True) as src_handle, os.fdopen(
tmp_fd,
"wb",
closefd=True,
) as dst_handle:
src_fd = -1
for chunk in iter(lambda: src_handle.read(1024 * 1024), b""):
digest.update(chunk)
dst_handle.write(chunk)
actual = digest.hexdigest()
if actual.lower() != expected_sha256.lower():
fail(f"verified runtime file changed while copying: {src}")
os.chmod(tmp_name, 0o755 if src_stat.st_mode & 0o111 else 0o644)
os.replace(tmp_name, dst)
tmp_name = None
finally:
if src_fd >= 0:
os.close(src_fd)
if tmp_name and os.path.exists(tmp_name):
os.unlink(tmp_name)
Comment on lines +264 to +283

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Potential double-close on src_fd when dst_handle setup fails

with os.fdopen(src_fd, …) as src_handle, os.fdopen(tmp_fd, …) as dst_handle: is desugared as nested context managers. If os.fdopen(tmp_fd, "wb", …) raises after src_handle.__enter__() has succeeded, Python calls src_handle.__exit__(), which closes the underlying src_fd. The src_fd = -1 assignment in the body never executes, so the finally block then calls os.close(src_fd) on an already-closed descriptor — raising EBADF and masking the original exception.

Comment on lines +282 to +283

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 os.path.exists follows symlinks, so a dangling symlink planted at tmp_name (between os.replace failing and the finally running) would cause exists() to return False and silently skip cleanup, leaking the temp file. os.path.lexists checks the path itself without following symlinks, or a try/except around os.unlink is even more idiomatic.

Suggested change
if tmp_name and os.path.exists(tmp_name):
os.unlink(tmp_name)
if tmp_name:
try:
os.unlink(tmp_name)
except FileNotFoundError:
pass


def chown_tree_root(path):
if os.geteuid() != 0:
Expand Down Expand Up @@ -350,6 +384,7 @@ if runtime.get("filesComplete") is not True:
fail("runtime.filesComplete must be true")
hashed_entrypoint_paths = set()
declared_files = {}
declared_hashes = {}
for item in files:
if not isinstance(item, dict):
fail("runtime.files entries must be objects")
Expand All @@ -368,6 +403,7 @@ for item in files:
if rel_text in declared_files:
fail(f"runtime.files contains duplicate path: {rel_text}")
declared_files[rel_text] = path
declared_hashes[rel_text] = expected.lower()

for candidate in bundle_dir.rglob("*"):
if candidate.is_symlink():
Expand Down Expand Up @@ -403,6 +439,7 @@ if floor_path.exists():
fail("manifest sequence is below the stored channel floor")

model_catalog_path = ""
model_catalog_digest = ""
model_catalog = manifest.get("modelCatalog")
if model_catalog is not None:
if not isinstance(model_catalog, dict):
Expand All @@ -417,6 +454,7 @@ if model_catalog is not None:
actual = file_sha256(catalog_path)
if actual.lower() != expected.lower():
fail("model catalog hash mismatch")
model_catalog_digest = expected.lower()
catalog = read_json(catalog_path, "modelCatalog")
if catalog.get("schemaVersion") != 1 or catalog.get("kind") != "elizaos.modelCatalog":
fail("model catalog kind/schemaVersion mismatch")
Expand All @@ -432,14 +470,22 @@ else:
tmp_runtime = tmp_store / "runtime"
try:
for rel_text, src in declared_files.items():
copy_verified_file(src, tmp_runtime / pathlib.PurePosixPath(rel_text))
copy_verified_file(
src,
tmp_runtime / pathlib.PurePosixPath(rel_text),
declared_hashes[rel_text],
)
node_modules_rel = pathlib.Path(
os.path.relpath(resolved_entrypoints["nodeModules"], bundle_dir)
)
(tmp_runtime / node_modules_rel).mkdir(mode=0o755, parents=True, exist_ok=True)
copy_verified_file(manifest_path, tmp_store / "manifest.json")
copy_verified_file(manifest_path, tmp_store / "manifest.json", manifest_digest)
if model_catalog_path:
copy_verified_file(pathlib.Path(model_catalog_path), tmp_store / "model-catalog.json")
copy_verified_file(
pathlib.Path(model_catalog_path),
tmp_store / "model-catalog.json",
model_catalog_digest,
)
model_catalog_path = str(tmp_store / "model-catalog.json")
chown_tree_root(tmp_store)
for root, dirs, files in os.walk(tmp_store):
Expand All @@ -463,7 +509,7 @@ if not materialized_manifest_path.is_file():
if file_sha256(materialized_manifest_path) != manifest_digest:
fail("materialized update manifest hash mismatch")
for rel_text, src in declared_files.items():
expected = file_sha256(src)
expected = declared_hashes[rel_text]
materialized_file = materialized_runtime / pathlib.PurePosixPath(rel_text)
if materialized_file.is_symlink():
fail(f"materialized runtime contains unsupported symlink: {rel_text}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Desktop Entry]
_Name=Tails
_Comment=Tails specific tools
_Name=elizaOS
_Comment=elizaOS live tools
Icon=preferences-system
Type=Directory
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ GUID=$(sgdisk --print "${PARENT_DEVICE}" |

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

log "This is not the first boot, so repairing filesystem"
PLYMOUTH_MSG="Checking the Tails system partition for errors..."
PLYMOUTH_MSG="Checking the elizaOS system partition for errors..."
plymouth display-message --text="${PLYMOUTH_MSG}"
repair_system_partition
# `plymouth hide-message` doesn't work (#20401)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ log "Restoring random seed from LBA 34"
dd bs=512 skip=34 count=1 status=none if="${PARENT_DEVICE}" of=/dev/urandom
log "Random seed restored from LBA 34"

# We try to obfuscate the number of times Tails has been booted by
# We try to obfuscate the number of times elizaOS has been booted by
# writing a random number of times (1-500) to the seed during the first
# boot.
if [ -n "${FIRST_BOOT:-}" ]; then
ITERATIONS=$((1 + $(od -An -N2 -t uI /dev/urandom) % 500))
log "First boot, writing random seed $ITERATIONS times..."
PLYMOUTH_INIT_MSG="Preparing Tails for first use..."
PLYMOUTH_INIT_MSG="Preparing elizaOS for first use..."
plymouth display-message --text="${PLYMOUTH_INIT_MSG}"

# Debug output for the following loop would be too verbose
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- /usr/share/debootstrap/scripts/debian-common 2019-07-06 13:22:30.000000000 +0200
+++ /usr/share/debootstrap/scripts/debian-common 2019-08-05 14:15:07.165451726 +0200
@@ -217,4 +217,8 @@

progress $bases $bases CONFBASE "Configuring base system"
info BASESUCCESS "Base system installed successfully."
+
+ # Tails-specific part:
+ chroot $TARGET /usr/bin/dpkg-divert --divert /usr/bin/apt-get.real --rename /usr/bin/apt-get
+ cp -f %%topdir%%/data/wrappers/apt-get $TARGET/usr/bin/apt-get
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading