* test(search,daemon,storage): assert behaviour instead of frozen literals Several suites encoded exact internal values that legitimate tuning and ranking changes are allowed to move: SCIENTIFIC-era profile weights and rrfK values, corpus-statistic figures, and hardcoded timeout/cancellation numbers. They failed on every platform after those changes landed, so the real behaviour regressions they should catch were hidden behind noise. Replace the frozen literals with contract assertions: weights are finite, non-negative and normalize, state selection still holds, capability-gated sources stay zeroed, and hybrid results keep matching canonical hash/path sets. Deadlines are not widened and the daemon paths they cover are unchanged. Also add independent corpus and adapter oracles, deterministic collector cancellation and late-result gates, and synchronize ServiceManager shutdown observation, so the tests fail for the behaviour they name. The portability allowlist keeps its two reviewed raw test-env reads; only their line numbers moved. Signed-off-by: trvon <git@trevon.dev> * ci(tests): summarise Meson failures without flooding the log A failing test run printed the whole Meson suite output, so the actual failing test names and their assertion messages were buried and the uploaded logs were dominated by passing-test chatter. Add a bounded reporter that extracts the failing test names plus their surrounding diagnostics, keeps an overall cap on emitted lines, and is unit-tested against representative Meson output. Wire it into the Tests workflow so failures stay readable. Signed-off-by: trvon <git@trevon.dev> * build(deb): validate candidate identity, mapping and upgrade ordering The Debian packaging scripts took filenames and versions on trust, so a misnamed candidate or a wrong package ordering could pass validation while the real install path was never exercised. Fail closed on candidate identity, version and architecture, verify the package-to-source mapping, seed the legacy-only layout, wait on the actual services, and assert install/upgrade/remove ordering for both Debian and RPM in an offline container. Cases that cannot be proven on a host are reported as skipped rather than silently passing. Signed-off-by: trvon <git@trevon.dev> --------- Signed-off-by: trvon <git@trevon.dev>
837 lines
34 KiB
Bash
Executable file
837 lines
34 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# yams/scripts/local-ci/package-validate-checks.sh
|
|
#
|
|
# In-container half of scripts/local-ci/package-validate.sh (the entry point;
|
|
# run that, not this). It runs as root inside a distro container booted with
|
|
# systemd as PID 1, and prints one line per check:
|
|
#
|
|
# PASS <id> <detail> FAIL <id> <detail> INFO <id> <detail>
|
|
#
|
|
# The packages ship a systemd *user* unit; the checks drive it through a
|
|
# normal user with lingering enabled (so a user manager runs without a login).
|
|
#
|
|
# Usage: package-validate-checks.sh <phase> <pkg-manager> [args]
|
|
# install <pm> <pkg-file> install like a user would (no env vars)
|
|
# layout <pm> installed file list + ELF + permission audit
|
|
# service <pm> user unit + preset installed, globally enabled,
|
|
# no system unit
|
|
# user <pm> a user's unit is enabled and active, socket in
|
|
# $XDG_RUNTIME_DIR, CLI works with no env vars,
|
|
# yams daemon stop/start/restart go via systemd
|
|
# onnx <pm> after `yams init` the user daemon loads the
|
|
# onnx plugin with the bundled ONNX Runtime
|
|
# persist <pm> after a container reboot
|
|
# seed <pm> state of an older release (system service)
|
|
# upgraded <pm> after upgrading over `seed`
|
|
# remove <pm> uninstall (deb: remove + purge)
|
|
# minimal <pm> <pkg-file> raw base image, no systemd, nothing
|
|
# preinstalled: install pulls only declared deps
|
|
#
|
|
# <pm> is deb, rpm or arch. The exit status is the number of FAIL lines (0 = ok).
|
|
set -uo pipefail
|
|
|
|
PHASE="${1:?phase}"
|
|
PM="${2:?package manager}"
|
|
ARG="${3:-}"
|
|
|
|
UNIT=yams-daemon.service
|
|
USER_UNIT_DIR=/usr/lib/systemd/user
|
|
SYSTEM_UNIT_DIR=/usr/lib/systemd/system
|
|
LEGACY_DATA_DIR=/var/lib/yams
|
|
LEGACY_MANIFEST=/var/tmp/yams-validate-legacy.sha256
|
|
UPGRADE_NOTE=/var/tmp/yams-validate-upgrade-note.txt
|
|
TUSER=alice # the user whose daemon the checks drive (lingering)
|
|
OUSER=bob # a second user, for isolation
|
|
PUSER=carol # probes the bundled ONNX Runtime with its own config
|
|
TOKEN_FILE=/var/tmp/yams-validate-tokens
|
|
|
|
FAILS=0
|
|
pass() { printf 'PASS %s %s\n' "$1" "${2:-}"; }
|
|
fail() {
|
|
printf 'FAIL %s %s\n' "$1" "${2:-}"
|
|
FAILS=$((FAILS + 1))
|
|
}
|
|
info() { printf 'INFO %s %s\n' "$1" "${2:-}"; }
|
|
check() { # check <id> <detail> <command...>
|
|
local id="$1" detail="$2"
|
|
shift 2
|
|
if "$@" >/dev/null 2>&1; then pass "${id}" "${detail}"; else fail "${id}" "${detail}"; fi
|
|
}
|
|
|
|
token() { printf 'yamsval%s%s' "$1" "$(od -An -N6 -tx1 /dev/urandom | tr -d ' \n')"; }
|
|
remember() { printf '%s=%s\n' "$1" "$2" >>"${TOKEN_FILE}"; }
|
|
recall() { sed -n "s/^$1=//p" "${TOKEN_FILE}" 2>/dev/null | tail -n1; }
|
|
|
|
# Run a command as <user> through a login shell (pam_systemd sets
|
|
# XDG_RUNTIME_DIR like a real login); no YAMS_* variable leaks in.
|
|
as_user() {
|
|
local user="$1" rt
|
|
shift
|
|
rt="$(user_runtime "${user}")"
|
|
su - "${user}" -c "[ -n \"\${XDG_RUNTIME_DIR:-}\" ] || export XDG_RUNTIME_DIR=${rt}; $*"
|
|
}
|
|
|
|
ensure_user() {
|
|
id "$1" >/dev/null 2>&1 || useradd -m -s /bin/bash "$1"
|
|
loginctl enable-linger "$1" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
user_runtime() { printf '/run/user/%s' "$(id -u "$1")"; }
|
|
|
|
# systemctl --user for <user>, with the runtime dir and bus set explicitly.
|
|
uctl() {
|
|
local user="$1" rt
|
|
shift
|
|
rt="$(user_runtime "${user}")"
|
|
local -a busenv=()
|
|
if [ -S "${rt}/bus" ]; then busenv=("DBUS_SESSION_BUS_ADDRESS=unix:path=${rt}/bus"); fi
|
|
runuser -u "${user}" -- env XDG_RUNTIME_DIR="${rt}" ${busenv[@]+"${busenv[@]}"} \
|
|
systemctl --user "$@" 2>/dev/null || true
|
|
}
|
|
|
|
wait_user_manager() {
|
|
local uid
|
|
uid="$(id -u "$1")"
|
|
for _ in $(seq 1 120); do
|
|
[ "$(systemctl is-active "user@${uid}.service" 2>/dev/null)" = active ] &&
|
|
[ -S "/run/user/${uid}/systemd/private" ] && return 0
|
|
sleep 0.5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
wait_user_active() {
|
|
local sock
|
|
sock="$(user_runtime "$1")/yams-daemon.sock"
|
|
for _ in $(seq 1 120); do
|
|
[ "$(uctl "$1" is-active "${UNIT}")" = active ] && [ -S "${sock}" ] && return 0
|
|
sleep 0.5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
user_journal() {
|
|
uctl "$1" status "${UNIT}" --no-pager -n 15 | sed 's/^/INFO user-unit /'
|
|
}
|
|
|
|
# Exactly one daemon runs for TUSER, and it is the unit's MainPID (no CLI-spawned copy).
|
|
check_single_daemon() {
|
|
local main pids
|
|
main="$(uctl "${TUSER}" show -p MainPID --value "${UNIT}")"
|
|
pids="$(pgrep -u "${TUSER}" -f '^/usr/bin/yams-daemon' | paste -sd' ' - || true)"
|
|
if [ -n "${main}" ] && [ "${main}" != 0 ] && [ "${pids}" = "${main}" ]; then
|
|
pass "$1" "the only daemon for ${TUSER} is the unit's (pid ${main})"
|
|
else
|
|
fail "$1" "unit MainPID=${main:-?}, ${TUSER}'s daemons: ${pids:-none}"
|
|
fi
|
|
}
|
|
|
|
wait_plugins() { # wait_plugins <user> <name-substring>
|
|
for _ in $(seq 1 30); do
|
|
as_user "$1" 'timeout 30 yams plugin health' 2>/dev/null | grep -qi "$2" && return 0
|
|
sleep 2
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Poll a search until it reports <needle> (indexing is asynchronous).
|
|
search_finds() { # search_finds <user> <query> <needle>
|
|
local user="$1" query="$2" needle="$3" out=""
|
|
for _ in $(seq 1 60); do
|
|
out="$(as_user "${user}" "timeout 30 yams search '${query}'" 2>&1 || true)"
|
|
case "${out}" in *"${needle}"*) return 0 ;; esac
|
|
sleep 1
|
|
done
|
|
printf '%s\n' "${out}" | tail -n 5 | sed 's/^/INFO search-output /'
|
|
return 1
|
|
}
|
|
|
|
pkg_files() {
|
|
case "${PM}" in
|
|
deb) dpkg -L yams ;;
|
|
rpm) rpm -ql yams ;;
|
|
arch) pacman -Qlq yams ;;
|
|
esac 2>/dev/null | sed 's#/$##' | while IFS= read -r f; do
|
|
[ -n "${f}" ] || continue
|
|
# Files and symlinks only; directories are shared with other packages.
|
|
if [ -L "${f}" ] || [ ! -d "${f}" ]; then printf '%s\n' "${f}"; fi
|
|
done | sort -u
|
|
}
|
|
|
|
# Compare package-manager identity, not the filename or the CLI's base version.
|
|
package_identity_from_file() {
|
|
local pkg="$1" name version arch
|
|
case "${PM}" in
|
|
deb)
|
|
name="$(dpkg-deb -f "${pkg}" Package)" || return 1
|
|
version="$(dpkg-deb -f "${pkg}" Version)" || return 1
|
|
arch="$(dpkg-deb -f "${pkg}" Architecture)" || return 1
|
|
[ "${name}" = yams ] && [ -n "${version}" ] && [ -n "${arch}" ] || return 1
|
|
printf '%s %s %s\n' "${name}" "${version}" "${arch}"
|
|
;;
|
|
rpm) rpm -qp --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH}\n' "${pkg}" ;;
|
|
arch) pacman -Qp "${pkg}" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
installed_package_identity() {
|
|
case "${PM}" in
|
|
deb)
|
|
[ "$(dpkg-query -W -f='${Status}' yams 2>/dev/null)" = 'install ok installed' ] || return 1
|
|
dpkg-query -W -f='${Package} ${Version} ${Architecture}\n' yams
|
|
;;
|
|
rpm) rpm -q --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH}\n' yams ;;
|
|
arch) pacman -Q yams ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
phase_install() {
|
|
local pkg="${ARG:?package file}"
|
|
local log=/var/tmp/yams-validate-install.log
|
|
local rc=0 expected actual
|
|
# An earlier successful old-package install must not certify a failed candidate.
|
|
remember INSTALL_VERIFIED no
|
|
if ! expected="$(package_identity_from_file "${pkg}")" || [ -z "${expected}" ]; then
|
|
fail install-identity "cannot read candidate package metadata"
|
|
return
|
|
fi
|
|
case "${expected}" in yams\ *) ;; *) fail install-identity "candidate is not yams"; return ;; esac
|
|
case "${PM}" in
|
|
deb)
|
|
# The Docker base image's policy-rc.d blocks service starts; a real host has none.
|
|
rm -f /usr/sbin/policy-rc.d
|
|
apt-get update >/dev/null 2>&1 || true
|
|
apt-get install -y "${pkg}" >"${log}" 2>&1 || rc=$?
|
|
;;
|
|
rpm) dnf install -y "${pkg}" >"${log}" 2>&1 || rc=$? ;;
|
|
arch) pacman -U --noconfirm "${pkg}" >"${log}" 2>&1 || rc=$? ;;
|
|
esac
|
|
if [ "${rc}" -ne 0 ]; then
|
|
fail install "$(basename "${pkg}") (rc=${rc})"
|
|
tail -n 20 "${log}" | sed 's/^/INFO install-log /'
|
|
return
|
|
fi
|
|
if ! actual="$(installed_package_identity)" || [ "${actual}" != "${expected}" ]; then
|
|
fail install-identity "expected '${expected}', installed '${actual:-unknown}'"
|
|
return
|
|
fi
|
|
remember INSTALL_EXPECTED "${expected}"
|
|
remember INSTALL_VERIFIED yes
|
|
pass install "$(basename "${pkg}") (${actual})"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
phase_layout() {
|
|
local files
|
|
files="$(pkg_files)"
|
|
if [ -z "${files}" ]; then
|
|
fail layout-filelist "package manager lists no files for yams"
|
|
return
|
|
fi
|
|
info layout-filelist "$(printf '%s\n' "${files}" | wc -l) files"
|
|
|
|
# Runtime package allowlist. Anything else is either a leak (headers,
|
|
# static libs, pkg-config, build trees) or a new file someone must review.
|
|
local allow='^/usr/bin/(yams|yams-cli|yams-daemon|yams-mcp-server)$'
|
|
allow+='|^/usr/lib(64)?/(yams/)?libyams_[A-Za-z0-9_]+\.so(\.[0-9]+)*$'
|
|
allow+='|^/usr/lib(64)?/yams/plugins/[A-Za-z0-9_.-]+\.so$'
|
|
# Private fallback ONNX Runtime used by the ONNX/Glint plugins when no compatible
|
|
# system copy exists (core library, its soname symlinks, providers_shared).
|
|
allow+='|^/usr/lib(64)?/yams/onnxruntime/libonnxruntime(_providers_shared)?\.so(\.[0-9]+)*$'
|
|
allow+='|^/usr/lib/systemd/user/yams-daemon\.service$'
|
|
allow+='|^/usr/lib/systemd/user-preset/80-yams\.preset$'
|
|
allow+='|^/usr/share/doc/yams(/[A-Za-z0-9_./-]+)?$'
|
|
allow+='|^/usr/share/licenses/yams(/[A-Za-z0-9_.-]+)?$'
|
|
# rpm's debuginfo build-id links (symlinks to the shipped ELF files).
|
|
allow+='|^/usr/lib/\.build-id(/[0-9a-f]{2}(/[0-9a-f]+)?)?$'
|
|
allow+='|^/usr/share/yams/[A-Za-z0-9_./-]+$'
|
|
local unexpected
|
|
unexpected="$(printf '%s\n' "${files}" | grep -Ev "${allow}" || true)"
|
|
if [ -z "${unexpected}" ]; then
|
|
pass layout-allowlist "every installed path is on the runtime allowlist"
|
|
else
|
|
fail layout-allowlist "unexpected installed paths: $(printf '%s ' ${unexpected})"
|
|
fi
|
|
|
|
local devel
|
|
devel="$(printf '%s\n' "${files}" | grep -E '^/usr/include/|\.a$|\.pc$|\.(h|hpp|hh)$|/cmake/' || true)"
|
|
if [ -z "${devel}" ]; then
|
|
pass layout-no-devel "no headers, static libraries, pkg-config or CMake files"
|
|
else
|
|
fail layout-no-devel "development files in runtime package: $(printf '%s ' ${devel})"
|
|
fi
|
|
|
|
for required in /usr/bin/yams /usr/bin/yams-daemon /usr/lib/systemd/user/yams-daemon.service \
|
|
/usr/lib/systemd/user-preset/80-yams.preset; do
|
|
check "layout-has:${required}" "installed" test -e "${required}"
|
|
done
|
|
|
|
local bad_perm=""
|
|
local f
|
|
while IFS= read -r f; do
|
|
# Skip symlinks and paths the image's dpkg path-exclude dropped (docs).
|
|
if [ -L "${f}" ] || [ ! -e "${f}" ]; then continue; fi
|
|
if [ -n "$(find "${f}" -maxdepth 0 \( -perm -0002 -o -perm -4000 -o -perm -2000 \) 2>/dev/null)" ]; then
|
|
bad_perm+="${f} "
|
|
fi
|
|
if [ "$(stat -c %U "${f}")" != root ]; then
|
|
bad_perm+="${f}(owner=$(stat -c %U "${f}")) "
|
|
fi
|
|
done <<<"${files}"
|
|
if [ -z "${bad_perm}" ]; then
|
|
pass layout-perms "no world-writable, setuid/setgid or non-root-owned files"
|
|
else
|
|
fail layout-perms "${bad_perm}"
|
|
fi
|
|
|
|
# ELF: dependencies resolve on a clean system and no build-tree rpath leaks.
|
|
local elf_bad="" rpath_bad="" elf_count=0
|
|
while IFS= read -r f; do
|
|
if [ -L "${f}" ] || [ ! -e "${f}" ]; then continue; fi
|
|
[ "$(head -c4 "${f}" 2>/dev/null | od -An -c | tr -d ' ')" = "177ELF" ] || continue
|
|
elf_count=$((elf_count + 1))
|
|
local missing
|
|
missing="$(ldd "${f}" 2>&1 | grep -E 'not found' || true)"
|
|
[ -n "${missing}" ] && elf_bad+="${f}: $(printf '%s' "${missing}" | tr -s ' \n' ' ') "
|
|
local rp
|
|
rp="$(readelf -d "${f}" 2>/dev/null | sed -n 's/.*(R\(UN\)\{0,1\}PATH).*\[\(.*\)\]/\2/p' | tr ':' '\n')"
|
|
local entry
|
|
while IFS= read -r entry; do
|
|
[ -z "${entry}" ] && continue
|
|
case "${entry}" in
|
|
'$ORIGIN' | '$ORIGIN/'* | /usr/lib | /usr/lib64 | /usr/lib/yams | /usr/lib/yams/*) ;;
|
|
*) rpath_bad+="${f}:${entry} " ;;
|
|
esac
|
|
done <<<"${rp}"
|
|
done <<<"${files}"
|
|
info layout-elf "${elf_count} ELF objects"
|
|
if [ -z "${elf_bad}" ]; then pass layout-elf-needed "all DT_NEEDED resolve (ldd)"; else fail layout-elf-needed "${elf_bad}"; fi
|
|
if [ -z "${rpath_bad}" ]; then pass layout-elf-runpath "RPATH/RUNPATH only \$ORIGIN or /usr/lib[/yams]"; else fail layout-elf-runpath "${rpath_bad}"; fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Packaging-level state of the user unit (as root, no user session needed).
|
|
phase_service() {
|
|
check service-user-unit "user unit installed in ${USER_UNIT_DIR}" test -f "${USER_UNIT_DIR}/${UNIT}"
|
|
check service-user-preset "user preset installed" test -f /usr/lib/systemd/user-preset/80-yams.preset
|
|
if [ -e "/usr/lib/systemd/system/${UNIT}" ] || [ -e "/lib/systemd/system/${UNIT}" ]; then
|
|
fail service-no-system-unit "a system unit is still installed"
|
|
else
|
|
pass service-no-system-unit "no system unit (the daemon is per-user)"
|
|
fi
|
|
local g
|
|
g="$(systemctl --global is-enabled "${UNIT}" 2>/dev/null || true)"
|
|
if [ "${g}" = enabled ]; then
|
|
pass service-global-enabled "enabled for all users (user preset applied)"
|
|
else
|
|
fail service-global-enabled "systemctl --global is-enabled says '${g:-?}'"
|
|
fi
|
|
if grep -Eq '^(ProtectHome|PrivateTmp)=' "${USER_UNIT_DIR}/${UNIT}"; then
|
|
fail unit-reads-home "unit hides the user's files (ProtectHome/PrivateTmp)"
|
|
else
|
|
pass unit-reads-home "unit can read the user's home and /tmp"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# A normal user with lingering: the user manager starts the unit (globally
|
|
# enabled), and the CLI uses it with no environment set up by hand.
|
|
phase_user() {
|
|
ensure_user "${TUSER}"
|
|
if ! wait_user_manager "${TUSER}"; then
|
|
fail user-manager "user@$(id -u "${TUSER}").service did not start"
|
|
journalctl -u "user@$(id -u "${TUSER}").service" --no-pager -n 8 2>/dev/null | sed 's/^/INFO user-manager /'
|
|
return
|
|
fi
|
|
pass user-manager "user manager running for ${TUSER} (linger)"
|
|
local verify
|
|
verify="$(runuser -u "${TUSER}" -- env XDG_RUNTIME_DIR="$(user_runtime "${TUSER}")" \
|
|
systemd-analyze --user verify "${USER_UNIT_DIR}/${UNIT}" 2>&1 | grep -viE 'cgroup' || true)"
|
|
if [ -z "${verify}" ]; then
|
|
pass unit-verify "systemd-analyze --user verify is clean"
|
|
else
|
|
fail unit-verify "$(printf '%s' "${verify}" | tr '\n' ' ')"
|
|
fi
|
|
local xdg
|
|
xdg="$(su - "${TUSER}" -c 'printf %s "${XDG_RUNTIME_DIR:-}"')"
|
|
if [ "${xdg}" = "$(user_runtime "${TUSER}")" ]; then
|
|
pass user-login-env "su login has XDG_RUNTIME_DIR=${xdg} (pam_systemd)"
|
|
else
|
|
info user-login-env "su login lacks XDG_RUNTIME_DIR (no pam_systemd in su); the checks set it like a real login"
|
|
fi
|
|
local en
|
|
en="$(uctl "${TUSER}" is-enabled "${UNIT}")"
|
|
if [ "${en}" = enabled ]; then pass user-enabled "${UNIT} enabled for ${TUSER}"; else fail user-enabled "is-enabled: '${en}'"; fi
|
|
if wait_user_active "${TUSER}"; then
|
|
pass user-active "${UNIT} active for ${TUSER} without a manual start"
|
|
else
|
|
fail user-active "${UNIT} is '$(uctl "${TUSER}" is-active "${UNIT}")'"
|
|
user_journal "${TUSER}"
|
|
return
|
|
fi
|
|
local sock perms
|
|
sock="$(user_runtime "${TUSER}")/yams-daemon.sock"
|
|
perms="$(stat -c '%a %U' "${sock}" 2>/dev/null || true)"
|
|
case "${perms}" in
|
|
*" ${TUSER}") pass user-socket "socket ${sock} (${perms})" ;;
|
|
*) fail user-socket "no socket owned by ${TUSER} at ${sock} (${perms:-missing})" ;;
|
|
esac
|
|
|
|
local t rc=0 out
|
|
t="$(token user)"
|
|
remember USER "${t}"
|
|
as_user "${TUSER}" "printf 'user note %s\n' '${t}' > ~/user-note.txt; printf 'tmp note %s\n' '${t}x' > /tmp/user-tmp-note.txt"
|
|
out="$(as_user "${TUSER}" 'timeout 60 yams daemon status' 2>&1)" || rc=$?
|
|
if [ "${rc}" -eq 0 ] && ! printf '%s' "${out}" | grep -qi 'not running'; then
|
|
pass cli-status "yams daemon status reaches the unit's daemon"
|
|
else
|
|
fail cli-status "rc=${rc}: $(printf '%s' "${out}" | tail -n 2 | tr '\n' ' ')"
|
|
fi
|
|
rc=0
|
|
out="$(as_user "${TUSER}" 'timeout 120 yams add ~/user-note.txt && timeout 120 yams add /tmp/user-tmp-note.txt' 2>&1)" || rc=$?
|
|
if [ "${rc}" -eq 0 ]; then pass cli-add "yams add from \$HOME and /tmp"; else fail cli-add "rc=${rc}: $(printf '%s' "${out}" | tail -n 2 | tr '\n' ' ')"; fi
|
|
if search_finds "${TUSER}" "${t}" user-note; then pass cli-search "home file searchable"; else fail cli-search "home note not found by yams search"; fi
|
|
if as_user "${TUSER}" 'test -e ~/.local/share/yams/yams.db'; then
|
|
pass cli-data-dir "corpus in ~${TUSER}/.local/share/yams"
|
|
else
|
|
fail cli-data-dir "no ~/.local/share/yams/yams.db for ${TUSER}"
|
|
fi
|
|
check_single_daemon cli-single-daemon
|
|
check user-log "daemon log in ~/.local/state/yams" as_user "${TUSER}" 'test -s ~/.local/state/yams/daemon.log'
|
|
|
|
# Lifecycle commands go through systemd.
|
|
as_user "${TUSER}" 'timeout 60 yams daemon stop' >/dev/null 2>&1 || true
|
|
local st
|
|
st="$(uctl "${TUSER}" is-active "${UNIT}")"
|
|
if [ "${st}" != active ]; then pass cli-stop "yams daemon stop stopped the unit (${st})"; else fail cli-stop "unit still active after yams daemon stop"; fi
|
|
as_user "${TUSER}" 'timeout 60 yams daemon start' >/dev/null 2>&1 || true
|
|
if wait_user_active "${TUSER}"; then
|
|
pass cli-start-unit "yams daemon start started the unit"
|
|
else
|
|
fail cli-start-unit "unit is '$(uctl "${TUSER}" is-active "${UNIT}")' after yams daemon start"
|
|
fi
|
|
check_single_daemon cli-start-single
|
|
local before after
|
|
before="$(uctl "${TUSER}" show -p MainPID --value "${UNIT}")"
|
|
as_user "${TUSER}" 'timeout 60 yams daemon restart' >/dev/null 2>&1 || true
|
|
wait_user_active "${TUSER}" || true
|
|
after="$(uctl "${TUSER}" show -p MainPID --value "${UNIT}")"
|
|
if [ -n "${after}" ] && [ "${after}" != 0 ] && [ "${after}" != "${before}" ]; then
|
|
pass cli-restart "yams daemon restart restarted the unit (pid ${before} -> ${after})"
|
|
else
|
|
fail cli-restart "MainPID ${before} -> ${after}"
|
|
fi
|
|
check cli-install-user "yams daemon install --user enables the packaged unit" \
|
|
as_user "${TUSER}" 'yams daemon install --user | grep -q "packaged" && test ! -e ~/.config/systemd/user/yams-daemon.service'
|
|
|
|
# Users are isolated: another user's runtime dir and socket are private.
|
|
ensure_user "${OUSER}"
|
|
if as_user "${OUSER}" "test -r $(user_runtime "${TUSER}")/yams-daemon.sock || test -x $(user_runtime "${TUSER}")"; then
|
|
fail user-isolation "${OUSER} can reach ${TUSER}'s runtime dir"
|
|
else
|
|
pass user-isolation "${OUSER} cannot reach ${TUSER}'s daemon"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# `yams init` writes the user's config; with it the unit loads the plugins and
|
|
# the onnx plugin uses the bundled ONNX Runtime (lane images have no system copy).
|
|
phase_onnx() {
|
|
local bundled
|
|
bundled="$(ls -d /usr/lib/yams/onnxruntime /usr/lib64/yams/onnxruntime 2>/dev/null | head -n 1)"
|
|
if [ -n "${bundled}" ] && ls "${bundled}"/libonnxruntime.so* >/dev/null 2>&1; then
|
|
pass onnx-runtime-shipped "bundled runtime present in ${bundled}"
|
|
else
|
|
fail onnx-runtime-shipped "no libonnxruntime.so under /usr/lib/yams/onnxruntime"
|
|
fi
|
|
check onnx-plugin-shipped "onnx plugin installed" test -e /usr/lib/yams/plugins/libyams_onnx_plugin.so
|
|
local rc=0 out
|
|
out="$(as_user "${TUSER}" 'timeout 300 yams init --non-interactive --no-keygen' 2>&1)" || rc=$?
|
|
if as_user "${TUSER}" "grep -Eq '^[[:space:]]*auto_load_plugins[[:space:]]*=[[:space:]]*true' ~/.config/yams/config.toml"; then
|
|
pass onnx-init-config "yams init wrote auto_load_plugins = true (rc=${rc})"
|
|
else
|
|
fail onnx-init-config "rc=${rc}; config lacks auto_load_plugins = true: $(printf '%s' "${out}" | tail -n 2 | tr '\n' ' ')"
|
|
as_user "${TUSER}" 'mkdir -p ~/.config/yams && printf "[daemon]\nauto_load_plugins = true\n" >> ~/.config/yams/config.toml'
|
|
fi
|
|
uctl "${TUSER}" restart "${UNIT}" >/dev/null
|
|
if ! wait_user_active "${TUSER}"; then
|
|
fail onnx-restart "unit did not come back after yams init"
|
|
user_journal "${TUSER}"
|
|
return
|
|
fi
|
|
if wait_plugins "${TUSER}" glint; then
|
|
pass onnx-autoload "init's config autoloads plugins (glint loaded)"
|
|
else
|
|
fail onnx-autoload "no plugins loaded with init's config"
|
|
as_user "${TUSER}" 'timeout 60 yams plugin health' 2>&1 | tail -n 4 | sed 's/^/INFO plugin-health /'
|
|
fi
|
|
check_single_daemon onnx-single-daemon
|
|
|
|
# The bundled ONNX Runtime, probed with a separate user so TUSER keeps
|
|
# init's configuration for the later phases: plugin autoload with the
|
|
# default ("auto") embedding backend adopts the onnx model provider, which
|
|
# resolves the runtime at load (no model is installed, so nothing embeds).
|
|
ensure_user "${PUSER}"
|
|
if ! wait_user_manager "${PUSER}"; then
|
|
fail onnx-probe-user "no user manager for ${PUSER}"
|
|
return
|
|
fi
|
|
# config_version = 3 keeps the daemon from migrating this file to the full
|
|
# template (whose default embedding backend is the built-in simeon).
|
|
as_user "${PUSER}" 'mkdir -p ~/.config/yams && printf "[version]\nconfig_version = 3\n\n[daemon]\nauto_load_plugins = true\n\n[embeddings]\nenable = true\nbackend = \"auto\"\n" > ~/.config/yams/config.toml'
|
|
local since
|
|
since="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
uctl "${PUSER}" restart "${UNIT}" >/dev/null
|
|
wait_user_active "${PUSER}" || true
|
|
local line=""
|
|
for _ in $(seq 1 60); do
|
|
line="$(journalctl -q --no-pager _UID="$(id -u "${PUSER}")" --since "${since}" 2>/dev/null |
|
|
grep -E 'Using (bundled|system) ONNX Runtime' | tail -n 1)"
|
|
[ -n "${line}" ] && break
|
|
sleep 1
|
|
done
|
|
case "${line}" in
|
|
*"Using bundled ONNX Runtime"*) pass onnx-runtime-bundled "user daemon: ${line##*] }" ;;
|
|
*)
|
|
fail onnx-runtime-bundled "no 'Using bundled ONNX Runtime' from the user daemon (${line:-none})"
|
|
journalctl -q --no-pager _UID="$(id -u "${PUSER}")" --since "${since}" 2>/dev/null |
|
|
grep -iE 'onnx|plugin' | tail -n 8 | sed 's/^/INFO onnx-log /'
|
|
;;
|
|
esac
|
|
if wait_plugins "${PUSER}" onnx; then
|
|
pass onnx-plugin-loaded "yams plugin health lists the onnx plugin"
|
|
else
|
|
fail onnx-plugin-loaded "yams plugin health does not list onnx"
|
|
fi
|
|
uctl "${PUSER}" stop "${UNIT}" >/dev/null
|
|
loginctl disable-linger "${PUSER}" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
phase_persist() {
|
|
if ! wait_user_manager "${TUSER}"; then
|
|
fail reboot-user-manager "no user manager for ${TUSER} after reboot (linger)"
|
|
return
|
|
fi
|
|
if wait_user_active "${TUSER}"; then
|
|
pass reboot-active "${UNIT} active for ${TUSER} after reboot"
|
|
else
|
|
fail reboot-active "${UNIT} is '$(uctl "${TUSER}" is-active "${UNIT}")' after reboot"
|
|
user_journal "${TUSER}"
|
|
return
|
|
fi
|
|
local t
|
|
t="$(recall USER)"
|
|
if [ -n "${t}" ] && search_finds "${TUSER}" "${t}" user-note; then
|
|
pass reboot-data-persists "${TUSER}'s document survives a reboot"
|
|
else
|
|
fail reboot-data-persists "${TUSER}'s document missing after reboot"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Record existing legacy bytes only after the old system daemon has stopped.
|
|
# Do not manufacture an absent /var/lib/yams to satisfy an upgrade postcondition.
|
|
snapshot_legacy_corpus() {
|
|
[ -d "${LEGACY_DATA_DIR}" ] && [ ! -L "${LEGACY_DATA_DIR}" ] || return 1
|
|
[ -n "$(find "${LEGACY_DATA_DIR}" -type f -print -quit)" ] || return 1
|
|
find "${LEGACY_DATA_DIR}" -type f -print0 | LC_ALL=C sort -z |
|
|
xargs -0 sha256sum >"${LEGACY_MANIFEST}" || return 1
|
|
[ -s "${LEGACY_MANIFEST}" ]
|
|
}
|
|
|
|
phase_seed() {
|
|
: >"${TOKEN_FILE}"
|
|
local mode t hash out state="" rc=0
|
|
if [ -f "${SYSTEM_UNIT_DIR}/${UNIT}" ] || [ -f "/lib/systemd/system/${UNIT}" ]; then
|
|
mode=legacy
|
|
# Keep the original startup budget, but only for a real legacy system unit.
|
|
for _ in $(seq 1 120); do
|
|
state="$(systemctl is-active "${UNIT}" 2>/dev/null || true)"
|
|
[ "${state}" = active ] && break
|
|
sleep 0.5
|
|
done
|
|
if [ "${state}" != active ]; then
|
|
fail upgrade-seed-service "legacy system daemon is not active"
|
|
return
|
|
fi
|
|
pass upgrade-old-system-unit "old release runs the system ${UNIT}"
|
|
if ! systemctl stop "${UNIT}" || [ "$(systemctl is-active "${UNIT}" 2>/dev/null)" = active ]; then
|
|
fail upgrade-seed-legacy "cannot quiesce legacy corpus"
|
|
return
|
|
fi
|
|
elif [ -f "${USER_UNIT_DIR}/${UNIT}" ]; then
|
|
mode=per-user
|
|
info upgrade-old-system-unit "per-user release; no legacy system service expected"
|
|
else
|
|
fail upgrade-seed-mode "neither a legacy system unit nor a packaged user unit exists"
|
|
return
|
|
fi
|
|
if [ "${mode}" = legacy ] || [ -e "${LEGACY_DATA_DIR}" ]; then
|
|
if ! snapshot_legacy_corpus; then
|
|
fail upgrade-seed-legacy "no verifiable existing legacy corpus"
|
|
return
|
|
fi
|
|
remember LEGACY_PRESENT yes
|
|
pass upgrade-seed-legacy "existing legacy files recorded by SHA256"
|
|
else
|
|
remember LEGACY_PRESENT no
|
|
fi
|
|
ensure_user "${TUSER}"
|
|
if ! wait_user_manager "${TUSER}"; then
|
|
fail upgrade-seed-service "no user manager for ${TUSER}"
|
|
return
|
|
fi
|
|
if [ "${mode}" = per-user ]; then
|
|
uctl "${TUSER}" start "${UNIT}" >/dev/null
|
|
if ! wait_user_active "${TUSER}"; then
|
|
fail upgrade-seed-service "old per-user daemon is not ready"
|
|
return
|
|
fi
|
|
fi
|
|
# The legacy lane also preserves a user's locally ingested document while
|
|
# migrating the package from system-service to per-user-service layout.
|
|
t="$(token seed)"
|
|
printf 'package upgrade note %s\n' "${t}" >"${UPGRADE_NOTE}"
|
|
chmod 644 "${UPGRADE_NOTE}"
|
|
hash="$(sha256sum "${UPGRADE_NOTE}" | awk '{print $1}')"
|
|
if ! as_user "${TUSER}" "cat '${UPGRADE_NOTE}' > ~/pre-upgrade-note.txt"; then
|
|
fail upgrade-seed-add "cannot prepare user source document"
|
|
return
|
|
fi
|
|
out="$(as_user "${TUSER}" 'timeout 120 yams add ~/pre-upgrade-note.txt' 2>&1)" || rc=$?
|
|
if [ "${rc}" -ne 0 ]; then
|
|
fail upgrade-seed-add "rc=${rc}: $(printf '%s' "${out}" | tail -n 2)"
|
|
return
|
|
fi
|
|
if ! as_user "${TUSER}" "timeout 120 yams cat --hash '${hash}'" >"${UPGRADE_NOTE}.before" ||
|
|
! cmp -s "${UPGRADE_NOTE}" "${UPGRADE_NOTE}.before"; then
|
|
fail upgrade-seed-body "pre-transition body does not match original bytes"
|
|
return
|
|
fi
|
|
if ! search_finds "${TUSER}" "${t}" pre-upgrade-note; then
|
|
fail upgrade-seed-search "pre-transition document is not searchable"
|
|
return
|
|
fi
|
|
# Do not let an old CLI-spawned daemon answer the post-install probes.
|
|
if ! as_user "${TUSER}" 'timeout 60 yams daemon stop'; then
|
|
fail upgrade-seed-service "cannot stop old user daemon before package transition"
|
|
return
|
|
fi
|
|
remember UPGRADE_TOKEN "${t}"
|
|
remember UPGRADE_HASH "${hash}"
|
|
remember UPGRADE_MODE "${mode}"
|
|
pass upgrade-seed-body "pre-transition document searchable and SHA256 body hydrated"
|
|
# The candidate package must be installed next; an old install cannot qualify it.
|
|
remember INSTALL_VERIFIED no
|
|
}
|
|
|
|
phase_upgraded() {
|
|
local mode hash t expected actual legacy_present
|
|
mode="$(recall UPGRADE_MODE)"
|
|
legacy_present="$(recall LEGACY_PRESENT)"
|
|
hash="$(recall UPGRADE_HASH)"
|
|
t="$(recall UPGRADE_TOKEN)"
|
|
case "${mode}" in
|
|
legacy | per-user) ;;
|
|
*) fail upgrade-prerequisites "missing verified pre-transition corpus"; return ;;
|
|
esac
|
|
case "${legacy_present}" in
|
|
yes | no) ;;
|
|
*) fail upgrade-prerequisites "legacy corpus precondition not recorded"; return ;;
|
|
esac
|
|
if [ "${mode}" = legacy ] && [ "${legacy_present}" != yes ]; then
|
|
fail upgrade-prerequisites "legacy fixture did not record existing corpus"
|
|
return
|
|
fi
|
|
if [ -z "${t}" ] || ! printf '%s\n' "${hash}" | grep -Eq '^[0-9a-f]{64}$' ||
|
|
[ ! -s "${UPGRADE_NOTE}" ]; then
|
|
fail upgrade-prerequisites "missing verified pre-transition body/hash"
|
|
return
|
|
fi
|
|
expected="$(recall INSTALL_EXPECTED)"
|
|
if [ "$(recall INSTALL_VERIFIED)" != yes ] || [ -z "${expected}" ] ||
|
|
! actual="$(installed_package_identity)" || [ "${actual}" != "${expected}" ]; then
|
|
fail upgrade-package-identity "candidate install not verified or installed metadata differs"
|
|
return
|
|
fi
|
|
pass upgrade-package-identity "candidate '${actual}' installed and configured"
|
|
if [ "$(systemctl is-active "${UNIT}" 2>/dev/null)" = active ]; then
|
|
fail upgrade-system-unit-stopped "old system unit still running"
|
|
else
|
|
pass upgrade-system-unit-stopped "old system unit stopped"
|
|
fi
|
|
if [ -e "${SYSTEM_UNIT_DIR}/${UNIT}" ] || [ -e "/lib/systemd/system/${UNIT}" ]; then
|
|
fail upgrade-system-unit-removed "system unit file still installed"
|
|
else
|
|
pass upgrade-system-unit-removed "system unit file removed"
|
|
fi
|
|
check_no_dangling_links upgrade-no-dangling-links
|
|
if [ "${legacy_present}" = yes ]; then
|
|
if [ -s "${LEGACY_MANIFEST}" ] && sha256sum --quiet -c "${LEGACY_MANIFEST}"; then
|
|
pass upgrade-old-corpus-kept "every recorded legacy file retains its SHA256"
|
|
else
|
|
fail upgrade-old-corpus-kept "legacy manifest missing, files removed or bytes changed"
|
|
fi
|
|
else
|
|
info upgrade-old-corpus-kept "no pre-existing legacy corpus; checking seeded user data"
|
|
fi
|
|
local g
|
|
g="$(systemctl --global is-enabled "${UNIT}" 2>/dev/null || true)"
|
|
if [ "${g}" = enabled ]; then pass upgrade-global-enabled "user unit enabled for all users"; else fail upgrade-global-enabled "'${g:-?}'"; fi
|
|
# The already-running user manager learns about the unit; it starts at the
|
|
# next login, or now on request.
|
|
uctl "${TUSER}" daemon-reload >/dev/null
|
|
local en
|
|
en="$(uctl "${TUSER}" is-enabled "${UNIT}")"
|
|
if [ "${en}" = enabled ]; then pass upgrade-user-enabled "${UNIT} enabled for ${TUSER}"; else fail upgrade-user-enabled "'${en}'"; fi
|
|
uctl "${TUSER}" start "${UNIT}" >/dev/null
|
|
if wait_user_active "${TUSER}"; then
|
|
pass upgrade-user-active "${TUSER}'s user daemon runs after the upgrade"
|
|
else
|
|
fail upgrade-user-active "'$(uctl "${TUSER}" is-active "${UNIT}")'"
|
|
user_journal "${TUSER}"
|
|
return
|
|
fi
|
|
if as_user "${TUSER}" "timeout 120 yams cat --hash '${hash}'" >"${UPGRADE_NOTE}.after" &&
|
|
cmp -s "${UPGRADE_NOTE}" "${UPGRADE_NOTE}.after"; then
|
|
pass upgrade-seeded-body "pre-transition body retains its SHA256 and exact bytes"
|
|
else
|
|
fail upgrade-seeded-body "pre-transition document missing or changed"
|
|
fi
|
|
if search_finds "${TUSER}" "${t}" pre-upgrade-note; then
|
|
pass upgrade-seeded-search "pre-transition document remains searchable"
|
|
else
|
|
fail upgrade-seeded-search "pre-transition document missing from search"
|
|
fi
|
|
local rc=0
|
|
t="$(token upgrade)"
|
|
as_user "${TUSER}" "printf 'upgrade note %s\n' '${t}' > ~/upgrade-note.txt"
|
|
as_user "${TUSER}" 'timeout 120 yams add ~/upgrade-note.txt' >/dev/null 2>&1 || rc=$?
|
|
if [ "${rc}" -eq 0 ] && search_finds "${TUSER}" "${t}" upgrade-note; then
|
|
pass upgrade-cli "${TUSER}'s CLI works against the user daemon"
|
|
else
|
|
fail upgrade-cli "add rc=${rc} or document not found"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
phase_minimal() {
|
|
local pkg="${ARG:?package file}"
|
|
local log=/var/tmp/yams-validate-minimal.log rc=0
|
|
case "${PM}" in
|
|
deb)
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update >/dev/null 2>&1 || true
|
|
apt-get install -y "${pkg}" >"${log}" 2>&1 || rc=$?
|
|
;;
|
|
rpm) dnf install -y "${pkg}" >"${log}" 2>&1 || rc=$? ;;
|
|
arch)
|
|
pacman -Sy --noconfirm >/dev/null 2>&1 || true
|
|
pacman -U --noconfirm "${pkg}" >"${log}" 2>&1 || rc=$?
|
|
;;
|
|
esac
|
|
if [ "${rc}" -eq 0 ]; then
|
|
pass minimal-install "$(basename "${pkg}") installs on the bare base image"
|
|
else
|
|
fail minimal-install "rc=${rc}"
|
|
tail -n 15 "${log}" | sed 's/^/INFO minimal-install-log /'
|
|
return
|
|
fi
|
|
if [ "${PM}" = deb ]; then
|
|
local status
|
|
status="$(dpkg-query -W -f='${Status}' yams 2>/dev/null)"
|
|
if [ "${status}" = "install ok installed" ]; then
|
|
pass minimal-configured "dpkg status '${status}'"
|
|
else
|
|
fail minimal-configured "dpkg status '${status}' (half-configured?)"
|
|
fi
|
|
fi
|
|
check minimal-user-unit "user unit installed" test -f "${USER_UNIT_DIR}/${UNIT}"
|
|
check minimal-version "yams --version runs" yams --version
|
|
local t out
|
|
t="$(token minimal)"
|
|
mkdir -p /var/tmp/yams-min-home
|
|
printf 'minimal note %s\n' "${t}" >/var/tmp/yams-min-home/note.txt
|
|
out="$(env -i HOME=/var/tmp/yams-min-home PATH=/usr/bin:/bin timeout 180 yams add /var/tmp/yams-min-home/note.txt 2>&1)" || true
|
|
local found=0
|
|
for _ in $(seq 1 30); do
|
|
if env -i HOME=/var/tmp/yams-min-home PATH=/usr/bin:/bin timeout 60 yams search "${t}" 2>/dev/null | grep -q note; then
|
|
found=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [ "${found}" -eq 1 ]; then
|
|
pass minimal-cli "yams add + search work without systemd"
|
|
else
|
|
fail minimal-cli "$(printf '%s' "${out}" | tail -n 2 | tr '\n' ' ')"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
check_no_dangling_links() { # check_no_dangling_links <id>
|
|
local dangling
|
|
dangling="$(find /etc/systemd/system /etc/systemd/user -name "${UNIT}" -xtype l 2>/dev/null || true)"
|
|
if [ -z "${dangling}" ]; then
|
|
pass "$1" "no dangling enablement symlinks"
|
|
else
|
|
fail "$1" "$(printf '%s ' ${dangling})"
|
|
fi
|
|
}
|
|
|
|
phase_remove() {
|
|
local rc=0 log=/var/tmp/yams-validate-remove.log
|
|
case "${PM}" in
|
|
deb) apt-get remove -y yams >"${log}" 2>&1 || rc=$? ;;
|
|
rpm) dnf remove -y yams >"${log}" 2>&1 || rc=$? ;;
|
|
arch) pacman -R --noconfirm yams >"${log}" 2>&1 || rc=$? ;;
|
|
esac
|
|
if [ "${rc}" -eq 0 ]; then pass remove "package removed"; else
|
|
fail remove "rc=${rc}"
|
|
tail -n 20 "${log}" | sed 's/^/INFO remove-log /'
|
|
fi
|
|
local st
|
|
st="$(uctl "${TUSER}" is-active "${UNIT}")"
|
|
if [ "${st}" = active ]; then
|
|
fail remove-user-stopped "${TUSER}'s user daemon still active after removal"
|
|
else
|
|
pass remove-user-stopped "${TUSER}'s user daemon stopped (${st:-gone})"
|
|
fi
|
|
if pgrep -f '^/usr/bin/yams-daemon' >/dev/null 2>&1; then
|
|
fail remove-no-daemons "yams-daemon processes left: $(pgrep -fa '^/usr/bin/yams-daemon' | tr '\n' ' ')"
|
|
else
|
|
pass remove-no-daemons "no yams-daemon processes left"
|
|
fi
|
|
check remove-binaries "binaries removed" test ! -e /usr/bin/yams-daemon
|
|
check remove-global-disabled "global enablement removed" test ! -e "/etc/systemd/user/default.target.wants/${UNIT}"
|
|
check_no_dangling_links remove-no-dangling-links
|
|
check remove-user-data-kept "${TUSER}'s corpus kept" as_user "${TUSER}" 'test -e ~/.local/share/yams/yams.db'
|
|
if [ "${PM}" = deb ]; then
|
|
rc=0
|
|
apt-get purge -y yams >>"${log}" 2>&1 || rc=$?
|
|
if [ "${rc}" -eq 0 ]; then pass purge "package purged"; else fail purge "rc=${rc}"; fi
|
|
check purge-user-data-kept "${TUSER}'s corpus kept after purge" as_user "${TUSER}" 'test -e ~/.local/share/yams/yams.db'
|
|
fi
|
|
}
|
|
|
|
case "${PHASE}" in
|
|
install) phase_install ;;
|
|
layout) phase_layout ;;
|
|
service) phase_service ;;
|
|
user) phase_user ;;
|
|
onnx) phase_onnx ;;
|
|
persist) phase_persist ;;
|
|
seed) phase_seed ;;
|
|
upgraded) phase_upgraded ;;
|
|
remove) phase_remove ;;
|
|
minimal) phase_minimal ;;
|
|
*)
|
|
echo "unknown phase: ${PHASE}" >&2
|
|
exit 64
|
|
;;
|
|
esac
|
|
exit "${FAILS}"
|