#!/usr/bin/env bash set -euo pipefail # ── Defaults ────────────────────────────────────────────────────────────────── DEFAULT_INSTALL_DIR="/opt/reversellama-client" DEFAULT_SERVICE_NAME="reversellama-client" DEFAULT_UPSTREAM="http://localhost:11434" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" INSTALL_DIR="$DEFAULT_INSTALL_DIR" SERVICE_NAME="$DEFAULT_SERVICE_NAME" SERVER_URL="" TOKEN="" CLIENT_ID="$(hostname -s 2>/dev/null || echo "linux-client")" UPSTREAM="$DEFAULT_UPSTREAM" SKIP_OLLAMA=false # ── Colors ──────────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } die() { err "$@"; exit 1; } # ── Usage ───────────────────────────────────────────────────────────────────── usage() { cat < ReverseLlama server URL (e.g. http://my-server:5050) --token Shared secret token for the server Optional: --client-id Client identifier; defaults to hostname --upstream Local Ollama URL; defaults to $DEFAULT_UPSTREAM --install-dir Install directory; defaults to $DEFAULT_INSTALL_DIR --service-name systemd service name; defaults to $DEFAULT_SERVICE_NAME --no-ollama Skip Ollama installation and status check -h, --help Show this help message Examples: $0 --server http://gpu-server:5050 --token "my-secret" $0 --server http://gpu-server:5050 --token "my-secret" --no-ollama EOF exit 0 } # ── Argument parsing ────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in --server) SERVER_URL="$2"; shift 2 ;; --token) TOKEN="$2"; shift 2 ;; --client-id) CLIENT_ID="$2"; shift 2 ;; --upstream) UPSTREAM="$2"; shift 2 ;; --install-dir) INSTALL_DIR="$2"; shift 2 ;; --service-name) SERVICE_NAME="$2"; shift 2 ;; --no-ollama) SKIP_OLLAMA=true; shift ;; -h|--help) usage ;; *) die "Unknown option: $1" ;; esac done # ── Prompt for missing required values ─────────────────────────────────────── if [[ -z "$SERVER_URL" ]]; then read -rp "ReverseLlama server URL (e.g. http://my-server:5050): " SERVER_URL fi if [[ -z "$SERVER_URL" ]]; then die "Server URL is required." fi if [[ -z "$TOKEN" ]]; then read -rsp "Server token: " TOKEN echo fi if [[ -z "$TOKEN" ]]; then die "Token is required." fi # ── Root check ──────────────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then die "This script must be run as root (or with sudo)." fi # ── Check / install .NET 10 SDK ────────────────────────────────────────────── install_dotnet() { info "Installing .NET 10 SDK..." if command -v apt-get &>/dev/null; then apt-get update -qq apt-get install -y -qq wget apt-transport-https # Detect distro codename for the Microsoft package repo if grep -qi "ubuntu" /etc/os-release 2>/dev/null; then DISTRO="ubuntu" CODENAME="$(. /etc/os-release && echo "$VERSION_CODENAME")" case "$CODENAME" in noble|jammy|focal) ;; # supported *) warn "Ubuntu $CODENAME may not have .NET 10 packages yet; using latest available." CODENAME="noble" ;; esac elif grep -qi "debian" /etc/os-release 2>/dev/null; then DISTRO="debian" CODENAME="$(. /etc/os-release && echo "$VERSION_CODENAME")" case "$CODENAME" in trixie|bookworm|bullseye) ;; *) warn "Debian $CODENAME may not have .NET 10 packages; using latest available." CODENAME="trixie" ;; esac else die "Unsupported distro. Install .NET 10 SDK manually: https://dotnet.microsoft.com/download" fi # Add Microsoft package repository GPG key and repo curl -fsSL "https://packages.microsoft.com/config/$DISTRO/$CODENAME/packages-microsoft-prod.deb" \ -o /tmp/packages-microsoft-prod.deb dpkg -i /tmp/packages-microsoft-prod.deb rm -f /tmp/packages-microsoft-prod.deb apt-get update -qq apt-get install -y -qq dotnet-sdk-10.0 elif command -v dnf &>/dev/null || command -v yum &>/dev/null; then PKG_MGR="dnf" command -v dnf &>/dev/null || PKG_MGR="yum" # Add Microsoft package repository for RHEL/CentOS/Fedora cat > /etc/yum.repos.d/microsoft-prod.repo <<'REPO' [microsoft-prod] name=Microsoft Production Repository baseurl=https://packages.microsoft.com/rhel/9/prod/ enabled=1 gpgcheck=1 gpgkey=https://packages.microsoft.com/keys/microsoft.asc REPO $PKG_MGR install -y dotnet-sdk-10.0 elif command -v pacman &>/dev/null; then warn "Arch Linux detected. .NET 10 may need to be installed from the AUR or manually." warn "See: https://dotnet.microsoft.com/download" die "Cannot auto-install .NET SDK on Arch. Please install dotnet-sdk-10 manually." else die "Unsupported package manager. Install .NET 10 SDK manually: https://dotnet.microsoft.com/download" fi DOTNET_CMD="$(command -v dotnet 2>/dev/null || echo /usr/share/dotnet/dotnet)" if [[ ! -x "$DOTNET_CMD" ]]; then DOTNET_CMD="/usr/bin/dotnet" fi if [[ ! -x "$DOTNET_CMD" ]]; then die ".NET SDK installation succeeded but dotnet binary not found. Please add it to PATH." fi DOTNET_VER="$("$DOTNET_CMD" --version 2>/dev/null || true)" info ".NET SDK installed: $DOTNET_CMD ($DOTNET_VER)" } DOTNET_CMD="" if command -v dotnet &>/dev/null; then DOTNET_CMD="$(command -v dotnet)" DOTNET_VER="$("$DOTNET_CMD" --version 2>/dev/null || true)" if [[ "$DOTNET_VER" == 10.* ]]; then info "dotnet 10 is already installed: $DOTNET_CMD ($DOTNET_VER)" else warn "dotnet is installed ($DOTNET_VER) but version 10.x is required." install_dotnet fi else install_dotnet fi # ── Check / install Ollama ─────────────────────────────────────────────────── if [[ "$SKIP_OLLAMA" == "false" ]]; then if command -v ollama &>/dev/null; then info "Ollama is installed: $(command -v ollama)" if systemctl is-active --quiet ollama 2>/dev/null; then info "Ollama service is running." else warn "Ollama is installed but not running. Starting..." systemctl start ollama systemctl enable ollama 2>/dev/null || true info "Ollama started." fi else info "Ollama is not installed. Installing..." curl -fsSL https://ollama.com/install.sh | sh info "Ollama installed." systemctl start ollama systemctl enable ollama 2>/dev/null || true info "Ollama started." fi else info "Skipping Ollama check (--no-ollama)." fi # ── Build client from source ───────────────────────────────────────────────── CLIENT_SRC="$REPO_ROOT/src/ReverseLlama.Client" if [[ ! -d "$CLIENT_SRC" ]]; then die "Client source not found at $CLIENT_SRC. Run this script from the repository or pass --install-dir." fi info "Building ReverseLlama client (self-contained, linux-x64)..." BUILD_DIR="$(mktemp -d /tmp/reversellama-build.XXXXXX)" trap 'rm -rf "$BUILD_DIR"' EXIT "$DOTNET_CMD" publish "$CLIENT_SRC/ReverseLlama.Client.csproj" \ -c Release \ -r linux-x64 \ --self-contained true \ -o "$BUILD_DIR" if [[ ! -f "$BUILD_DIR/ReverseLlama.Client" ]]; then die "Build failed. ReverseLlama.Client binary not found in output." fi info "Build successful." # ── Install ─────────────────────────────────────────────────────────────────── info "Installing to $INSTALL_DIR..." mkdir -p "$INSTALL_DIR" cp -a "$BUILD_DIR"/. "$INSTALL_DIR/" chmod +x "$INSTALL_DIR/ReverseLlama.Client" info "Client installed to $INSTALL_DIR." # ── Create systemd service ─────────────────────────────────────────────────── SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" if systemctl list-unit-files "$SERVICE_NAME.service" &>/dev/null 2>&1; then info "Stopping existing service $SERVICE_NAME..." systemctl stop "$SERVICE_NAME" 2>/dev/null || true fi cat > "$SERVICE_FILE" <