deploy-to-home-server #69
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: deploy-to-home-server | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| fresh_start: | |
| description: 'WARNING: This ERASES OPENCLAW WORKSPACE DATA - memory, skills, sessions, uploaded files. This action is IRREVERSIBLE. Obsidian vault volume is NOT removed.' | |
| required: false | |
| default: false | |
| type: boolean | |
| skip_git_sync: | |
| description: 'Skip git sync on this deployment (use local workspace state only)' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| deploy: | |
| runs-on: self-hosted | |
| steps: | |
| - name: Clean up work directory | |
| run: sudo rm -rf ${{ github.workspace }}/credentials || true | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Debug User Info | |
| run: | | |
| echo "Current user: $(whoami)" | |
| echo "User ID: $(id -u)" | |
| echo "Group ID: $(id -g)" | |
| echo "Groups: $(id -Gn)" | |
| echo "Docker group members: $(getent group docker)" | |
| echo "Docker socket permissions: $(ls -la /var/run/docker.sock 2>/dev/null || echo 'Docker socket not found')" | |
| - name: Validate required repository variables | |
| run: | | |
| if [ -z "${{ vars.WORKSPACE_STATE_REPO }}" ]; then | |
| echo "ERROR: Repository variable WORKSPACE_STATE_REPO is required" | |
| exit 1 | |
| fi | |
| GUI_BIND_IP="${{ vars.SYNCTHING_GUI_BIND_IP }}" | |
| if [ -n "$GUI_BIND_IP" ]; then | |
| if [ "$GUI_BIND_IP" = "0.0.0.0" ]; then | |
| echo "ERROR: SYNCTHING_GUI_BIND_IP cannot be 0.0.0.0" | |
| exit 1 | |
| fi | |
| if ! echo "$GUI_BIND_IP" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "ERROR: SYNCTHING_GUI_BIND_IP must be an IPv4 address when set" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Create .env file | |
| run: | | |
| umask 077 | |
| cat > .env << EOF | |
| ZAI_API_KEY=${{ secrets.ZAI_API_KEY }} | |
| TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }} | |
| OLLAMA_API_KEY=${{ secrets.OLLAMA_API_KEY }} | |
| PRIMARY_TELEGRAM_ID=${{ secrets.PRIMARY_TELEGRAM_ID }} | |
| GATEWAY_AUTH_PASSWORD=${{ secrets.GATEWAY_AUTH_PASSWORD }} | |
| GOG_KEYRING_PASSWORD=${{ secrets.GOG_KEYRING_PASSWORD }} | |
| TS_AUTHKEY=${{ secrets.TS_AUTHKEY }} | |
| CONTROL_UI_ALLOWED_ORIGIN_1=${{ vars.CONTROL_UI_ALLOWED_ORIGIN_1 }} | |
| CONTROL_UI_ALLOWED_ORIGIN_2=${{ vars.CONTROL_UI_ALLOWED_ORIGIN_2 }} | |
| WORKSPACE_STATE_REPO=${{ vars.WORKSPACE_STATE_REPO }} | |
| WORKSPACE_REPO_TOKEN=${{ secrets.WORKSPACE_REPO_TOKEN }} | |
| WORKSPACE_SYNC_ON_START=${{ github.event.inputs.skip_git_sync == 'false' && 'true' || 'false' }} | |
| WORKSPACE_SYNC_INTERVAL=60 | |
| WORKSPACE_MEMORY_DAYS=30 | |
| TZ=${{ vars.TZ }} | |
| TAILSCALE_HOSTNAME=${{ vars.TAILSCALE_HOSTNAME }} | |
| TS_EXTRA_ARGS=${{ vars.TS_EXTRA_ARGS }} | |
| SYNCTHING_GUI_BIND_IP=${{ vars.SYNCTHING_GUI_BIND_IP }} | |
| AUX_ML_ENABLED=${{ vars.AUX_ML_ENABLED == 'false' && 'false' || 'true' }} | |
| COMPOSE_PROFILES=${{ vars.AUX_ML_ENABLED == 'false' && '' || 'aux-ml' }} | |
| AUX_ML_URL=http://aux-ml:8091 | |
| AUX_ML_MEMORY_LIMIT=${{ vars.AUX_ML_MEMORY_LIMIT }} | |
| AUX_ML_MEMORY_LIMIT_MB=${{ vars.AUX_ML_MEMORY_LIMIT_MB }} | |
| AUX_ML_MAX_QUEUE=${{ vars.AUX_ML_MAX_QUEUE }} | |
| AUX_ML_JOB_TIMEOUT_SECONDS=${{ vars.AUX_ML_JOB_TIMEOUT_SECONDS }} | |
| AUX_ML_POLL_INTERVAL_SECONDS=${{ vars.AUX_ML_POLL_INTERVAL_SECONDS }} | |
| AUX_ML_LLAMACPP_TIMEOUT_SECONDS=${{ vars.AUX_ML_LLAMACPP_TIMEOUT_SECONDS }} | |
| AUX_ML_ALLOWED_INPUT_DIRS=${{ vars.AUX_ML_ALLOWED_INPUT_DIRS }} | |
| AUX_ML_ENFORCE_MEMORY_LIMIT=${{ vars.AUX_ML_ENFORCE_MEMORY_LIMIT }} | |
| AUX_ML_OCR_MAX_PAGES=${{ vars.AUX_ML_OCR_MAX_PAGES }} | |
| EOF | |
| chmod 600 .env | |
| - name: Set compose profiles | |
| run: | | |
| AUX_ML_ENABLED="${{ vars.AUX_ML_ENABLED }}" | |
| if [ -z "$AUX_ML_ENABLED" ]; then | |
| AUX_ML_ENABLED="true" | |
| fi | |
| if [ "$AUX_ML_ENABLED" = "true" ]; then | |
| echo "COMPOSE_PROFILES=aux-ml" >> "$GITHUB_ENV" | |
| else | |
| echo "COMPOSE_PROFILES=" >> "$GITHUB_ENV" | |
| fi | |
| - name: Validate aux-ml model source | |
| if: vars.AUX_ML_ENABLED != 'false' | |
| run: | | |
| if [ -f "aux-ml/models/glm-ocr.gguf" ]; then | |
| echo "Found local glm-ocr model in repository checkout" | |
| else | |
| echo "No local glm-ocr model found; compose default URL will be used" | |
| fi | |
| if [ -f "aux-ml/models/mmproj-glm-ocr.gguf" ]; then | |
| echo "Found local mmproj model in repository checkout" | |
| else | |
| echo "No local mmproj model found; compose default URL will be used" | |
| fi | |
| - name: Create rclone config for backup container | |
| run: | | |
| if [ -n "${{ secrets.RCLONE_CONFIG_B64 }}" ]; then | |
| COMPOSE_PROJECT=$(basename "$PWD") | |
| RCLONE_VOLUME="${COMPOSE_PROJECT}_obsidian-rclone-config" | |
| TMP_RCLONE_CONF=$(mktemp) | |
| trap 'rm -f "$TMP_RCLONE_CONF"' EXIT | |
| echo "${{ secrets.RCLONE_CONFIG_B64 }}" | base64 --decode > "$TMP_RCLONE_CONF" | |
| chmod 600 "$TMP_RCLONE_CONF" | |
| docker volume create "$RCLONE_VOLUME" >/dev/null | |
| docker run --rm \ | |
| -v "$TMP_RCLONE_CONF:/tmp/rclone.conf:ro" \ | |
| -v "$RCLONE_VOLUME:/config/rclone" \ | |
| alpine:3.20 \ | |
| sh -c 'cp /tmp/rclone.conf /config/rclone/rclone.conf && chmod 600 /config/rclone/rclone.conf' | |
| rm -f "$TMP_RCLONE_CONF" | |
| trap - EXIT | |
| echo "rclone config loaded into Docker volume" | |
| else | |
| echo "WARNING: RCLONE_CONFIG_B64 secret not set. Obsidian backups will fail until configured." | |
| fi | |
| - name: FRESH START - Remove workspace volume | |
| if: github.event.inputs.fresh_start == 'true' | |
| run: | | |
| echo "==========================================" | |
| echo "!!! WARNING: FRESH START REQUESTED !!!" | |
| echo "==========================================" | |
| echo "This will DELETE ALL agent data:" | |
| echo " - All memory files (SOUL.md, MEMORY.md, AGENTS.md, etc.)" | |
| echo " - All skills (including agent modifications)" | |
| echo " - All session history and conversation data" | |
| echo " - All uploaded files (PDFs, etc.)" | |
| echo " - All workspace configuration" | |
| echo " - Obsidian vault volume is NOT deleted" | |
| echo "==========================================" | |
| echo "Waiting 10 seconds before proceeding..." | |
| echo "Press Cancel on this workflow to abort." | |
| sleep 10 | |
| docker compose down || true | |
| docker volume rm josemar-assistente_openclaw-workspace 2>/dev/null || true | |
| echo "Workspace volume removed" | |
| - name: Stop existing services | |
| run: docker compose down | |
| - name: Clean up old Docker images | |
| run: | | |
| docker image prune -a -f | |
| docker builder prune -f | |
| docker rmi $(docker images 'josemar-assistente*' -q) 2>/dev/null || true | |
| - name: Build Docker image | |
| run: docker compose build --no-cache | |
| - name: Start services | |
| run: docker compose up -d | |
| - name: Verify tailscale sidecar login | |
| run: | | |
| echo "Checking tailscale sidecar status..." | |
| if ! docker compose ps tailscale | grep -q "Up"; then | |
| echo "ERROR: tailscale service is not running" | |
| docker compose ps | |
| exit 1 | |
| fi | |
| TS_IP="" | |
| for i in {1..30}; do | |
| TS_IP="$(docker compose exec -T tailscale tailscale ip -4 2>/dev/null | sed -n '1p')" | |
| if echo "$TS_IP" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "Tailscale sidecar connected with IPv4: $TS_IP" | |
| break | |
| fi | |
| echo "Waiting for tailscale login/connection... ($i/30)" | |
| sleep 2 | |
| done | |
| if ! echo "$TS_IP" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "ERROR: tailscale sidecar is running but not logged in/connected" | |
| echo "Set TS_AUTHKEY secret or log in manually with: docker compose exec -T tailscale tailscale up" | |
| docker compose logs --tail=120 tailscale | |
| exit 1 | |
| fi | |
| - name: Wait for container to be healthy | |
| run: | | |
| echo "Waiting for container to be healthy..." | |
| for i in {1..30}; do | |
| if docker compose ps | grep -q "healthy"; then | |
| echo "Container is healthy" | |
| break | |
| fi | |
| echo " Waiting... ($i/30)" | |
| sleep 2 | |
| done | |
| - name: Verify deployment | |
| run: docker ps | grep josemar-assistente | |
| - name: Verify skill deployment | |
| run: | | |
| echo "Checking deployed skills..." | |
| docker compose exec -T openclaw ls -la /root/.openclaw/skills/ 2>/dev/null || echo "No skills deployed yet" | |
| - name: Cleanup sensitive files | |
| if: always() | |
| run: rm -f .env |