Skip to content
Open
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,25 @@ all of that app's factory-image changes to an existing VM, and an in-guest
update should not be assumed to reproduce them. A confirmed reset is the
deliberate, destructive way to start again from the newest bundled factory.

### Updating integrations in an existing VM

The Mac launcher’s **VM integrations → Review…** action explains how to add
new Try Omarchy features to an existing VM. It offers a one-time setup command
for guests that do not yet have the integration manager. Run that command in an
Omarchy terminal; it mounts the app’s dedicated read-only bundle and opens a
review before requesting the Linux administrator password. SSH and personal
folder sharing are not required.

After setup, use **Omarchy Menu → Setup → Try Omarchy Integrations** or run
`try-omarchy-integrations`. The initial guide installs or updates the sudo Touch ID support already bundled
with Try Omarchy. Biometric pairing remains a separate explicit choice. It does
not install pending integrations or upgrade the guest OS.

The app checks integration status after every VM launch. The launcher labels
cached results **Last check**. A guest that does not respond may need setup or
repair; a timeout is not proof that its components are absent. See
[integration updates](docs/integration-updates.md) for scope and recovery details.

### Growing an existing VM disk

To add capacity without resetting the VM, shut down Omarchy and run the
Expand Down
8 changes: 4 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,10 @@ but the direct-boot kernel and matching headers, the packaged
`try-omarchy-runtime`, and reviewed compatibility backports remain pinned in
Try Omarchy's prioritized local repository. Reusing a disk therefore does not
silently import a newer app's factory contents, and running the in-guest updater
must not be described as reproducing every factory-image change. Delivering
new Try Omarchy runtime or backport revisions to existing disks requires an
explicitly designed in-guest migration channel; today a factory reset is the
way to opt into the complete new factory.
must not be described as reproducing every factory-image change. The bundled integration manager provides an explicit migration channel for
reviewed guest integrations, with user-approved installation and per-VM status
reporting. It does not replace the pinned kernel or reproduce every factory
change. Factory reset remains the way to opt into the complete new factory.

Optional, user-initiated installers run after the factory image has been built
and are a separate trust boundary. They may resolve a mutable current release
Expand Down
75 changes: 75 additions & 0 deletions docs/integration-updates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Integration updates for existing VMs

App upgrades retain existing guest disks. The integration manager delivers
reviewed guest features independently of the bundled factory image.

## First setup

Open **VM integrations > Review…** in the Mac launcher. Launch Omarchy and paste
the supplied command into an Omarchy terminal. It mounts the app's dedicated
read-only 9p share at `/mnt/try-omarchy-updates` and opens a review. The share is
separate from the optional personal shared folder and needs no SSH connection.

Choose **Install/update integration support** and review replacements before
confirming. Installation asks for the Linux user's sudo authorization, retains
backups, and verifies each component before recording it as complete. Biometric
enrollment remains a separate action. Existing PAM enrollment is preserved.

The guide is then available under **Omarchy Menu > Setup > Try Omarchy
Integrations**, or with `try-omarchy-integrations` in the guest terminal.

## Features and boundaries

- sudo Touch ID: installs support; pairing is explicit and can be tested or repaired.

The initial bundle contains only upstream sudo Touch ID support. Additional
integrations can be added after their own upstream review. The manager does not
replace the kernel, upgrade the graphics stack, repair package holds, install
1Password integration, or reproduce every change in a newer factory image. Ordinary package
updates remain with Omarchy Update. No VM reset is required for these integrations.

## Status

A dedicated virtio port carries bounded status reports to the host every ten
seconds. Every VM launch starts a new check. After 120 seconds without a valid
report the host shows that setup or repair may be needed and continues listening.
An older, slow, or stopped guest agent cannot be distinguished by silence alone.

When setup, updates, or repairs may be needed, the app offers a review once per
bundled integration revision for that disk. Choosing Later leaves the VM running
and keeps the review action available. Checks still run on every launch.

The Mac menu bar provides a live integration status and review action. The
launcher shows the last check for the selected persistent disk. A report of
current components means installed files and relevant services passed inspection;
it does not attest that Touch ID was successfully used. Status messages never
execute commands or authorize host or guest installation.

## Failure and retry

An installed bundle with additional integrations is not replaced by this smaller
bundle. Use an app that supports those integrations; their files and enrollment
are left intact.

The updater verifies the exact bundle inventory and hashes before installation,
then stages a root-private copy. The app signature covers the bundle and manifest;
hashes detect corruption and do not independently establish trust in an app.

Previous files, the previous installed bundle, and progress are retained under
`/var/lib/try-omarchy/integrations`. A component is marked complete only after
verification. Rerunning skips a previously completed step only when its files and
required services still match. This is resumable installation, not a transactional
rollback of all PAM or systemd effects. A failed step prints its error and leaves
progress and backups available for repair.

Installation lists existing integration files that differ before asking to
replace them. Unrelated menu entries and package-configuration settings are
preserved. Unsupported or unsafe paths stop the operation. Close Omarchy Update
before installing integrations. An active package transaction blocks installation.

Guest status diagnostics:

```sh
systemctl status try-omarchy-integrations.service --no-pager
sudo journalctl -u try-omarchy-integrations.service -b -n 40 --no-pager
```
10 changes: 8 additions & 2 deletions guest/native-overlay/usr/local/bin/try-omarchy-touch-id-test
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

set -eu

printf '%s\n' \
"Testing Touch ID for sudo. Approve the Touch ID prompt on your Mac." \
"Password fallback is disabled for this test; no guest password will be requested." \
""
sudo -k
if SUDO_ASKPASS=/bin/false sudo -A true; then
echo "sudo authenticated without a guest password. Touch ID was used if its prompt appeared."
echo "Test passed: sudo authenticated without a guest password."
echo "If you approved the Touch ID prompt, Touch ID completed this test."
else
echo "Touch ID sudo check failed; password fallback was not accepted." >&2
printf '\n%s\n' "Touch ID authentication did not complete." >&2
echo "Password fallback is disabled for this test. Your guest password remains available for normal sudo commands." >&2
exit 1
fi
9 changes: 9 additions & 0 deletions guest/scripts/configure-rootfs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ mkdir -p "$root/usr/local/lib/try-omarchy"
install -m 0755 "$guest_dir/scripts/finalize-rootfs.sh" "$root/usr/local/lib/try-omarchy/finalize-rootfs"
install -m 0644 "$spec" "$root/usr/share/try-omarchy/build-spec.json"

# Fresh guests report integration status from their first boot. Older guests
# receive the same bundle through the app's explicit bootstrap flow.
python3 "$guest_dir/../integrations/build-bundle.py" "$root/usr/local/share/try-omarchy/integrations"
install -m 0755 "$guest_dir/../integrations/try-omarchy-integrations" "$root/usr/local/bin/try-omarchy-integrations"
install -m 0644 "$guest_dir/../integrations/try-omarchy-integrations.service" "$root/usr/lib/systemd/system/try-omarchy-integrations.service"
mkdir -p "$root/etc/systemd/system/multi-user.target.wants"
ln -s /usr/lib/systemd/system/try-omarchy-integrations.service "$root/etc/systemd/system/multi-user.target.wants/try-omarchy-integrations.service"
python3 "$root/usr/local/share/try-omarchy/integrations/updater.py" stage-menu "$root/etc/skel/.config/omarchy/extensions/omarchy-menu.jsonc"

# Record content digests before the user overlay is copied into $HOME. This is
# the machine-readable proof that the compositor/shell runtime came from the
# pinned Omarchy tree rather than a frontend reproduction.
Expand Down
9 changes: 6 additions & 3 deletions guest/scripts/install-touch-id-menu-entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def fail(message: str) -> None:
raise SystemExit(f"install-touch-id-menu-entry: {message}")


def install(path: Path) -> None:
def install(path: Path, previous_entry: str | None = None) -> None:
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
try:
info = path.stat(follow_symlinks=False)
Expand All @@ -45,15 +45,18 @@ def install(path: Path) -> None:
text = data.decode("utf-8")
except UnicodeDecodeError:
fail("menu extension is not UTF-8")
if ENTRY_ID in text:
if ENTRY_ID in text and (previous_entry is None or text.count(previous_entry) != 1):
return

opening = text.find("{")
if opening < 0 or text[:opening].strip():
fail("menu extension does not start with a JSONC object")
if text.rstrip()[-1:] != "}":
fail("menu extension is not a JSONC object")
updated = text[: opening + 1] + "\n" + ENTRY + text[opening + 1 :]
if ENTRY_ID in text:
updated = text.replace(previous_entry, ENTRY, 1)
else:
updated = text[: opening + 1] + "\n" + ENTRY + text[opening + 1 :]

directory = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
temporary = f".{path.name}.{secrets.token_hex(8)}"
Expand Down
Loading
Loading