#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" command -v zip >/dev/null || { echo "zip is required to register the GNOME extension" >&2; exit 1; } APP_DIR="${HOME}/.local/share/jarvis-qvac" EXT_DIR="${XDG_DATA_HOME:-${HOME}/.local/share}/gnome-shell/extensions/jarvis@qvac.local" CONFIG_DIR="${HOME}/.config/jarvis" CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/jarvis/models" APP_PARENT="$(dirname "${APP_DIR}")" LOG_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/jarvis" LOG_FILE="${LOG_DIR}/install.log" mkdir -p "${APP_PARENT}" "${CONFIG_DIR}" "${HOME}/.config/systemd/user" "${CACHE_DIR}" "${LOG_DIR}" log() { local line line="$(date -Iseconds 2>/dev/null || date '+%Y-%m-%dT%H:%M:%S%z') jarvis-install: $*" printf '%s\n' "${line}" | tee -a "${LOG_FILE}" if command -v logger >/dev/null; then logger --tag jarvis-install -- "$*" 2>/dev/null || logger -t jarvis-install -- "$*" 2>/dev/null || true fi } STAGING="" EXT_TMP_DIR="" cleanup_install() { if [[ -n "${STAGING}" && -e "${STAGING}" ]]; then log "removing unfinished staging directory ${STAGING}" rm -rf "${STAGING}" fi rm -rf "${EXT_TMP_DIR}" } trap cleanup_install EXIT has_bundled_bare() { [[ -f "${ROOT_DIR}/node_modules/bare-runtime-linux-x64/bin/bare" || -f "${ROOT_DIR}/node_modules/bare-runtime-linux-arm64/bin/bare" ]] } extract_tree() { local dest="$1" mkdir -p "${dest}" if has_bundled_bare; then log "staging bundled Bare tree from ${ROOT_DIR} into ${dest}" tar --exclude='.git' --exclude='./.agents' --exclude='./.codex' --exclude='./dist' --exclude='./vendor/agent-harness/node_modules' --exclude='*/__pycache__' --exclude='*.pyc' -cf - -C "${ROOT_DIR}" . | tar -xf - -C "${dest}" else log "staging source tree from ${ROOT_DIR} into ${dest} (excluding node_modules)" tar --exclude='.git' --exclude='./.agents' --exclude='./.codex' --exclude='./dist' --exclude='./node_modules' --exclude='*/node_modules' --exclude='*/__pycache__' --exclude='*.pyc' -cf - -C "${ROOT_DIR}" . | tar -xf - -C "${dest}" fi } log "starting install from ${ROOT_DIR}" log "application directory ${APP_DIR}; extension ${EXT_DIR}" log "preserving config ${CONFIG_DIR}, cache ${CACHE_DIR}, and user data under ${HOME}/.local/share/jarvis" PREVIOUSLY_ACTIVE=false if command -v systemctl >/dev/null; then if systemctl --user is-active --quiet jarvisd.service 2>/dev/null; then PREVIOUSLY_ACTIVE=true log "stopping running jarvisd.service before replacing files" if systemctl --user stop jarvisd.service; then log "stopped jarvisd.service" else log "warning: could not stop jarvisd.service; continuing with replacement" fi else log "jarvisd.service was not running" fi else log "systemctl not found; skipped service stop" fi if [[ -e "${APP_DIR}" ]]; then log "previous application tree found at ${APP_DIR}" else log "no previous application tree at ${APP_DIR}" fi if [[ -e "${EXT_DIR}" ]]; then log "previous GNOME extension found at ${EXT_DIR}" else log "no previous GNOME extension at ${EXT_DIR}" fi EXT_UUID="jarvis@qvac.local" SHOULD_ENABLE_EXTENSION=false if [[ "${1:-}" == "--enable" ]]; then SHOULD_ENABLE_EXTENSION=true; fi if command -v gsettings >/dev/null; then ENABLED_EXTENSIONS="$(gsettings get org.gnome.shell enabled-extensions 2>/dev/null || printf '[]')" if [[ "${ENABLED_EXTENSIONS}" == *"${EXT_UUID}"* ]]; then SHOULD_ENABLE_EXTENSION=true; fi fi if command -v gnome-extensions >/dev/null && gnome-extensions info "${EXT_UUID}" 2>/dev/null | grep -q 'Enabled: Yes'; then SHOULD_ENABLE_EXTENSION=true log "GNOME extension ${EXT_UUID} is currently enabled; it will be reactivated after the file replace" fi shopt -s nullglob for leftover in "${APP_PARENT}"/jarvis-qvac.staging.*; do log "removing leftover staging directory ${leftover}" rm -rf "${leftover}" done shopt -u nullglob STAGING="$(mktemp -d "${APP_PARENT}/jarvis-qvac.staging.XXXXXX")" extract_tree "${STAGING}" if [[ -e "${APP_DIR}" ]]; then log "removing previous application tree ${APP_DIR}" rm -rf "${APP_DIR}" fi log "moving staged tree ${STAGING} into ${APP_DIR}" mv "${STAGING}" "${APP_DIR}" STAGING="" log "application tree replaced at ${APP_DIR}" case "$(uname -m)" in x86_64|amd64) BARE_BIN="${APP_DIR}/node_modules/bare-runtime-linux-x64/bin/bare" ;; aarch64|arm64) BARE_BIN="${APP_DIR}/node_modules/bare-runtime-linux-arm64/bin/bare" ;; *) BARE_BIN="" ;; esac if [[ ! -x "${BARE_BIN}" ]]; then BARE_BIN="$(command -v bare || true)"; fi if [[ -z "${BARE_BIN}" || ! -x "${BARE_BIN}" ]]; then log "error: no Bare runtime for $(uname -m) in the installed bundle" echo "The installed bundle does not contain a Bare runtime for $(uname -m). Install the official rolling bundle or build it with npm run package:bare." >&2 exit 1 fi chmod 755 "${BARE_BIN}" log "using Bare runtime ${BARE_BIN}" NODE_BIN="$(command -v node || true)" if [[ -z "${NODE_BIN}" || ! -x "${NODE_BIN}" ]]; then NODE_BIN="$(bash -lc 'command -v node' 2>/dev/null || true)" fi if [[ -n "${NODE_BIN}" && -x "${NODE_BIN}" ]]; then log "using Node.js ${NODE_BIN} for the browser sidecar" else NODE_BIN="" log "warning: no Node.js on PATH; set JARVIS_BROWSER_NODE after install so the browser sidecar can start" fi EXT_TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/jarvis-extension.XXXXXX")" EXT_ZIP="${EXT_TMP_DIR}/jarvis-extension.zip" log "replacing GNOME extension files at ${EXT_DIR}" rm -rf "${EXT_DIR}" mkdir -p "${EXT_DIR}" cp -a "${ROOT_DIR}/apps/gnome-extension/jarvis@qvac.local/." "${EXT_DIR}/" log "copied GNOME extension files to ${EXT_DIR}" ( cd "${ROOT_DIR}/apps/gnome-extension/jarvis@qvac.local" && zip -q -r "${EXT_ZIP}" . ) if command -v gnome-extensions >/dev/null; then if gnome-extensions install --force "${EXT_ZIP}"; then log "registered GNOME extension from ${EXT_ZIP}" else log "warning: gnome-extensions install failed; the HUD files are already in ${EXT_DIR}" echo "GNOME extension registration failed; the files were copied directly." >&2 fi fi if command -v glib-compile-schemas >/dev/null && [[ -d "${EXT_DIR}/schemas" ]]; then glib-compile-schemas "${EXT_DIR}/schemas" >/dev/null log "compiled GSettings schemas in ${EXT_DIR}/schemas" fi persist_enabled_extension() { command -v gsettings >/dev/null || return 0 gsettings writable org.gnome.shell enabled-extensions >/dev/null 2>&1 || return 0 ENABLED_EXTENSIONS="$(gsettings get org.gnome.shell enabled-extensions 2>/dev/null || printf '[]')" if [[ "${ENABLED_EXTENSIONS}" == *"${EXT_UUID}"* ]]; then log "org.gnome.shell enabled-extensions already lists ${EXT_UUID}" return 0 fi if [[ "${ENABLED_EXTENSIONS}" == "[]" ]]; then ENABLED_EXTENSIONS="['${EXT_UUID}']" else ENABLED_EXTENSIONS="${ENABLED_EXTENSIONS%]}, '${EXT_UUID}']" fi gsettings set org.gnome.shell enabled-extensions "${ENABLED_EXTENSIONS}" log "persisted ${EXT_UUID} in org.gnome.shell enabled-extensions" } activate_extension() { persist_enabled_extension if command -v gdbus >/dev/null; then if gdbus call --session --dest org.gnome.Shell.Extensions --object-path /org/gnome/Shell/Extensions --method org.gnome.Shell.Extensions.ReloadExtension "${EXT_UUID}" >/dev/null 2>&1; then log "reloaded ${EXT_UUID} in the running GNOME Shell" else log "GNOME Shell did not reload ${EXT_UUID} yet" fi if gdbus call --session --dest org.gnome.Shell.Extensions --object-path /org/gnome/Shell/Extensions --method org.gnome.Shell.Extensions.EnableExtension "${EXT_UUID}" >/dev/null 2>&1; then log "GNOME Shell EnableExtension succeeded for ${EXT_UUID}" else log "GNOME Shell EnableExtension did not activate ${EXT_UUID}" fi fi if command -v gnome-extensions >/dev/null && gnome-extensions enable "${EXT_UUID}" >/dev/null 2>&1; then log "enabled GNOME extension ${EXT_UUID}" else log "gnome-extensions enable ${EXT_UUID} failed" fi if command -v gnome-extensions >/dev/null && gnome-extensions info "${EXT_UUID}" 2>/dev/null | grep -q 'Enabled: Yes'; then log "GNOME extension ${EXT_UUID} is enabled in this session" else log "GNOME extension ${EXT_UUID} is installed but not active in this session; log out and back in, or run: gnome-extensions enable ${EXT_UUID}" fi } sed -e "s#__JARVIS_BARE__#${BARE_BIN}#" -e "s#__JARVIS_NODE__#${NODE_BIN}#" "${ROOT_DIR}/packaging/jarvisd.service" > "${HOME}/.config/systemd/user/jarvisd.service" log "wrote ${HOME}/.config/systemd/user/jarvisd.service" DBUS_DIR="${XDG_DATA_HOME:-${HOME}/.local/share}/dbus-1/services" mkdir -p "${DBUS_DIR}" cat > "${DBUS_DIR}/io.qvac.Jarvis.service" <<'EOF' [D-BUS Service] Name=io.qvac.Jarvis Exec=/bin/false SystemdService=jarvisd.service EOF log "wrote ${DBUS_DIR}/io.qvac.Jarvis.service" ICON_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/icons/hicolor/scalable/apps" APP_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/applications" mkdir -p "${ICON_HOME}" "${APP_HOME}" ICON_SRC="${ROOT_DIR}/apps/gnome-extension/jarvis@qvac.local/brand/icons/io.qvac.Jarvis.Control.svg" if [[ -f "${ICON_SRC}" ]]; then cp "${ICON_SRC}" "${ICON_HOME}/io.qvac.Jarvis.Control.svg" log "installed application icon io.qvac.Jarvis.Control" fi cat > "${APP_HOME}/io.qvac.Jarvis.Control.desktop" < "${CONFIG_DIR}/config.json" <<'EOF' { "wakePhrase": "hey jarvis", "wakeCommand": "jarvis-wake-bridge", "modelProfile": "laptop-16gb", "ttsEnabled": true, "fsAccess": "workspace", "freeVramOnIdle": true } EOF log "created ${CONFIG_DIR}/config.json with default wake phrase, CPU wake detector, laptop-16gb profile, and TTS enabled" fi if [[ ! -f "${CONFIG_DIR}/qvac.config.json" ]]; then sed "s#__JARVIS_CACHE__#${CACHE_DIR}#g" "${ROOT_DIR}/packaging/qvac.config.template.json" > "${CONFIG_DIR}/qvac.config.json" log "created ${CONFIG_DIR}/qvac.config.json" else log "kept existing ${CONFIG_DIR}/qvac.config.json" fi restart_user_service() { systemctl --user daemon-reload log "reloaded user systemd units" if [[ "${1:-}" == "enable" ]]; then systemctl --user enable jarvisd.service log "enabled jarvisd.service" fi systemctl --user restart jarvisd.service log "restarted jarvisd.service" } if [[ "${1:-}" == "--enable" ]]; then restart_user_service enable activate_extension else if [[ "${PREVIOUSLY_ACTIVE}" == true ]]; then restart_user_service log "restarted jarvisd.service with the upgraded files" fi if [[ "${SHOULD_ENABLE_EXTENSION}" == true ]]; then activate_extension fi fi log "install complete; log file ${LOG_FILE}" echo "Installed JARVIS-QVAC to ${APP_DIR}" if [[ "${1:-}" == "--enable" ]] || [[ "${PREVIOUSLY_ACTIVE}" == true ]]; then echo "Jarvis is ready. Default wake phrase: hey jarvis. Change it in Settings." else echo "Start with: systemctl --user enable --now jarvisd.service" echo "Then enable the GNOME extension: gnome-extensions enable jarvis@qvac.local" fi