name: OpenBrain MCP Build and Deploy on: push: branches: - '**' jobs: build-and-deploy: runs-on: ubuntu-latest env: CARGO_TERM_COLOR: always VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} OPENBRAIN__DATABASE__HOST: ${{ secrets.OPENBRAIN__DATABASE__HOST }} OPENBRAIN__DATABASE__PORT: ${{ secrets.OPENBRAIN__DATABASE__PORT }} OPENBRAIN__DATABASE__NAME: ${{ secrets.OPENBRAIN__DATABASE__NAME }} OPENBRAIN__DATABASE__USER: ${{ secrets.OPENBRAIN__DATABASE__USER }} OPENBRAIN__DATABASE__PASSWORD: ${{ secrets.OPENBRAIN__DATABASE__PASSWORD }} OPENBRAIN__DATABASE__POOL_SIZE: ${{ secrets.OPENBRAIN__DATABASE__POOL_SIZE }} DEPLOY_DIR: /opt/openbrain-mcp SERVICE_NAME: openbrain-mcp steps: - name: Install prerequisites run: | set -euxo pipefail if command -v apt-get >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=; fi $SUDO apt-get update $SUDO apt-get install -y --no-install-recommends git ca-certificates curl elif command -v apk >/dev/null 2>&1; then apk add --no-cache git ca-certificates curl fi - name: Checkout repository run: | set -euxo pipefail git clone --depth 1 "https://${{ github.token }}@gitea.ingwaz.work/${{ github.repository }}.git" . git fetch origin "${{ github.ref }}" git checkout FETCH_HEAD - name: Install build dependencies run: | set -euxo pipefail if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=; fi $SUDO apt-get install -y --no-install-recommends build-essential pkg-config libssl-dev openssh-client - name: Install Rust toolchain run: | set -euxo pipefail curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable . "$HOME/.cargo/env" rustc --version cargo --version - name: CI checks run: | set -euxo pipefail . "$HOME/.cargo/env" cargo check cargo test --no-run - name: Build release run: | set -euxo pipefail . "$HOME/.cargo/env" cargo build --release test -x target/release/openbrain-mcp - name: Setup SSH auth if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' run: | set -euxo pipefail : "${VPS_HOST:?Set repository secret VPS_HOST}" : "${VPS_USER:=root}" install -d -m 700 "$HOME/.ssh" printf '%s\n' "${{ secrets.VPS_SSH_KEY }}" > "$HOME/.ssh/deploy_key" chmod 600 "$HOME/.ssh/deploy_key" ssh-keyscan -H "$VPS_HOST" >> "$HOME/.ssh/known_hosts" - name: Deploy artifacts if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' run: | set -euxo pipefail : "${VPS_HOST:?Set repository secret VPS_HOST}" : "${VPS_USER:=root}" SSH="ssh -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes" SCP="scp -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes" # Stop service before deploying to avoid "Text file busy" error $SSH "$VPS_USER@$VPS_HOST" "systemctl stop $SERVICE_NAME 2>/dev/null || true" $SSH "$VPS_USER@$VPS_HOST" "mkdir -p $DEPLOY_DIR/.gitea $DEPLOY_DIR/models $DEPLOY_DIR/logs $DEPLOY_DIR/lib" $SCP target/release/openbrain-mcp "$VPS_USER@$VPS_HOST:$DEPLOY_DIR/openbrain-mcp" $SCP .gitea/openbrain.service "$VPS_USER@$VPS_HOST:/etc/systemd/system/$SERVICE_NAME.service" $SCP .gitea/download-model.sh "$VPS_USER@$VPS_HOST:$DEPLOY_DIR/.gitea/download-model.sh" - name: Bootstrap VPS and restart service if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' run: | set -euxo pipefail : "${VPS_HOST:?Set repository secret VPS_HOST}" : "${VPS_USER:=root}" SSH="ssh -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes" : "${OPENBRAIN__DATABASE__HOST:?Set repository secret OPENBRAIN__DATABASE__HOST}" : "${OPENBRAIN__DATABASE__NAME:?Set repository secret OPENBRAIN__DATABASE__NAME}" : "${OPENBRAIN__DATABASE__USER:?Set repository secret OPENBRAIN__DATABASE__USER}" : "${OPENBRAIN__DATABASE__PASSWORD:?Set repository secret OPENBRAIN__DATABASE__PASSWORD}" : "${OPENBRAIN__DATABASE__PORT:=5432}" : "${OPENBRAIN__DATABASE__POOL_SIZE:=10}" $SSH "$VPS_USER@$VPS_HOST" "\ DEPLOY_DIR=$DEPLOY_DIR \ SERVICE_USER=openbrain \ SERVICE_GROUP=openbrain \ ORT_VERSION=1.24.3 \ OPENBRAIN__DATABASE__HOST='$OPENBRAIN__DATABASE__HOST' \ OPENBRAIN__DATABASE__PORT='$OPENBRAIN__DATABASE__PORT' \ OPENBRAIN__DATABASE__NAME='$OPENBRAIN__DATABASE__NAME' \ OPENBRAIN__DATABASE__USER='$OPENBRAIN__DATABASE__USER' \ OPENBRAIN__DATABASE__PASSWORD='$OPENBRAIN__DATABASE__PASSWORD' \ OPENBRAIN__DATABASE__POOL_SIZE='$OPENBRAIN__DATABASE__POOL_SIZE' \ bash -s" <<'EOS' set -euo pipefail DEPLOY_DIR="${DEPLOY_DIR:-/opt/openbrain-mcp}" SERVICE_USER="${SERVICE_USER:-openbrain}" SERVICE_GROUP="${SERVICE_GROUP:-openbrain}" ORT_VERSION="${ORT_VERSION:-1.24.3}" if command -v apt-get >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update apt-get install -y --no-install-recommends ca-certificates curl tar libssl3 fi if ! getent group "$SERVICE_GROUP" >/dev/null 2>&1; then groupadd --system "$SERVICE_GROUP" fi if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then useradd --system --gid "$SERVICE_GROUP" --home "$DEPLOY_DIR" --shell /usr/sbin/nologin "$SERVICE_USER" fi install -d -m 0755 "$DEPLOY_DIR" "$DEPLOY_DIR/models" "$DEPLOY_DIR/logs" "$DEPLOY_DIR/lib" ARCH="$(uname -m)" case "$ARCH" in x86_64) ORT_ARCH="x64" ;; aarch64|arm64) ORT_ARCH="aarch64" ;; *) echo "Unsupported arch: $ARCH"; exit 1 ;; esac if [[ ! -f "$DEPLOY_DIR/lib/libonnxruntime.so" ]]; then TMP_DIR="$(mktemp -d)" ORT_TGZ="onnxruntime-linux-${ORT_ARCH}-${ORT_VERSION}.tgz" ORT_URL="https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VERSION}/${ORT_TGZ}" curl -fL "$ORT_URL" -o "$TMP_DIR/$ORT_TGZ" tar -xzf "$TMP_DIR/$ORT_TGZ" -C "$TMP_DIR" ORT_ROOT="$TMP_DIR/onnxruntime-linux-${ORT_ARCH}-${ORT_VERSION}" cp "$ORT_ROOT/lib/libonnxruntime.so" "$DEPLOY_DIR/lib/libonnxruntime.so" cp "$ORT_ROOT/lib/libonnxruntime.so.${ORT_VERSION}" "$DEPLOY_DIR/lib/libonnxruntime.so.${ORT_VERSION}" || true rm -rf "$TMP_DIR" fi ENV_FILE="$DEPLOY_DIR/.env" if [[ ! -f "$ENV_FILE" ]]; then if [[ -f "$DEPLOY_DIR/.env.example" ]]; then cp "$DEPLOY_DIR/.env.example" "$ENV_FILE"; else touch "$ENV_FILE"; fi fi upsert_env() { local key="$1" local value="$2" local escaped_value escaped_value="$(printf '%s' "$value" | sed -e 's/[\\&|]/\\&/g')" if grep -qE "^${key}=" "$ENV_FILE"; then sed -i "s|^${key}=.*|${key}=${escaped_value}|" "$ENV_FILE" else printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE" fi } upsert_env "OPENBRAIN__DATABASE__HOST" "$OPENBRAIN__DATABASE__HOST" upsert_env "OPENBRAIN__DATABASE__PORT" "$OPENBRAIN__DATABASE__PORT" upsert_env "OPENBRAIN__DATABASE__NAME" "$OPENBRAIN__DATABASE__NAME" upsert_env "OPENBRAIN__DATABASE__USER" "$OPENBRAIN__DATABASE__USER" upsert_env "OPENBRAIN__DATABASE__PASSWORD" "$OPENBRAIN__DATABASE__PASSWORD" upsert_env "OPENBRAIN__DATABASE__POOL_SIZE" "$OPENBRAIN__DATABASE__POOL_SIZE" upsert_env "OPENBRAIN__EMBEDDING__MODEL_PATH" "$DEPLOY_DIR/models/all-MiniLM-L6-v2" upsert_env "ORT_DYLIB_PATH" "$DEPLOY_DIR/lib/libonnxruntime.so" upsert_env "OPENBRAIN__SERVER__HOST" "0.0.0.0" chmod +x "$DEPLOY_DIR/openbrain-mcp" "$DEPLOY_DIR/.gitea/download-model.sh" chown -R "$SERVICE_USER:$SERVICE_GROUP" "$DEPLOY_DIR" EOS $SSH "$VPS_USER@$VPS_HOST" "DEPLOY_DIR=$DEPLOY_DIR bash $DEPLOY_DIR/.gitea/download-model.sh" $SSH "$VPS_USER@$VPS_HOST" "cd $DEPLOY_DIR && ./openbrain-mcp migrate" $SSH "$VPS_USER@$VPS_HOST" "systemctl daemon-reload" $SSH "$VPS_USER@$VPS_HOST" "systemctl enable $SERVICE_NAME" $SSH "$VPS_USER@$VPS_HOST" "systemctl restart $SERVICE_NAME" - name: Verify deployment if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' run: | set -euxo pipefail : "${VPS_HOST:?Set repository secret VPS_HOST}" : "${VPS_USER:=root}" SSH="ssh -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes" sleep 5 $SSH "$VPS_USER@$VPS_HOST" "systemctl status $SERVICE_NAME --no-pager || journalctl -u $SERVICE_NAME --no-pager -n 80" curl -fsS "http://$VPS_HOST:3100/health" curl -fsS "http://$VPS_HOST:3100/ready" - name: Cleanup SSH key if: always() run: | rm -f "$HOME/.ssh/deploy_key"