Files
Hermit/scripts/bootstrap_linux.sh
T
WHCandCursor ea38247161
ci / tcc-check (push) Failing after 42s
ci / linux (aarch64, 0) (push) Skipped
ci / linux (x86, 1) (push) Skipped
ci / linux (x86_64, 1) (push) Skipped
ci / windows (push) Skipped
feat: Hermit 1.1.0 — HERMIT_* env, Tauri desktop shell
Wire hermit_getenv across the engine, rebrand WebUI/systemd/docs, and
add apps/desktop (Tauri 2 + React) with a DeepSeek-Harness-like UI.
Qt app/ is frozen; full libcstcore FFI is scaffolded behind --features engine.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 13:15:47 +08:00

285 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install or upgrade Hermit on Linux, WSL or macOS. Safe to rerun: an
# existing checkout is fast-forwarded, rebuilt and reinstalled.
#
# bootstrap_linux.sh [DIR] [--no-pull] [--release] [--source]
# hermit-update (installed by this script; same as rerunning it)
# cst-agent-update (compat symlink → hermit-update)
#
# --release downloads the static musl tarball for this host (fast; no compile).
# --source (default) builds from the checkout. CST_INSTALL_MODE=release|source
# overrides the flag.
#
# Env: HERMIT_PREFIX / CST_AGENT_PREFIX (install prefix, default ~/.local),
# HERMIT_DIR / CST_AGENT_DIR (checkout), HERMIT_REPO / CST_AGENT_REPO,
# HERMIT_VERSION / CST_AGENT_VERSION (pin a release; default: cst_version.h).
set -euo pipefail
ROOT_HINT="$(cd "$(dirname "$0")/.." 2>/dev/null && pwd || true)"
deps_hint() {
echo "==> deps: gcc/clang make pkg-config libcurl ncursesw"
if [[ "$(uname -s)" == Darwin ]]; then
echo " macOS: xcode-select --install && brew install ncurses curl pkg-config"
elif command -v pacman >/dev/null 2>&1; then
echo " Arch: sudo pacman -S --needed base-devel curl ncurses"
elif command -v apt-get >/dev/null 2>&1; then
echo " Debian/Ubuntu/WSL: sudo apt install build-essential pkg-config libcurl4-openssl-dev libncurses-dev"
elif command -v dnf >/dev/null 2>&1; then
echo " Fedora: sudo dnf install gcc make pkgconf libcurl-devel ncurses-devel"
elif command -v apk >/dev/null 2>&1; then
echo " Alpine: sudo apk add build-base pkgconf curl-dev ncurses-dev"
fi
}
# On NixOS (outside a nix shell) build inside shell.nix.
run_make() {
if [[ -z "${IN_NIX_SHELL:-}" ]] && command -v nix-shell >/dev/null 2>&1 &&
[[ -f shell.nix ]]; then
nix-shell --run "make $*"
else
make "$@"
fi
}
install_bin() {
local prefix="$1" bin="$2"
mkdir -p "$prefix/bin"
install -m 0755 "$bin" "$prefix/bin/hermit"
ln -sfr "$prefix/bin/hermit" "$prefix/bin/cst-agent" 2>/dev/null || \
ln -sf hermit "$prefix/bin/cst-agent"
}
install_font() {
local prefix="$1" dir="$2"
local ttf=""
if [[ -f "$dir/res/hermit-icons.ttf" ]]; then
ttf="$dir/res/hermit-icons.ttf"
elif [[ -f "$dir/share/hermit/hermit-icons.ttf" ]]; then
ttf="$dir/share/hermit/hermit-icons.ttf"
elif [[ -f "$dir/hermit-icons.ttf" ]]; then
ttf="$dir/hermit-icons.ttf"
fi
[[ -n "$ttf" ]] || {
echo "warning: hermit-icons.ttf not found; run 'make hermit-font' in the tree" >&2
return 0
}
mkdir -p "$prefix/share/hermit" "$prefix/share/fonts/hermit" \
"${HOME:-/tmp}/.local/share/fonts"
install -m 0644 "$ttf" "$prefix/share/hermit/hermit-icons.ttf"
install -m 0644 "$ttf" "$prefix/share/fonts/hermit/hermit-icons.ttf"
install -m 0644 "$ttf" "${HOME}/.local/share/fonts/hermit-icons.ttf"
if [[ -d "$dir/res/hermit-frames" ]]; then
mkdir -p "$prefix/share/hermit/frames"
cp -a "$dir/res/hermit-frames/." "$prefix/share/hermit/frames/"
fi
if command -v fc-cache >/dev/null 2>&1; then
fc-cache -f "${HOME}/.local/share/fonts" >/dev/null 2>&1 || true
fi
echo "==> installed Hermit Icons font"
}
install_helpers() {
local prefix="$1" dir="$2"
local bash_bin
bash_bin="$(command -v bash)"
mkdir -p "$prefix/bin"
cat >"$prefix/bin/hermit-update" <<EOF
#!$bash_bin
# Upgrade Hermit (source build or --release tarball).
export HERMIT_PREFIX="\${HERMIT_PREFIX:-\${CST_AGENT_PREFIX:-$prefix}}"
export CST_AGENT_PREFIX="\$HERMIT_PREFIX"
export HERMIT_DIR="\${HERMIT_DIR:-\${CST_AGENT_DIR:-$dir}}"
export CST_AGENT_DIR="\$HERMIT_DIR"
exec bash "$dir/scripts/bootstrap_linux.sh" "\$HERMIT_DIR" "\$@"
EOF
chmod 0755 "$prefix/bin/hermit-update"
ln -sfr "$prefix/bin/hermit-update" "$prefix/bin/cst-agent-update" 2>/dev/null || \
ln -sf hermit-update "$prefix/bin/cst-agent-update"
cat >"$prefix/bin/hermit-service" <<EOF
#!$bash_bin
# Manage the parliament seat service (foreground under systemd, or --service).
set -euo pipefail
BIN="$prefix/bin/hermit"
cmd="\${1:-status}"
case "\$cmd" in
status)
"\$BIN" --version
if command -v systemctl >/dev/null 2>&1 && systemctl --user is-active hermit-parliament.service >/dev/null 2>&1; then
systemctl --user status hermit-parliament.service --no-pager
elif command -v systemctl >/dev/null 2>&1 && systemctl --user is-active cst-agent-parliament.service >/dev/null 2>&1; then
systemctl --user status cst-agent-parliament.service --no-pager
else
"\$BIN" --service-stop >/dev/null 2>&1 || true
echo "parliament service: not managed by systemd --user (use --login / --service)"
fi
;;
install-unit)
make -C "$dir" install-service PREFIX="$prefix"
;;
start) systemctl --user start hermit-parliament.service 2>/dev/null || systemctl --user start cst-agent-parliament.service ;;
stop) systemctl --user stop hermit-parliament.service 2>/dev/null || true; systemctl --user stop cst-agent-parliament.service 2>/dev/null || true; "\$BIN" --service-stop || true ;;
restart) systemctl --user restart hermit-parliament.service 2>/dev/null || systemctl --user restart cst-agent-parliament.service ;;
*)
echo "usage: hermit-service {status|install-unit|start|stop|restart}" >&2
exit 2
;;
esac
EOF
chmod 0755 "$prefix/bin/hermit-service"
ln -sfr "$prefix/bin/hermit-service" "$prefix/bin/cst-agent-service" 2>/dev/null || \
ln -sf hermit-service "$prefix/bin/cst-agent-service"
}
# Fast path: fetch static release tarball for this machine.
install_from_release() {
local prefix="$1" dir="$2"
# shellcheck source=lib_release.sh
. "$dir/scripts/lib_release.sh"
ROOT="$dir"
local arch
arch="$(cst_host_release_arch)" || {
echo "no prebuilt release for $(uname -m); use --source" >&2
return 1
}
local ver="${HERMIT_VERSION:-${CST_AGENT_VERSION:-}}"
if [[ -z "$ver" ]]; then
ver="$(cst_version)"
fi
local name="hermit-$ver-linux-$arch.tar.gz"
# One-cycle legacy asset name fallback handled below if download fails.
local tmp
tmp="$(mktemp -d /tmp/cst-rel.XXXXXX)"
# shellcheck disable=SC2046
local legacy="cst-agent-$ver-linux-$arch.tar.gz"
if ! cst_fetch "$tmp/$name" $(cst_release_asset_urls "$ver" "$name"); then
if ! cst_fetch "$tmp/$legacy" $(cst_release_asset_urls "$ver" "$legacy"); then
echo "release download failed; falling back to source build" >&2
rm -rf "$tmp"
return 1
fi
name="$legacy"
fi
tar -C "$tmp" -xzf "$tmp/$name"
local bin
bin="$(find "$tmp" -type f \( -name hermit -o -name cst-agent \) | head -1)"
[[ -x "$bin" ]] || { echo "tarball missing hermit/cst-agent" >&2; rm -rf "$tmp"; return 1; }
install_bin "$prefix" "$bin"
# Font may live next to the binary inside the tarball.
local rel_dir
rel_dir="$(dirname "$bin")"
install_font "$prefix" "$rel_dir"
rm -rf "$tmp"
echo "==> installed release $ver ($arch) -> $prefix/bin/hermit"
}
main() {
local repo_url="${HERMIT_REPO:-${CST_AGENT_REPO:-https://git.whcself.xyz/WHC/Hermit.git}}"
local prefix="${HERMIT_PREFIX:-${CST_AGENT_PREFIX:-$HOME/.local}}"
local dir="" pull=1 changed=0 mode="${CST_INSTALL_MODE:-source}" arg
for arg in "$@"; do
case "$arg" in
--no-pull) pull=0 ;;
--release) mode=release ;;
--source) mode=source ;;
--rebuilt) changed=1 ;; # internal: re-exec after a pull
-h | --help)
sed -n '2,16p' "$0"
return 0
;;
*) dir="$arg" ;;
esac
done
if [[ -z "$dir" ]]; then
if [[ -n "${HERMIT_DIR:-}" ]]; then
dir="$HERMIT_DIR"
elif [[ -n "${CST_AGENT_DIR:-}" ]]; then
dir="$CST_AGENT_DIR"
elif [[ -f Makefile && -f src/main.c && -d .git ]]; then
dir="$(pwd)"
elif [[ -n "$ROOT_HINT" && -f "$ROOT_HINT/Makefile" ]]; then
dir="$ROOT_HINT"
elif [[ -d "$HOME/Hermit/.git" ]]; then
dir="$HOME/Hermit"
else
dir="$HOME/CST-agent"
fi
fi
local old="" new=""
if [[ ! -f "$dir/Makefile" ]]; then
echo "==> git clone $repo_url -> $dir"
git clone "$repo_url" "$dir"
changed=1
elif ((pull)); then
old="$(git -C "$dir" rev-parse HEAD)"
if git -C "$dir" diff --quiet && git -C "$dir" diff --cached --quiet; then
echo "==> git pull ($dir)"
git -C "$dir" pull --ff-only
else
echo "warning: $dir has local changes; skipping git pull" >&2
fi
new="$(git -C "$dir" rev-parse HEAD)"
if [[ "$old" != "$new" ]]; then
echo " $(git -C "$dir" log --oneline "$old..$new" | wc -l) new commit(s)"
# The script itself may have changed: continue with the new version.
exec bash "$dir/scripts/bootstrap_linux.sh" "$dir" --no-pull --rebuilt \
${mode:+--$mode}
fi
fi
cd "$dir"
dir="$(pwd)"
if [[ "$mode" == release ]]; then
if install_from_release "$prefix" "$dir"; then
install_helpers "$prefix" "$dir"
echo
echo "OK: $("$prefix/bin/hermit" --version) (release install)"
case ":$PATH:" in
*":$prefix/bin:"*) ;;
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
esac
echo "Upgrade: hermit-update --release Rebuild from source: hermit-update --source"
return 0
fi
echo "==> continuing with source build"
fi
local jobs
jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
echo "==> build (incremental, -j$jobs)"
# Prefer incremental rebuilds. Set CST_AGENT_CLEAN=1 to force a clean tree.
if [[ "${CST_AGENT_CLEAN:-0}" == "1" ]]; then
run_make clean >/dev/null 2>&1 || true
fi
: "${changed:=0}"
if ! run_make -j"$jobs"; then
deps_hint
return 1
fi
if [[ "${CST_AGENT_SKIP_CHECK:-0}" != "1" ]]; then
run_make -j"$jobs" check >/dev/null 2>&1 || echo "warning: some tests failed (run 'make check')"
fi
echo "==> bake Hermit Icons font (if tooling present)"
run_make hermit-font >/dev/null 2>&1 || true
echo "==> install to $prefix/bin (+ font)"
install_bin "$prefix" "$dir/hermit"
install_font "$prefix" "$dir"
install_helpers "$prefix" "$dir"
echo
echo "OK: $("$prefix/bin/hermit" --version)"
case ":$PATH:" in
*":$prefix/bin:"*) ;;
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
esac
echo "Run 'hermit' in a project; upgrade with 'hermit-update' (compat: cst-agent-update)."
echo "Fast upgrade from Release: 'hermit-update --release'."
echo "Parliament helper: 'hermit-service status|install-unit'."
echo "The first start asks for the provider and its API key and saves them."
}
main "$@"