@@ -236,7 +236,10 @@ To remove Jarvis-owned memory, cache, and generated media as well:
|
||||
bash packaging/uninstall.sh --yes --purge-data
|
||||
~~~
|
||||
|
||||
Read [Migration notes](docs/MIGRATIONS.md) before upgrading or moving an
|
||||
Re-running the installer upgrades in place: it stops the old daemon, removes
|
||||
the previous application and extension files, then copies the new tree.
|
||||
Config, memory, and model cache are kept. Read
|
||||
[Migration notes](docs/MIGRATIONS.md) before upgrading or moving an
|
||||
installation.
|
||||
|
||||
## Documentation
|
||||
|
||||
+9
-3
@@ -6,9 +6,15 @@ The user installer keeps application files in
|
||||
`~/.config/jarvis/qvac.config.json`. Memory, voice references, audit logs, and
|
||||
generated media remain under their existing Jarvis data directories.
|
||||
|
||||
Upgrades replace only the application and extension files. Existing config and
|
||||
data are preserved. Run `systemctl --user daemon-reload` after installing a new
|
||||
service file, then restart `jarvisd.service`.
|
||||
Upgrades stop a running `jarvisd.service`, uninstall the previous GNOME
|
||||
extension, and replace `~/.local/share/jarvis-qvac` from a staging directory so
|
||||
leftover files from older versions cannot mix with the new tree. Failed staging
|
||||
directories next to the install prefix are removed first. Existing config and
|
||||
data under `~/.config/jarvis`, `~/.local/share/jarvis`, and the model cache are
|
||||
preserved. If the service was running, the installer restarts it after the
|
||||
replacement; `--enable` also enables the unit. Run
|
||||
`systemctl --user daemon-reload` only when you install the service file by
|
||||
hand.
|
||||
|
||||
The uninstall command requires `--yes` and preserves data. Add
|
||||
`--purge-data` only when the user explicitly wants Jarvis-owned memory, cache,
|
||||
|
||||
+5
-3
@@ -16,9 +16,11 @@ curl -fsSL https://git.ssh.surf/snxraven/gnome-jarvis/raw/branch/main/packaging/
|
||||
~~~
|
||||
|
||||
The installer downloads the self-contained Bare bundle from the `rolling`
|
||||
release and delegates to the repository installer. Omit `--enable` to copy
|
||||
files without starting the service. Use `JARVIS_SERVER`, `JARVIS_REPO`, and
|
||||
`JARVIS_BUNDLE_URL` for a mirror.
|
||||
release and delegates to the repository installer. Re-running it replaces the
|
||||
previous user-local application and extension trees after stopping
|
||||
`jarvisd.service`; config and data stay in place. Omit `--enable` to copy
|
||||
files without starting the service unless the daemon was already running.
|
||||
Use `JARVIS_SERVER`, `JARVIS_REPO`, and `JARVIS_BUNDLE_URL` for a mirror.
|
||||
|
||||
## Development start
|
||||
|
||||
|
||||
+62
-11
@@ -6,12 +6,58 @@ 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"
|
||||
mkdir -p "${APP_DIR}" "${EXT_DIR}" "${CONFIG_DIR}" "${HOME}/.config/systemd/user" "${CACHE_DIR}"
|
||||
if [[ -f "${ROOT_DIR}/node_modules/bare-runtime-linux-x64/bin/bare" || -f "${ROOT_DIR}/node_modules/bare-runtime-linux-arm64/bin/bare" ]]; then
|
||||
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 "${APP_DIR}"
|
||||
else
|
||||
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 "${APP_DIR}"
|
||||
APP_PARENT="$(dirname "${APP_DIR}")"
|
||||
mkdir -p "${APP_PARENT}" "${CONFIG_DIR}" "${HOME}/.config/systemd/user" "${CACHE_DIR}"
|
||||
|
||||
STAGING=""
|
||||
EXT_TMP_DIR=""
|
||||
cleanup_install() { rm -rf "${STAGING}" "${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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
systemctl --user stop jarvisd.service || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -e "${APP_DIR}" || -e "${EXT_DIR}" ]]; then
|
||||
echo "Cleaning the previous JARVIS-QVAC install so this upgrade is a clean replacement."
|
||||
fi
|
||||
|
||||
if command -v gnome-extensions >/dev/null; then
|
||||
gnome-extensions disable [email protected] >/dev/null 2>&1 || true
|
||||
gnome-extensions uninstall [email protected] >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -rf "${EXT_DIR}"
|
||||
|
||||
shopt -s nullglob
|
||||
for leftover in "${APP_PARENT}"/jarvis-qvac.staging.*; do
|
||||
rm -rf "${leftover}"
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
STAGING="$(mktemp -d "${APP_PARENT}/jarvis-qvac.staging.XXXXXX")"
|
||||
extract_tree "${STAGING}"
|
||||
rm -rf "${APP_DIR}"
|
||||
mv "${STAGING}" "${APP_DIR}"
|
||||
STAGING=""
|
||||
|
||||
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" ;;
|
||||
@@ -25,11 +71,8 @@ fi
|
||||
chmod 755 "${BARE_BIN}"
|
||||
EXT_TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/jarvis-extension.XXXXXX")"
|
||||
EXT_ZIP="${EXT_TMP_DIR}/jarvis-extension.zip"
|
||||
cleanup_ext() { rm -rf "${EXT_TMP_DIR}"; }
|
||||
trap cleanup_ext EXIT
|
||||
( cd "${ROOT_DIR}/apps/gnome-extension/[email protected]" && zip -q -r "${EXT_ZIP}" . )
|
||||
rm -rf "${EXT_DIR}"
|
||||
mkdir -p "${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
|
||||
echo "GNOME extension registration failed; installing the files directly." >&2
|
||||
@@ -45,10 +88,15 @@ sed "s#__JARVIS_BARE__#${BARE_BIN}#" "${ROOT_DIR}/packaging/jarvisd.service" > "
|
||||
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"
|
||||
fi
|
||||
if [[ "${1:-}" == "--enable" ]]; then
|
||||
restart_user_service() {
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable jarvisd.service
|
||||
if [[ "${1:-}" == "enable" ]]; then
|
||||
systemctl --user enable jarvisd.service
|
||||
fi
|
||||
systemctl --user restart jarvisd.service
|
||||
}
|
||||
if [[ "${1:-}" == "--enable" ]]; then
|
||||
restart_user_service enable
|
||||
if command -v gnome-extensions >/dev/null && gnome-extensions enable [email protected] >/dev/null 2>&1; then
|
||||
echo "GNOME extension enabled."
|
||||
elif command -v gsettings >/dev/null && gsettings writable org.gnome.shell enabled-extensions >/dev/null 2>&1; then
|
||||
@@ -67,6 +115,9 @@ if [[ "${1:-}" == "--enable" ]]; then
|
||||
else
|
||||
echo "GNOME Shell has not rescanned extensions; log out and back in, then run: gnome-extensions enable [email protected]"
|
||||
fi
|
||||
elif [[ "${PREVIOUSLY_ACTIVE}" == true ]]; then
|
||||
restart_user_service
|
||||
echo "Restarted jarvisd.service with the upgraded files."
|
||||
fi
|
||||
echo "Installed JARVIS-QVAC to ${APP_DIR}"
|
||||
echo "Run: ${APP_DIR}/packaging/first-run.sh"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { chmod, cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const repo = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
||||
|
||||
function run(command, args, env) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { env, cwd: repo });
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
||||
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
||||
child.on('error', reject);
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) resolve({ stdout, stderr });
|
||||
else reject(new Error(`install failed (${code}): ${stderr || stdout}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('install.sh replaces leftover files from an older version and keeps config', async () => {
|
||||
const work = await mkdtemp(path.join(os.tmpdir(), 'jarvis-install-'));
|
||||
const home = path.join(work, 'home');
|
||||
const root = path.join(work, 'src');
|
||||
const bin = path.join(work, 'bin');
|
||||
const log = path.join(work, 'commands.log');
|
||||
const machine = (await run('uname', ['-m'], process.env)).stdout.trim();
|
||||
const bareName = /aarch64|arm64/.test(machine) ? 'bare-runtime-linux-arm64' : 'bare-runtime-linux-x64';
|
||||
try {
|
||||
await mkdir(path.join(root, 'packaging'), { recursive: true });
|
||||
await mkdir(path.join(root, 'apps/gnome-extension'), { recursive: true });
|
||||
await mkdir(path.join(root, 'node_modules', bareName, 'bin'), { recursive: true });
|
||||
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/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');
|
||||
await chmod(path.join(root, 'node_modules', bareName, 'bin', 'bare'), 0o755);
|
||||
await writeFile(path.join(root, 'daemon-marker'), 'new-tree\n');
|
||||
|
||||
const appDir = path.join(home, '.local/share/jarvis-qvac');
|
||||
const extDir = path.join(home, '.local/share/gnome-shell/extensions/[email protected]');
|
||||
const configDir = path.join(home, '.config/jarvis');
|
||||
await mkdir(path.join(appDir, 'node_modules/old-pkg'), { recursive: true });
|
||||
await mkdir(extDir, { recursive: true });
|
||||
await mkdir(configDir, { recursive: true });
|
||||
await mkdir(path.join(home, '.local/share/jarvis-qvac.staging.oldfail'), { recursive: true });
|
||||
await writeFile(path.join(appDir, 'stale-old-version.js'), 'leftover from previous install\n');
|
||||
await writeFile(path.join(extDir, 'old-extension.js'), 'leftover extension\n');
|
||||
await writeFile(path.join(configDir, 'config.json'), '{"wakePhrase":"hey jarvis","ttsEnabled":true}\n');
|
||||
await writeFile(path.join(home, '.local/share/jarvis-qvac.staging.oldfail/junk'), 'failed staging\n');
|
||||
|
||||
await writeFile(path.join(bin, 'systemctl'), `#!/bin/sh
|
||||
echo "systemctl $*" >> '${log}'
|
||||
if [ "$2" = is-active ]; then exit 0; fi
|
||||
exit 0
|
||||
`);
|
||||
await writeFile(path.join(bin, 'gnome-extensions'), `#!/bin/sh
|
||||
echo "gnome-extensions $*" >> '${log}'
|
||||
if [ "$1" = install ]; then exit 1; fi
|
||||
exit 0
|
||||
`);
|
||||
await chmod(path.join(bin, 'systemctl'), 0o755);
|
||||
await chmod(path.join(bin, 'gnome-extensions'), 0o755);
|
||||
|
||||
await run('bash', [path.join(root, 'packaging/install.sh'), '--enable'], {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
PATH: `${bin}:${process.env.PATH}`,
|
||||
XDG_DATA_HOME: path.join(home, '.local/share'),
|
||||
XDG_CACHE_HOME: path.join(home, '.cache'),
|
||||
});
|
||||
|
||||
assert.equal(existsSync(path.join(appDir, 'stale-old-version.js')), false);
|
||||
assert.equal(existsSync(path.join(appDir, 'node_modules/old-pkg')), false);
|
||||
assert.equal(existsSync(path.join(home, '.local/share/jarvis-qvac.staging.oldfail')), false);
|
||||
assert.equal(existsSync(path.join(extDir, 'old-extension.js')), false);
|
||||
assert.equal(existsSync(path.join(extDir, 'extension.js')), true);
|
||||
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/);
|
||||
assert.doesNotMatch(unit, /\/usr\/bin\/node/);
|
||||
const commands = await readFile(log, 'utf8');
|
||||
assert.match(commands, /systemctl --user stop jarvisd\.service/);
|
||||
assert.match(commands, /gnome-extensions uninstall jarvis@qvac\.local/);
|
||||
assert.match(commands, /systemctl --user restart jarvisd\.service/);
|
||||
} finally {
|
||||
await rm(work, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user