#!/bin/bash # omarchy:summary=Install the Hermes desktop app # omarchy:requires-sudo=true set -e if (( EUID == 0 )); then echo "Run this command as your desktop user, without sudo." >&2 exit 1 fi echo "Installing Hermes Desktop..." omarchy-pkg-add hermes-desktop if [[ ! -r /usr/share/hermes-desktop/install.sh || ! -r /usr/share/hermes-desktop/runtime.patch ]] || ! release_commit=$(jq -er 'select(.branch == "main") | .commit | select(test("^[0-9a-f]{40}$"))' /opt/hermes-desktop/resources/install-stamp.json 2>/dev/null); then echo "The installed Hermes package cannot prepare in-app updates. Run 'omarchy update', then try again." >&2 exit 1 fi # If Hermes was already installed for the terminal, the app supersedes it: one # machine, one Hermes. This drops that copy so the terminal, the default agent # and the app all end up on the app's installation. omarchy-install-hermes-cli || true # Keep the runtime at the root even when invoked from a Hermes profile. HERMES_HOME=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") home_parent=$(dirname -- "$HERMES_HOME") if [[ ${home_parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then HERMES_HOME=$(dirname -- "$home_parent") fi export HERMES_HOME runtime="$HERMES_HOME/hermes-agent" native_app="$runtime/apps/desktop/release/linux-unpacked" runtime_ready() { [[ -f $runtime/.hermes-bootstrap-complete && -f $runtime/venv/bin/hermes && -x $runtime/venv/bin/hermes && -f $runtime/venv/bin/python && -x $runtime/venv/bin/python ]] && timeout 15 "$runtime/venv/bin/hermes" --version >/dev/null 2>&1 } check_main() { local main_commit main_commit=$(git -C "$runtime" rev-parse --verify refs/heads/main 2>/dev/null || true) if [[ -n $main_commit && $main_commit != "$release_commit" && $main_commit != "$(git -C "$runtime" rev-parse --verify refs/remotes/origin/main 2>/dev/null)" ]]; then echo "Hermes main has local commits. Keep that work and prepare the desktop with 'hermes desktop --build-only'." >&2 return 1 fi } if ! runtime_ready; then # The upstream installer can reset an existing checkout. Do not pin a newer # or modified runtime back to the package release while repairing setup. if [[ -e $runtime || -L $runtime ]]; then if [[ $(git -C "$runtime" rev-parse HEAD 2>/dev/null) != "$release_commit" ]] || [[ -n $(git -C "$runtime" status --porcelain --untracked-files=all) ]]; then echo "Hermes setup is incomplete at $runtime. Repair that installation before trying again; existing files have been kept." >&2 exit 1 fi check_main fi # Upstream replaces these commands, including foreign files and symlinks. # Keep their original bytes/links before handing the names to the desktop. command_backup="" for command in hermes hermes-agent hermes-acp; do command_path="$HOME/.local/bin/$command" if [[ -e $command_path || -L $command_path ]]; then if [[ ! -f $command_path && ! -L $command_path ]]; then echo "Cannot replace $command_path: move it aside before installing Hermes Desktop." >&2 exit 1 fi if [[ -z $command_backup ]]; then command_backup=$(mktemp -d "$HOME/.local/bin/.hermes-before-desktop.XXXXXX") echo "Saving existing Hermes commands in $command_backup" fi cp -a -- "$command_path" "$command_backup/" fi done echo "Setting up the Hermes runtime..." bash /usr/share/hermes-desktop/install.sh --skip-setup --branch main --commit "$release_commit" --force-commit --dir "$runtime" --hermes-home "$HERMES_HOME" if ! runtime_ready; then echo "Hermes runtime setup did not complete. Re-run this command after resolving the installer error." >&2 exit 1 fi fi runtime_commit=$(git -C "$runtime" rev-parse HEAD) if [[ $runtime_commit == "$release_commit" ]]; then # The updater switches to main before checking for changes. Start main at # the packaged release, with enough history for its first fast-forward. check_main if [[ $(git -C "$runtime" rev-parse --is-shallow-repository) == "true" ]]; then git -C "$runtime" fetch --unshallow origin main fi git -C "$runtime" switch -C main "$release_commit" if git -C "$runtime" apply --check /usr/share/hermes-desktop/runtime.patch >/dev/null 2>&1; then git -C "$runtime" apply /usr/share/hermes-desktop/runtime.patch elif ! git -C "$runtime" apply --reverse --check /usr/share/hermes-desktop/runtime.patch >/dev/null 2>&1; then echo "The Hermes Linux runtime patch conflicts with local changes. Existing files have been kept." >&2 exit 1 fi fi if [[ -e $native_app || -L $native_app ]]; then if [[ ! -f $native_app/Hermes || ! -x $native_app/Hermes || ! -f $native_app/resources/app.asar || ! -f $native_app/resources/install-stamp.json ]]; then echo "The Hermes desktop app at $native_app is incomplete. Repair it with 'hermes desktop --build-only' before trying again." >&2 exit 1 fi else if [[ $runtime_commit != "$release_commit" ]]; then echo "The Hermes runtime has moved beyond the packaged desktop release. Run 'hermes desktop --build-only', then try again." >&2 exit 1 fi desktop_changes=$(git -C "$runtime" status --porcelain --untracked-files=all -- apps/desktop package.json package-lock.json) if [[ -n $desktop_changes ]]; then echo "Hermes desktop sources have local changes. Run 'hermes desktop --build-only', then try again; existing files have been kept." >&2 exit 1 fi mkdir -p -- "${native_app%/*}" staging=$(mktemp -d "${native_app%/*}/.linux-unpacked.XXXXXX") trap 'rm -rf -- "$staging"' EXIT cp -a /opt/hermes-desktop/. "$staging/" chmod 0755 "$staging/chrome-sandbox" mv -T --no-clobber -- "$staging" "$native_app" if [[ -e $staging ]]; then echo "A Hermes desktop app appeared during setup. It has been kept; please try again." >&2 exit 1 fi trap - EXIT # Record this matching prebuilt app using the CLI's own content hash, so # subsequent menu launches do not rebuild an app that is already current. env -u PYTHONPATH -u PYTHONHOME "$runtime/venv/bin/python" - "$runtime" <<'PY' import sys from pathlib import Path sys.path.insert(0, sys.argv[1]) if Path(sys.argv[1], "hermes_cli/main_desktop.py").is_file(): from hermes_cli.main_desktop import _write_desktop_build_stamp else: from hermes_cli.main import _write_desktop_build_stamp _write_desktop_build_stamp(Path(sys.argv[1]), source_mode=False) PY fi echo "Opening Hermes Desktop..." setsid uwsm-app -- /usr/bin/hermes-desktop >/dev/null 2>&1 & # Only a running Hermes can be told which skin to show; a unit outlives this # terminal and reports to the journal. echo "Matching Hermes to the current theme once it is set up..." systemctl --user stop omarchy-hermes-theme.service 2>/dev/null || true systemd-run --user --quiet --collect --unit=omarchy-hermes-theme omarchy-theme-set-hermes --wait echo "" echo "Hermes Desktop has been installed."