Skip to content

Commit 157afe4

Browse files
committed
fix(os): refresh USB persistence hardening
1 parent 5b06031 commit 157afe4

7 files changed

Lines changed: 252 additions & 22 deletions

File tree

packages/os/linux/variants/milady-tails/build.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ if [ ! -d "${TAILS_SRC}/config" ]; then
5454
exit 1
5555
fi
5656

57+
if [ "${STAGE}" = "binary" ] && [ "${ELIZAOS_SYNC_CHROOT:-1}" = "1" ]; then
58+
echo "=== syncing elizaOS overlay into existing chroot ==="
59+
"${HERE}/scripts/sync-runtime-to-chroot.sh"
60+
fi
61+
5762
echo "=== building image ${IMAGE} ==="
5863
# The image bakes in only Tails' live-build fork; the Dockerfile's build
5964
# context needs that source available as tails-live-build/. The vendored

packages/os/linux/variants/milady-tails/scripts/prepare-milady-app-overlay.mjs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,103 @@ export default plugin;
256256

257257
const optionalStubPackages = new Map(
258258
Object.entries({
259+
"@elizaos/app-model-tester": `
260+
export const modelTesterPlugin = {
261+
name: "model-tester",
262+
description: "Model tester routes are not bundled in elizaOS Live.",
263+
routes: [],
264+
};
265+
export default modelTesterPlugin;
266+
`,
267+
"@elizaos/plugin-companion": `
268+
export const appCompanionPlugin = {
269+
name: "companion",
270+
description: "Companion overlay placeholder for elizaOS Live. The full 3D companion bundle can be installed through app updates.",
271+
actions: [],
272+
providers: [],
273+
services: [],
274+
routes: [],
275+
};
276+
export const companionPlugin = appCompanionPlugin;
277+
export const registerCompanionApp = () => undefined;
278+
export default appCompanionPlugin;
279+
`,
280+
"@elizaos/plugin-lifeops": `
281+
export const appLifeOpsPlugin = {
282+
name: "lifeops",
283+
description: "LifeOps placeholder for elizaOS Live. Cloud connectors and proactive workflows become available after provider setup.",
284+
actions: [],
285+
providers: [],
286+
services: [],
287+
routes: [],
288+
};
289+
export const lifeopsPlugin = {
290+
name: "lifeops-routes",
291+
routes: [],
292+
};
293+
export const BrowserBridgePluginService = undefined;
294+
export const browserBridgeProvider = undefined;
295+
export const detectHealthBackend = () => ({ available: false, backend: "none" });
296+
export const handleLifeOpsRoutes = async () => false;
297+
export const handleWebsiteBlockerRoutes = async () => false;
298+
export default appLifeOpsPlugin;
299+
`,
300+
"@elizaos/plugin-documents": `
301+
export const documentsPlugin = {
302+
name: "documents",
303+
description: "Documents app routes are not bundled in the elizaOS Live base runtime.",
304+
routes: [],
305+
};
306+
export const plugin = documentsPlugin;
307+
export default documentsPlugin;
308+
`,
309+
"@elizaos/plugin-hyperliquid-app": `
310+
export const hyperliquidPlugin = {
311+
name: "hyperliquid",
312+
description: "Hyperliquid app routes are not bundled in the elizaOS Live base runtime.",
313+
routes: [],
314+
};
315+
export const plugin = hyperliquidPlugin;
316+
export default hyperliquidPlugin;
317+
`,
318+
"@elizaos/plugin-polymarket-app": `
319+
export const polymarketPlugin = {
320+
name: "polymarket",
321+
description: "Polymarket app routes are not bundled in the elizaOS Live base runtime.",
322+
routes: [],
323+
};
324+
export const plugin = polymarketPlugin;
325+
export default polymarketPlugin;
326+
`,
327+
"@elizaos/plugin-shopify-ui": `
328+
export const shopifyPlugin = {
329+
name: "shopify",
330+
routes: [],
331+
};
332+
export default shopifyPlugin;
333+
`,
334+
"@elizaos/plugin-steward-app": `
335+
export const stewardPlugin = {
336+
name: "steward",
337+
routes: [],
338+
};
339+
export default stewardPlugin;
340+
`,
341+
"@elizaos/plugin-training": `
342+
export const trainingPlugin = {
343+
name: "training",
344+
routes: [],
345+
};
346+
export const registerTrainingRuntimeHooks = async () => undefined;
347+
export default trainingPlugin;
348+
`,
349+
"@elizaos/plugin-vincent": `
350+
export const vincentPlugin = {
351+
name: "vincent",
352+
routes: [],
353+
};
354+
export default vincentPlugin;
355+
`,
259356
"@elizaos/plugin-whatsapp": `
260357
const noop = () => undefined;
261358
const falseRoute = async () => false;
@@ -388,6 +485,15 @@ export default undefined;
388485
}).map(([packageName, source]) => [packageName, `${source.trimStart()}\n`]),
389486
);
390487

488+
const forceLiveStubPackages = new Set([
489+
"@elizaos/plugin-companion",
490+
"@elizaos/plugin-documents",
491+
"@elizaos/plugin-google",
492+
"@elizaos/plugin-hyperliquid-app",
493+
"@elizaos/plugin-lifeops",
494+
"@elizaos/plugin-polymarket-app",
495+
]);
496+
391497
const chromiumFlags = {
392498
"disable-gpu": true,
393499
"disable-gpu-compositing": true,
@@ -495,7 +601,11 @@ function isLiveStubPackage(packageJson) {
495601

496602
function shouldWriteLiveFallbackPackage(packageName) {
497603
const packageJson = readPackageManifest(packageName);
498-
return !packageJson || isLiveStubPackage(packageJson);
604+
return (
605+
forceLiveStubPackages.has(packageName) ||
606+
!packageJson ||
607+
isLiveStubPackage(packageJson)
608+
);
499609
}
500610

501611
function sourcePackageManifest(_packageName, packageJson) {
@@ -1007,7 +1117,10 @@ function collectPackageInventory(projectedPackages = []) {
10071117

10081118
function packageStatus(packageName) {
10091119
const packageJson = readPackageManifest(packageName);
1010-
const generated = !packageJson || isLiveStubPackage(packageJson);
1120+
const generated =
1121+
forceLiveStubPackages.has(packageName) ||
1122+
!packageJson ||
1123+
isLiveStubPackage(packageJson);
10111124
return {
10121125
packageName,
10131126
packagePath: relativeToStage(packageManifestPath(packageName)),

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

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,48 @@ for unit in \
878878
do
879879
grep -q '^ConditionPathExists=!/run/elizaos/persistence-maintenance$' "${unit}"
880880
done
881+
if [ "${SOURCE_ONLY}" != "1" ]; then
882+
verify_materialized_file() {
883+
local rel="$1"
884+
local src="tails/config/chroot_local-includes/${rel}"
885+
local chroot_path="tails/chroot/${rel}"
886+
local squashfs="tails/binary/live/filesystem.squashfs"
887+
local tmp
888+
889+
if [ -e "${chroot_path}" ] && ! cmp -s "${src}" "${chroot_path}"; then
890+
echo "${chroot_path} is stale; run scripts/sync-runtime-to-chroot.sh before binary rebuilds." >&2
891+
exit 1
892+
fi
893+
894+
if [ -f "${squashfs}" ] && command -v unsquashfs >/dev/null 2>&1; then
895+
tmp="$(mktemp)"
896+
if ! unsquashfs -cat "${squashfs}" "${rel}" >"${tmp}" 2>/dev/null; then
897+
rm -f "${tmp}"
898+
echo "${squashfs} is missing ${rel}" >&2
899+
exit 1
900+
fi
901+
if ! cmp -s "${src}" "${tmp}"; then
902+
rm -f "${tmp}"
903+
echo "${squashfs}:${rel} is stale; rebuild the binary image after syncing the chroot." >&2
904+
exit 1
905+
fi
906+
rm -f "${tmp}"
907+
fi
908+
}
909+
910+
for rel in \
911+
etc/systemd/user/elizaos-agent.service \
912+
etc/systemd/user/elizaos-renderer.service \
913+
etc/systemd/user/milady.service \
914+
usr/lib/systemd/user/tails-create-persistent-storage.service \
915+
usr/local/lib/elizaos/create-persistent-storage-session \
916+
usr/local/lib/elizaos/persistence-maintenance \
917+
usr/local/lib/persistent-storage/on-activated-hooks/MiladyData/20-restart-milady \
918+
usr/local/lib/persistent-storage/on-deactivated-hooks/MiladyData/20-restart-milady
919+
do
920+
verify_materialized_file "${rel}"
921+
done
922+
fi
881923
if grep -q 'systemctl --global enable elizaos-pill.service' \
882924
tails/config/chroot_local-hooks/52-update-systemd-units; then
883925
echo "Voice pill must stay installed but opt-in until the pill renderer is production-ready." >&2
@@ -1186,13 +1228,24 @@ for (const root of [
11861228
throw new Error(`${distIndex}: required runtime plugin dist is missing`);
11871229
}
11881230
}
1189-
const googleStubPath = `${nodeModules}/@elizaos/plugin-google/index.js`;
1190-
const googlePackagePath = `${nodeModules}/@elizaos/plugin-google/package.json`;
1191-
const googlePackage = JSON.parse(fs.readFileSync(googlePackagePath, "utf8"));
1192-
if (googlePackage.version === "0.0.0-elizaos-live-stub") {
1193-
const googleStub = fs.readFileSync(googleStubPath, "utf8");
1194-
if (!googleStub.includes("googlePlugin")) {
1195-
throw new Error(`${googleStubPath}: Google connector stub is malformed`);
1231+
const forcedLiveStubs = new Map([
1232+
["@elizaos/plugin-companion", "companion"],
1233+
["@elizaos/plugin-documents", "documents"],
1234+
["@elizaos/plugin-google", "google"],
1235+
["@elizaos/plugin-hyperliquid-app", "hyperliquid"],
1236+
["@elizaos/plugin-lifeops", "lifeops"],
1237+
["@elizaos/plugin-polymarket-app", "polymarket"],
1238+
]);
1239+
for (const [packageName, marker] of forcedLiveStubs) {
1240+
const stubPath = `${nodeModules}/${packageName}/index.js`;
1241+
const packagePath = `${nodeModules}/${packageName}/package.json`;
1242+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
1243+
if (packageJson.version !== "0.0.0-elizaos-live-stub") {
1244+
throw new Error(`${packagePath}: ${packageName} must be a live-safe stub in the base USB runtime`);
1245+
}
1246+
const stub = fs.readFileSync(stubPath, "utf8");
1247+
if (!stub.includes(marker)) {
1248+
throw new Error(`${stubPath}: ${packageName} live-safe stub is malformed`);
11961249
}
11971250
}
11981251

packages/os/linux/variants/milady-tails/scripts/sync-runtime-to-chroot.sh

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ OVERLAY="${ROOT}/tails/config/chroot_local-includes"
88
CHROOT="${ROOT}/tails/chroot"
99
APP_STAGE="${OVERLAY}/usr/share/elizaos/milady-app"
1010

11+
run_root() {
12+
if [ "$(id -u)" -eq 0 ]; then
13+
"$@"
14+
else
15+
sudo "$@"
16+
fi
17+
}
18+
1119
if [ ! -d "${CHROOT}" ]; then
1220
echo "ERROR: ${CHROOT} does not exist; run a full build first." >&2
1321
exit 1
@@ -26,27 +34,40 @@ sync_file() {
2634
echo "ERROR: missing overlay file ${src}" >&2
2735
exit 1
2836
fi
29-
sudo mkdir -p "$(dirname "${dst}")"
30-
sudo rsync -a --chown=root:root "${src}" "${dst}"
37+
run_root mkdir -p "$(dirname "${dst}")"
38+
run_root rsync -a --chown=root:root "${src}" "${dst}"
3139
}
3240

3341
copy_perl_module() {
3442
local src_rel="$1"
3543
local module_rel="$2"
3644
local src="${OVERLAY}/${src_rel}"
3745
local dst
38-
dst="$(sudo find "${CHROOT}/usr/local/share/perl" -path "*/${module_rel}" -print -quit)"
46+
dst="$(run_root find "${CHROOT}/usr/local/share/perl" -path "*/${module_rel}" -print -quit)"
3947
if [ -z "${dst}" ]; then
4048
echo "ERROR: installed Perl module ${module_rel} not found in ${CHROOT}" >&2
4149
exit 1
4250
fi
43-
sudo rsync -a --chown=root:root "${src}" "${dst}"
51+
run_root rsync -a --chown=root:root "${src}" "${dst}"
4452
}
4553

4654
runtime_files=(
55+
etc/gdm3/PostLogin/Default
56+
etc/generate-sudoers.d/elizaos-capability-runner.toml
57+
etc/generate-sudoers.d/elizaos-persistence-maintenance.toml
58+
etc/systemd/system/elizaos-root-mode.service
59+
etc/systemd/system/elizaos-update-health-check.service
60+
etc/systemd/system/elizaos-update-verify.service
61+
etc/systemd/system/milady.path
62+
etc/systemd/system/milady.service
63+
etc/systemd/user/elizaos-agent.service
64+
etc/systemd/user/elizaos-pill.service
65+
etc/systemd/user/elizaos-renderer.service
66+
etc/systemd/user/milady.service
4767
etc/whisperback/config.py
4868
usr/lib/systemd/user/tails-additional-software-install.service
4969
usr/lib/systemd/user/tails-configure-keyboard.service
70+
usr/lib/systemd/user/tails-create-persistent-storage.service
5071
usr/lib/systemd/user/tails-htpdate-notify-user.service
5172
usr/lib/systemd/user/tails-low-ram-notify-user.service
5273
usr/lib/systemd/user/tails-post-greeter-docs.service
@@ -58,11 +79,39 @@ runtime_files=(
5879
usr/lib/systemd/user/tails-upgrade-frontend.service
5980
usr/lib/systemd/user/tails-virt-notify-user.service
6081
usr/lib/systemd/user/tails-wait-until-tor-has-bootstrapped.service
82+
usr/lib/python3/dist-packages/tps/configuration/features.py
83+
usr/lib/python3/dist-packages/tps_frontend/views/features_view.py
84+
usr/local/bin/milady
6185
usr/local/bin/tails-about
6286
usr/local/bin/tails-security-check
6387
usr/local/bin/tails-upgrade-frontend-wrapper
88+
usr/local/lib/elizaos/capability-runner
89+
usr/local/lib/elizaos/create-persistent-storage-session
90+
usr/local/lib/elizaos/elizaos-webkit-shell
91+
usr/local/lib/elizaos/milady-keeper
92+
usr/local/lib/elizaos/persistence-maintenance
93+
usr/local/lib/elizaos/renderer-server.mjs
94+
usr/local/lib/elizaos/runtime-env
95+
usr/local/lib/elizaos/start-elizaos-agent-user
96+
usr/local/lib/elizaos/start-elizaos-browser-user
97+
usr/local/lib/elizaos/start-elizaos-pill-user
98+
usr/local/lib/elizaos/start-elizaos-renderer-user
99+
usr/local/lib/elizaos/start-milady-user
100+
usr/local/lib/elizaos/update-health-check
101+
usr/local/lib/elizaos/update-manager
102+
usr/local/lib/persistent-storage/on-activated-hooks/MiladyData/10-clean-runtime-state
103+
usr/local/lib/persistent-storage/on-activated-hooks/MiladyData/20-restart-milady
104+
usr/local/lib/persistent-storage/on-deactivated-hooks/MiladyData/20-restart-milady
64105
usr/local/lib/tails-boot-device-can-have-persistence
106+
usr/share/applications/milady.desktop
107+
usr/share/applications/org.boum.tails.PersistentStorage.desktop.in
108+
usr/share/pixmaps/elizaos-persistent-storage.svg
109+
usr/share/tails/persistent-storage/features_view.ui.in
110+
usr/share/tails/persistent-storage/locked_view.ui.in
111+
usr/share/tails/persistent-storage/passphrase_view.ui.in
65112
usr/share/tails/persistent-storage/style.css
113+
usr/share/tails/persistent-storage/welcome_view.ui.in
114+
usr/share/tails/persistent-storage/window.ui.in
66115
usr/share/whisperback/whisperback.ui.in
67116
)
68117

@@ -74,10 +123,11 @@ copy_perl_module usr/src/iuk/lib/Tails/IUK/Frontend.pm Tails/IUK/Frontend.pm
74123
copy_perl_module usr/src/iuk/lib/Tails/IUK/Install.pm Tails/IUK/Install.pm
75124
copy_perl_module usr/src/perl5lib/lib/Tails/RunningSystem.pm Tails/RunningSystem.pm
76125

77-
sudo rm -rf "${CHROOT}/usr/share/elizaos/milady-app"
78-
sudo mkdir -p "${CHROOT}/usr/share/elizaos"
79-
sudo rsync -a --delete --chown=root:root "${APP_STAGE}/" \
126+
run_root rm -rf "${CHROOT}/usr/share/elizaos/milady-app"
127+
run_root mkdir -p "${CHROOT}/usr/share/elizaos"
128+
run_root rsync -a --delete --chown=root:root "${APP_STAGE}/" \
80129
"${CHROOT}/usr/share/elizaos/milady-app/"
81-
sudo chroot "${CHROOT}" /bin/sh -s < "${ROOT}/tails/config/chroot_local-hooks/9100-install-milady"
130+
run_root chroot "${CHROOT}" /bin/sh -s < "${ROOT}/tails/config/chroot_local-hooks/9100-install-milady"
131+
run_root chroot "${CHROOT}" /bin/sh -s < "${ROOT}/tails/config/chroot_local-hooks/99-zzzzzz_permissions"
82132

83133
echo "Synced elizaOS runtime overlay into ${CHROOT}"

packages/os/linux/variants/milady-tails/scripts/usb-write.sh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,16 @@ if ! printf '%s' "${file_out}" | grep -qiE "ISO 9660|boot sector|partition|disk
8585
exit 2
8686
fi
8787

88-
if printf '%s' "${file_out}" | grep -qi "ISO 9660"; then
89-
yellow "WARNING: writing an ISO directly is for explicit override/testing only."
90-
yellow "Persistent Storage may reject devices that were not created from the USB image."
91-
fi
9288
image_is_iso=0
9389
if printf '%s' "${file_out}" | grep -qi "ISO 9660"; then
9490
image_is_iso=1
9591
fi
9692

93+
if [ "${image_is_iso}" = "1" ]; then
94+
yellow "WARNING: writing an ISO directly is for explicit override/testing only."
95+
yellow "Persistent Storage may reject devices that were not created from the USB image."
96+
fi
97+
9798
if [ "${image_is_iso}" != "1" ] && ! command -v sgdisk >/dev/null 2>&1; then
9899
red "sgdisk is required to prepare a cloned USB image for Persistent Storage."
99100
red "Install gdisk, then rerun this writer."

packages/os/linux/variants/milady-tails/tails/auto/scripts/create-usb-image-from-iso

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,15 @@ class ImageCreator:
362362
# Unmount in reverse order of setup so the chroot-visible image is
363363
# gone before we tear down the procfs mount used by syslinux. Track
364364
# successful mounts so setup failures do not leak chroot state.
365+
unmount_errors = []
365366
for mountpoint in reversed(mounted):
366-
unmount(mountpoint)
367+
try:
368+
unmount(mountpoint)
369+
except subprocess.CalledProcessError as err:
370+
logger.error("Failed to unmount %s: %s", mountpoint, err)
371+
unmount_errors.append(err)
372+
if unmount_errors:
373+
raise unmount_errors[0]
367374

368375
def install_syslinux(self):
369376
logger.info("Installing bootloader")

packages/os/release/schema/elizaos-os-release-manifest.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
"required": ["status"]
183183
},
184184
"then": {
185+
"required": ["downloadUrl"],
185186
"properties": {
186187
"downloadUrl": { "type": "string", "minLength": 1 }
187188
}

0 commit comments

Comments
 (0)