Files
LLeMbas/deploy/update.sh
T
Jaroslav Beneš 5117168454 A terminal panel beside an agent chat
A real shell on the chat's own connection, opened and closed like the
inspector and never beside it. The modes govern the model; what a person
types is theirs, since they hold the credential and could open the same
shell with an ssh client. The model cannot see the panel -- a button
copies the output you choose into the composer.

The session outlives the socket: closing the panel leaves a build
running, and coming back reattaches with the scrollback. Two tabs share
one shell and the smaller window decides the size. It ends on an idle
timeout, on deleting the chat, on disabling, moving or deleting the
connection, and on a restart -- which says why rather than quietly
opening a fresh shell that has lost the working directory.

The nginx template's `Connection ""` is right for SSE and fails every
WebSocket handshake, so `location /` now uses a `map $http_upgrade`;
update.sh grows a drift check for it, because the only symptom on a
stale vhost is a panel that cannot connect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 01:44:07 +02:00

132 lines
5.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Pull the latest LLeMbas into the deployment and restart the service.
#
# Run this after pushing. It fetches, hard-resets the deployment checkout to the
# remote branch, reinstalls dependencies if they changed, and restarts. Nothing
# is ever edited in place under the deployment prefix, so a hard reset is safe
# and avoids merge conflicts from a dirty tree.
set -euo pipefail
SERVICE_USER="${SERVICE_USER:-lembas}"
PREFIX="${PREFIX:-/srv/lembas}"
BRANCH="${LEMBAS_BRANCH:-main}"
APP="$PREFIX/app"
VENV="$PREFIX/venv"
if [[ ! -d "$APP/.git" ]]; then
echo "No deployment at $APP. Run deploy/install.sh first." >&2
exit 1
fi
git_as() { sudo -u "$SERVICE_USER" git -C "$APP" "$@"; }
before=$(git_as rev-parse HEAD)
echo "== fetching =="
git_as fetch --quiet origin "$BRANCH"
git_as reset --hard --quiet "origin/$BRANCH"
after=$(git_as rev-parse HEAD)
if [[ "$before" == "$after" ]]; then
echo " already at ${after:0:7}, nothing to pull"
else
echo " ${before:0:7} -> ${after:0:7}"
git_as --no-pager log --oneline "$before..$after" | sed 's/^/ /'
fi
# Cheap and idempotent; catches a dependency added since the last deploy.
# The extras a deployment gets. `search` because DuckDuckGo is the default web
# search provider and is meant to need no setup; `ssh` because agent chats reach
# their machine over it and a deployment without it offers the feature with an
# install hint instead. Listed here AND in install.sh -- an extra added to only
# one of them means existing deployments silently miss it.
LEMBAS_EXTRAS="${LEMBAS_EXTRAS:-search,ssh}"
echo "== dependencies =="
sudo -u "$SERVICE_USER" "$VENV/bin/pip" install --quiet -e "$APP[$LEMBAS_EXTRAS]"
# The unit is NOT reinstalled automatically. An installed unit usually carries
# host-specific lines the template cannot know about -- an ordering dependency
# on whatever serves the models, a note about how the prefix is mounted -- and
# overwriting those on every update would be a worse surprise than drifting.
#
# So this compares the *template* against the one last applied here, not the
# template against the installed file. Comparing the files would warn forever
# about the local lines, and a warning that always fires is one nobody reads.
#
# The drift is worth catching: a change in the unit can be what makes a release
# work at all, and a host that pulled the code without it would run the new
# version under the old settings and fail confusingly.
STAMP="$PREFIX/.unit-applied"
current=$(sha256sum "$APP/deploy/lembas.service" | cut -d' ' -f1)
if [[ -f "$STAMP" && "$(cat "$STAMP")" != "$current" ]]; then
echo "== systemd unit ==" >&2
echo " deploy/lembas.service has changed since it was last applied here." >&2
echo " Review it and merge by hand, keeping this host's own lines:" >&2
echo " diff /etc/systemd/system/lembas.service <(sed \\" >&2
echo " -e 's|__PREFIX__|$PREFIX|g' -e 's|__SERVICE_USER__|$SERVICE_USER|g' \\" >&2
echo " $APP/deploy/lembas.service)" >&2
echo " Then: sudo systemctl daemon-reload && sudo systemctl restart lembas" >&2
echo " And record it as applied: echo $current | sudo tee $STAMP" >&2
elif [[ ! -f "$STAMP" ]]; then
# First run after this check was added. Assume what is installed is current;
# there is nothing to compare against and crying wolf on every host once is
# not worth it.
echo "$current" | sudo tee "$STAMP" >/dev/null
fi
# The same argument for the vhost, and the failure is worse. A stale unit at
# least says something in the journal; a stale vhost breaks a feature two layers
# away, and the only symptom is a panel that says it could not connect. The
# terminal is a WebSocket, and a `location` that does not pass an upgrade
# through fails every handshake while every test in the suite still passes.
VHOST_STAMP="$PREFIX/.vhost-applied"
vhost_now=$(sha256sum "$APP/deploy/nginx-vhost.conf" | cut -d' ' -f1)
site_host=""; app_port="8080"
# Written by install.sh. Absent on a deployment that predates it, in which case
# the commands below are a template rather than a copy-paste.
if [[ -f "$PREFIX/.deploy-env" ]]; then
. "$PREFIX/.deploy-env"
site_host="$SITE_HOST"; app_port="$APP_PORT"
fi
installed_vhost="/etc/nginx/conf.d/${site_host:-your-host}.conf"
vhost_stale=""
if [[ -f "$VHOST_STAMP" ]]; then
[[ "$(cat "$VHOST_STAMP")" != "$vhost_now" ]] && vhost_stale="the template has changed"
elif [[ -n "$site_host" && -f "$installed_vhost" ]]; then
# First run with this check, so there is no stamp to compare against. Rather
# than assume what is installed is current -- which is what the unit check
# does, and would hide exactly the change this was added for -- look for the
# one thing that must be there. Everything else is left to the stamp.
grep -q 'lembas_connection_upgrade' "$installed_vhost" \
|| vhost_stale="the installed vhost does not pass WebSocket upgrades through, so the terminal cannot connect"
fi
if [[ -n "$vhost_stale" ]]; then
echo "== nginx vhost ==" >&2
echo " $vhost_stale." >&2
echo " Review and reinstall it:" >&2
echo " diff $installed_vhost <(sed \\" >&2
echo " -e 's|__SITE_HOST__|${site_host:-your-host}|g' -e 's|__APP_PORT__|$app_port|g' \\" >&2
echo " $APP/deploy/nginx-vhost.conf)" >&2
echo " Then: sudo nginx -t && sudo systemctl reload nginx" >&2
echo " And record it as applied: echo $vhost_now | sudo tee $VHOST_STAMP" >&2
elif [[ ! -f "$VHOST_STAMP" ]]; then
echo "$vhost_now" | sudo tee "$VHOST_STAMP" >/dev/null
fi
echo "== restart =="
sudo systemctl restart lembas
sleep 2
if systemctl is-active --quiet lembas; then
echo " lembas is running"
else
echo " lembas FAILED to start:" >&2
sudo journalctl -u lembas -n 30 --no-pager >&2
exit 1
fi