Files
ai-agent/sidecar/install.sh
Gabriel Vidal 3f5c09f67b refactor: adapt to standalone-repo layout
The repo now lives outside the homelab monorepo. Drop the homelab-specific
deploy files from HEAD (docker-compose.yml, traefik.yml, deploy.sh,
deploy.override.yml — canonical copies live in the homelab's
services/ai-agent shim; history keeps these as reference), fix stale
services/ai-agent path references, and make sidecar/install.sh resolve its
.env (SIDECAR_ENV_FILE > repo .env > ~/homelab/.env) and SIDECAR_CWD instead
of assuming the monorepo nesting. Mark the GOAL.md repo-split as done.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 13:23:30 +02:00

84 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# install.sh — set up the claude-sidecar as a systemd *user* service.
#
# The sidecar must run natively on the host (not in Docker) as the repo owner so
# the `claude -p` sessions it spawns use the real ~/.claude auth, hooks and
# skills. This installs a venv, writes a systemd user unit, and starts it.
#
# Reboot survival needs lingering (`sudo loginctl enable-linger $USER`); we try
# it but it's fine if it fails — the service still runs for the current login.
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$HERE/.." && pwd)" # sidecar/ -> repo root
VENV="$HERE/.venv"
PORT="${SIDECAR_PORT:-8790}"
UNIT_DIR="$HOME/.config/systemd/user"
UNIT="$UNIT_DIR/claude-sidecar.service"
# The .env holding SIDECAR_TOKEN (+ optional SIDECAR_PORT), single source of
# truth shared with the docker-compose deployment. Override with
# SIDECAR_ENV_FILE; defaults to this repo's .env, then the homelab checkout's
# (where the compose stack that proxies to this sidecar reads it from).
ENV_FILE="${SIDECAR_ENV_FILE:-}"
if [[ -z "$ENV_FILE" ]]; then
for f in "$PROJECT_ROOT/.env" "$HOME/homelab/.env"; do
[[ -f "$f" ]] && { ENV_FILE="$f"; break; }
done
fi
if [[ -n "$ENV_FILE" && -f "$ENV_FILE" ]]; then
TOKEN="$(grep -E '^SIDECAR_TOKEN=' "$ENV_FILE" | head -1 | cut -d= -f2-)"
PORT="$(grep -E '^SIDECAR_PORT=' "$ENV_FILE" | head -1 | cut -d= -f2- || true)"
PORT="${PORT:-8790}"
fi
[[ -n "${TOKEN:-}" ]] || { echo "error: SIDECAR_TOKEN not found (looked in ${ENV_FILE:-\$PROJECT_ROOT/.env, ~/homelab/.env})" >&2; exit 1; }
# Where spawned `claude -p` sessions start. Defaults to the dir the .env came
# from (the homelab checkout there), overridable with SIDECAR_CWD.
SIDECAR_CWD="${SIDECAR_CWD:-$(dirname "$ENV_FILE")}"
echo "==> venv + deps ($VENV)"
command -v uv >/dev/null || { echo "error: 'uv' not found on PATH" >&2; exit 1; }
[[ -d "$VENV" ]] || uv venv "$VENV"
uv pip install --quiet --python "$VENV" -r "$HERE/requirements.txt"
echo "==> writing $UNIT"
mkdir -p "$UNIT_DIR"
cat > "$UNIT" <<EOF
[Unit]
Description=claude-sidecar — spawns \`claude -p\` sessions for the ai-agent viewer
After=network-online.target
[Service]
Type=simple
WorkingDirectory=$HERE
Environment=SIDECAR_TOKEN=$TOKEN
Environment=SIDECAR_CWD=$SIDECAR_CWD
Environment=PATH=$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=$VENV/bin/uvicorn sidecar:app --host 0.0.0.0 --port $PORT
Restart=on-failure
RestartSec=3
# Spawned \`claude -p\` runs are detached daemons (start_new_session). KillMode=
# process makes systemd signal only uvicorn on stop/restart, so redeploying the
# sidecar (e.g. while Claude is editing it) leaves those in-flight runs alive;
# the restarted sidecar re-discovers them from logs/*.pid.
KillMode=process
[Install]
WantedBy=default.target
EOF
echo "==> enabling + starting"
systemctl --user daemon-reload
systemctl --user enable --now claude-sidecar.service
sleep 1
systemctl --user --no-pager status claude-sidecar.service | head -8 || true
# Survive logout/reboot (needs privileges; ignore failure).
loginctl enable-linger "$USER" 2>/dev/null \
&& echo "==> lingering enabled (survives logout/reboot)" \
|| echo "note: run 'sudo loginctl enable-linger $USER' for reboot survival"
echo "==> done. health: curl -s localhost:$PORT/health"