Files
gnome-jarvis/packaging/install.sh
T
snxravenandCursor c88062ba69
Rolling release / release (push) Failing after 6m40s
Ship the Bare ELF in the release and stop baking host paths into the service.
The rolling bundle already contains the runtime binary. The installer now starts that file through a relative launcher, and the checkout unit is no longer packed for other users.

Co-authored-by: Cursor <[email protected]>
2026-09-14 12:29:26 -04:00

279 lines
11 KiB
Bash
Executable File

#!/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/[email protected]"
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="[email protected]"
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 [[ -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. A Bare binary from PATH is not used." >&2
exit 1
fi
chmod 755 "${BARE_BIN}" "${ROOT_DIR}/packaging/jarvisd-launch.sh"
log "using bundled 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/[email protected]/." "${EXT_DIR}/"
log "copied GNOME extension files to ${EXT_DIR}"
( cd "${ROOT_DIR}/apps/gnome-extension/[email protected]" && 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_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/[email protected]/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" <<EOF
[Desktop Entry]
Name=Jarvis
GenericName=Voice Assistant
Comment=Local voice for your desktop
Exec=gjs -m ${APP_DIR}/apps/control-center/main.js
Icon=io.qvac.Jarvis.Control
Terminal=false
Type=Application
Categories=GTK;Settings;Utility;
Keywords=voice;assistant;jarvis;qvac;
StartupNotify=true
EOF
log "installed ${APP_HOME}/io.qvac.Jarvis.Control.desktop"
if [[ -f "${CONFIG_DIR}/config.json" ]]; then
log "kept existing ${CONFIG_DIR}/config.json"
else
cat > "${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 [email protected]"
fi