Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

7.9 KiB

Phase 7 — Persistent encrypted USB integration

User opts into LUKS persistence via the greeter; elizaOS's data survives reboots; no Tails persistence code is modified, only added configuration. Paths: TAILS = packages/os/linux/tails.

Status as of 2026-05-16: the ElizaOSData backend feature, frontend row, and on-activated cleanup hook exist in source and are part of the current rebuild/test pass. They still need QEMU/USB validation in a live ISO.

Key finding: this Tails release uses modern Persistent Storage (tps)

The original plan said "reuse tails-persistence-setup", but this Tails tree replaced the legacy Perl GTK app with the Python Persistent Storage stack (tps backend + tps_frontend UI). It still writes a persistence.conf in the live-boot(5) format — same kernel machinery — but feature definitions live in code, not a static preset file.

Relevant files (under TAILS/config/chroot_local-includes/):

  • usr/lib/python3/dist-packages/tps/configuration/features.py — the preset definitions; get_classes() auto-discovers every Feature subclass.
  • .../tps/configuration/feature.py — the Feature base class.
  • .../tps/configuration/binding.pyBinding(src, dest, …); activation is a nosymfollow bind-mount, bootstrapped from dest on first activation.
  • .../tps/configuration/config_file.py — writes persistence.conf.
  • .../tps/service.pydo_create() activates every enabled_by_default feature right after LUKS format.
  • usr/share/tails/persistent-storage/features_view.ui.in — the GTK UI; requires per-feature <id>_box/_row/_switch widgets or the frontend raises RuntimeError.
  • usr/local/lib/persistent-storage/on-activated-hooks/<FeatureId>/ — optional post-activation hook scripts (run as root).

1. The ElizaOSData feature — the entire backend change

Add one Feature subclass to tps/configuration/features.py. get_classes() picks it up automatically; no registration list to edit.

class ElizaOSData(Feature):
    Id = "ElizaOSData"
    translatable_name = "elizaOS"
    Bindings = (
        Binding("elizaos/dot-eliza",  "/home/amnesia/.eliza"),
        Binding("elizaos/dot-elizaos", "/home/amnesia/.elizaos"),
        Binding("elizaos/config",     "/home/amnesia/.config/elizaos"),
    )
    enabled_by_default = True
    conflicting_apps = (
        ConflictingApp(name="elizaOS", desktop_id="elizaos.desktop",
                       process_names=["elizaos", "bun"]),
    )
  • ~/.eliza and ~/.elizaos are both real, separate live state roots — not aliases. Both must persist. Tails' live user is amnesia (uid 1000).
  • ~/.config/elizaos/ — matches PLAN's "custom themes, dotfile customizations".
  • enabled_by_default = True is the parity lever: when the user creates Persistent Storage, service.do_create() auto-activates ElizaOSData — "persistent mode" means elizaOS's state persists, period, no hunting for a switch.
  • conflicting_appstps blocks activate/deactivate while elizaOS runs, to avoid corrupting a live bind-mount.
  • Whole-directory bind-mounts (not link/symlinks) — ~/.eliza etc. hold a DB, models, logs that must persist in full.

This produces, when enabled, these persistence.conf lines (you never hand-write the file — tps generates it from Bindings):

/home/amnesia/.config/elizaos	source=elizaos/config
/home/amnesia/.eliza	source=elizaos/dot-eliza
/home/amnesia/.elizaos	source=elizaos/dot-elizaos

Wi-Fi (/etc/NetworkManager/system-connections/) is already Tails' NetworkConnections feature — do not re-declare it (duplicate binding).

Not persisted (ephemeral in both modes): ~/.eliza/sockets/ — Unix sockets are runtime-only; handle via an on-activated hook (you can't exclude a subdir of a bind-mount), not a binding.

2. Parity correctness — same paths in both modes

tps's design gives this for free:

  • Amnesia: ~/.eliza doesn't exist yet; elizaOS creates it on first launch, in RAM, wiped on shutdown.
  • Persistent: at boot after greeter unlock, tps bind-mounts the LUKS-backed dir over /home/amnesia/.eliza before the session starts. elizaOS writes to ~/.elizaexact same path.

The elizaOS app and the agent contain zero persistence-aware branching — the only difference is whether /home/amnesia/.eliza is a tmpfs dir or a bind-mount, invisible above the VFS layer. First-activation bootstrap (binding.py does cp -a dest src if the LUKS source is empty) means an amnesia→persistent transition mid-session loses no data.

3. The on-activated hook

TAILS/config/chroot_local-includes/usr/local/lib/persistent-storage/on-activated-hooks/ElizaOSData/10-clean-runtime-state (executable, runs as root after the bind-mount):

  • remove stale Chromium/Electrobun runtime cache and singleton lock files under /home/amnesia/.eliza (Cache, Code Cache, GPUCache, DawnCache, Crashpad, LOCK, Singleton*).
  • Ownership normalization is a possible hardening follow-up if live validation finds mixed owners; it is not in the current hook.

Directory name must equal Feature.Id (ElizaOSData).

4. The GTK UI row (required or the frontend crashes)

tps_frontend/feature.py requires elizaos_data_box/_row/_switch widgets in features_view.ui.in or it raises RuntimeError at startup. Add a row to TAILS/config/chroot_local-includes/usr/share/tails/persistent-storage/features_view.ui.in — copy an existing simple row (e.g. gnu_pg_row), rename the three widget ids to the elizaos_data_ prefix, title "elizaOS", subtitle "Chat history, built apps, downloaded models, sign-in". Because enabled_by_default=True, the switch is pre-toggled after Create.

5. Chat actions (identification only — these are agent-side, Phase 6/9)

Two new elizaOS Actions in the elizaOS agent, NOT Tails code:

  • "save my work to encrypted USB" — query the tps D-Bus service org.boum.tails.PersistentStorage IsCreated; if false, exec /usr/local/bin/tails-persistent-storage (Tails' GUI). Do not reimplement LUKS — that was the older prototype's mistake. The chat surface can be simple, but its runner must point at tpscli / the D-Bus service.
  • "what's on my persistent storage?" — enumerate enabled features via tpscli/D-Bus, du -sh each binding dest.

The Tails-side contract Phase 7 owns: /etc/elizaos/... is irrelevant here — tps's D-Bus service + persistence.conf are the source of truth.

6. Lessons from older prototype persistence bugs to avoid

The older prototype hand-rolled a shell+cryptsetup script and hit: a hardcoded partition slot (bricked the EFI partition), a LUKS in-use kernel lock, and mount-path drift. tps already solved every one — that is the whole point of "Tails-native". So: do not write partition-selection logic, do not pre-create partitions in a build hook, do not hardcode mount paths (the Feature uses relative src paths). And inspect the built squashfs to confirm the modified features.py is actually in it (don't trust grep).

Ordered implementation checklist

  1. Add the ElizaOSData Feature subclass to tps/configuration/features.py. Done locally.
  2. Add the elizaos_data_* row to features_view.ui.in. Done locally.
  3. Add the on-activated-hooks/ElizaOSData/10-clean-runtime-state hook. Done locally.
  4. Confirm Tails' NetworkConnections feature is offered in the greeter UI — do NOT re-declare it.
  5. Add the 2 agent chat actions (thin — shell Tails' GUI).
  6. Build the ISO; inspect the squashfs for the 3 modified/added files.
  7. QEMU multi-partition USB test: amnesia first-boot → create-via-chat → reboot → greeter unlock → state intact → confirm ~/.eliza is a bind-mountpoint and sockets/ was wiped. Repeat the create→unlock leg under Privacy Mode (persistence is orthogonal to Tor).
  8. Record any amnesia/persistent behavior difference in docs/mode-parity.md before merge.