Ship the Bare ELF in the release and stop baking host paths into the service.
Rolling release / release (push) Failing after 6m40s

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]>
This commit is contained in:
2026-09-14 12:29:26 -04:00
co-authored by Cursor
parent 3c9257ce88
commit c88062ba69
9 changed files with 58 additions and 19 deletions
+1
View File
@@ -31,6 +31,7 @@ jobs:
mkdir -p /tmp/jarvis-ci-models
printf '%s\n' '{"cacheDirectory":"/tmp/jarvis-ci-models"}' > "$QVAC_CONFIG_PATH"
npm run package
tar -tzf dist/jarvis-qvac-bare-linux-*.tar.gz | grep -E 'node_modules/bare-runtime-linux-.*/bin/bare$'
- name: Publish rolling prerelease to Gitea
env:
GITEA_SERVER_URL: ${{ gitea.server_url }}
+5 -4
View File
@@ -40,10 +40,11 @@ npm run bare:smoke
npm run package:bare
```
The installer executes `node_modules/bare-runtime-linux-*/bin/bare` directly.
The `node_modules/.bin/bare` convenience script is a Node launcher and is not
used by the service. The generated systemd unit therefore has no Node
executable in its `ExecStart` line.
The installer requires `node_modules/bare-runtime-linux-*/bin/bare` inside the
installed tree. It does not fall back to a `bare` binary on `PATH`. The user
unit starts `%h/.local/share/jarvis-qvac/packaging/jarvisd-launch.sh`, which
resolves that ELF relative to the install. The unit does not contain a
machine path, and the checkout unit under `systemd/user/` is not shipped.
GPU policy remains strict. The Bare QVAC engine must report a visible GPU and
the master load path passes `device: "gpu"`, full GPU layer offload, and GPU
+4 -5
View File
@@ -50,11 +50,10 @@ journalctl --user -u jarvisd.service -f
```
The unit at `packaging/jarvisd.service` (installed to
`~/.config/systemd/user/jarvisd.service`) starts Bare on
`daemon/bare-entry.js` with working directory `~/.local/share/jarvis-qvac` and
`QVAC_CONFIG_PATH=~/.config/jarvis/qvac.config.json`. The repository
development unit uses the checkout path; use the packaged unit when testing
installation behavior.
`~/.config/systemd/user/jarvisd.service`) starts the bundled Bare ELF through
`packaging/jarvisd-launch.sh`. It uses `%h` for the install directory and does
not record a host path. The file `systemd/user/jarvisd.service` is a checkout
helper only and is not included in the release archive.
Restart after daemon code changes:
+15 -2
View File
@@ -15,7 +15,8 @@ mkdir -p "${OUT_DIR}"
BUNDLE_DIR="${OUT_DIR}/jarvis-qvac-bare-${PLATFORM}"
rm -rf "${BUNDLE_DIR}" "${OUT_DIR}/jarvis-qvac-bare-${PLATFORM}.tar.gz"
mkdir -p "${BUNDLE_DIR}"
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 "${BUNDLE_DIR}"
# The checkout unit points at a developer tree and Node. Do not ship it.
tar --exclude='.git' --exclude='./.agents' --exclude='./.codex' --exclude='./dist' --exclude='./systemd' --exclude='./vendor/agent-harness/node_modules' --exclude='*/__pycache__' --exclude='*.pyc' -cf - -C "${ROOT_DIR}" . | tar -xf - -C "${BUNDLE_DIR}"
cp -a "${ROOT_DIR}/node_modules" "${BUNDLE_DIR}/"
# QVAC publishes one package containing native prebuilds for every supported
# platform. The rolling asset is architecture-specific: retain all Linux x64
@@ -31,6 +32,18 @@ fi
if [[ "${JARVIS_VERIFY_VOICE_MODELS:-0}" == "1" ]]; then
(cd "${BUNDLE_DIR}" && bash packaging/bare-launch.sh packaging/bare-run.js packaging/voice-model-smoke.js)
fi
chmod 755 "${BUNDLE_DIR}/packaging/jarvisd-launch.sh" "${BUNDLE_DIR}/node_modules/bare-runtime-${PLATFORM}/bin/bare"
tar -C "${OUT_DIR}" -czf "${OUT_DIR}/jarvis-qvac-bare-${PLATFORM}.tar.gz" "jarvis-qvac-bare-${PLATFORM}"
rm -rf "${BUNDLE_DIR}"
echo "${OUT_DIR}/jarvis-qvac-bare-${PLATFORM}.tar.gz"
ARCHIVE="${OUT_DIR}/jarvis-qvac-bare-${PLATFORM}.tar.gz"
MEMBER="jarvis-qvac-bare-${PLATFORM}/node_modules/bare-runtime-${PLATFORM}/bin/bare"
if ! tar -tzf "${ARCHIVE}" | grep -qx "${MEMBER}"; then
echo "Release archive is missing the Bare runtime executable: ${MEMBER}" >&2
exit 1
fi
if tar -tzf "${ARCHIVE}" | grep -q 'systemd/user/jarvisd.service'; then
echo "Release archive still contains the developer systemd unit." >&2
exit 1
fi
echo "Packaged Bare runtime ${MEMBER}"
echo "${ARCHIVE}"
+4 -5
View File
@@ -114,14 +114,13 @@ case "$(uname -m)" in
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
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}"
log "using Bare runtime ${BARE_BIN}"
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)"
@@ -193,7 +192,7 @@ activate_extension() {
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"
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}"
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Start the Bare ELF shipped with this install. Never use a bare from PATH.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
case "$(uname -m)" in
x86_64|amd64) BIN="${ROOT}/node_modules/bare-runtime-linux-x64/bin/bare" ;;
aarch64|arm64) BIN="${ROOT}/node_modules/bare-runtime-linux-arm64/bin/bare" ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 2 ;;
esac
if [[ ! -x "${BIN}" ]]; then
echo "This Jarvis install has no Bare runtime at ${BIN}." >&2
echo "Install the official rolling bundle. A Bare binary from PATH is not used." >&2
exit 1
fi
exec "${BIN}" "$@"
+1 -1
View File
@@ -7,7 +7,7 @@ PartOf=graphical-session.target
[Service]
Type=simple
WorkingDirectory=%h/.local/share/jarvis-qvac
ExecStart=__JARVIS_BARE__ %h/.local/share/jarvis-qvac/daemon/bare-entry.js
ExecStart=%h/.local/share/jarvis-qvac/packaging/jarvisd-launch.sh %h/.local/share/jarvis-qvac/daemon/bare-entry.js
Environment=QVAC_CONFIG_PATH=%h/.config/jarvis/qvac.config.json
Environment=JARVIS_GPU_REQUIRED=1
Environment=JARVIS_QVAC_OWNER=jarvisd
+8 -1
View File
@@ -32,8 +32,15 @@ created="$(curl -fsS -X POST "${AUTH[@]}" "${JSON[@]}" "${API}/releases" --data
release_id="$(jq -r '.id // empty' <<<"${created}")"
test -n "${release_id}"
shopt -s nullglob
bare_assets=(dist/jarvis-qvac-bare-*.tar.gz)
if (( ${#bare_assets[@]} == 0 )); then
echo "Refusing to publish: the Bare runtime bundle is missing." >&2
exit 1
fi
shopt -u nullglob
for asset in dist/jarvis-qvac_*.deb dist/jarvis-qvac-extension.zip dist/jarvis-qvac-bare-*.tar.gz dist/SHA256SUMS; do
[[ -f "${asset}" ]] || continue
[[ -f "${asset}" ]] || { echo "Refusing to publish: missing ${asset}" >&2; exit 1; }
curl -fsS -X POST "${AUTH[@]}" \
"${API}/releases/${release_id}/assets?name=$(basename "${asset}")" \
-F "attachment=@${asset}" >/dev/null
+5 -1
View File
@@ -39,6 +39,7 @@ test('install.sh replaces leftover files from an older version and keeps config'
await mkdir(bin, { recursive: true });
await cp(path.join(repo, 'packaging/install.sh'), path.join(root, 'packaging/install.sh'));
await cp(path.join(repo, 'packaging/jarvisd.service'), path.join(root, 'packaging/jarvisd.service'));
await cp(path.join(repo, 'packaging/jarvisd-launch.sh'), path.join(root, 'packaging/jarvisd-launch.sh'));
await cp(path.join(repo, 'packaging/qvac.config.template.json'), path.join(root, 'packaging/qvac.config.template.json'));
await cp(path.join(repo, 'apps/gnome-extension/[email protected]'), path.join(root, 'apps/gnome-extension/[email protected]'), { recursive: true });
await writeFile(path.join(root, 'node_modules', bareName, 'bin', 'bare'), '#!/bin/sh\nexit 0\n');
@@ -108,7 +109,9 @@ exit 0
assert.equal(await readFile(path.join(appDir, 'daemon-marker'), 'utf8'), 'new-tree\n');
assert.equal(await readFile(path.join(configDir, 'config.json'), 'utf8'), '{"wakePhrase":"hey jarvis","ttsEnabled":true}\n');
const unit = await readFile(path.join(home, '.config/systemd/user/jarvisd.service'), 'utf8');
assert.match(unit, /bare-runtime-linux/);
const execStart = unit.split('\n').find((line) => line.startsWith('ExecStart='));
assert.match(execStart, /ExecStart=%h\/\.local\/share\/jarvis-qvac\/packaging\/jarvisd-launch\.sh/);
assert.doesNotMatch(execStart, /\/home\/|dev\/jarvis|__JARVIS_BARE__|\/usr\/bin\/node/);
assert.match(unit, /WantedBy=default\.target/);
assert.match(unit, /Restart=always/);
assert.doesNotMatch(unit, /ExecStart=\/usr\/bin\/node/);
@@ -152,6 +155,7 @@ test('install.sh seeds default config when missing and does not require first-ru
await mkdir(bin, { recursive: true });
await cp(path.join(repo, 'packaging/install.sh'), path.join(root, 'packaging/install.sh'));
await cp(path.join(repo, 'packaging/jarvisd.service'), path.join(root, 'packaging/jarvisd.service'));
await cp(path.join(repo, 'packaging/jarvisd-launch.sh'), path.join(root, 'packaging/jarvisd-launch.sh'));
await cp(path.join(repo, 'packaging/qvac.config.template.json'), path.join(root, 'packaging/qvac.config.template.json'));
await cp(path.join(repo, 'apps/gnome-extension/[email protected]'), path.join(root, 'apps/gnome-extension/[email protected]'), { recursive: true });
await writeFile(path.join(root, 'node_modules', bareName, 'bin', 'bare'), '#!/bin/sh\nexit 0\n');