Deploy and Keep Active #79
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 and Keep Active | |
| on: | |
| push: | |
| branches: [ main ] | |
| # Runs automatically every day at 0:00 UTC (8:00 AM Beijing Time) | |
| schedule: | |
| - cron: '0 0 * * *' | |
| workflow_dispatch: | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| env: | |
| GCE_USER: ${{ secrets.GCE_USER || 'zwlddx0815' }} | |
| GCE_INSTANCE_NAME: ${{ secrets.GCE_INSTANCE_NAME || 'interactive-brokers-quant-instance' }} | |
| GCE_ZONE: ${{ secrets.GCE_ZONE || 'us-east1-c' }} | |
| DEPLOY_PATH: ${{ secrets.DEPLOY_PATH || '/home/zwlddx0815/ib-docker' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Authenticate to Google Cloud | |
| id: auth | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| credentials_json: ${{ secrets.GCP_SA_KEY }} | |
| - name: Set up gcloud | |
| uses: google-github-actions/setup-gcloud@v3 | |
| with: | |
| project_id: ${{ steps.auth.outputs.project_id }} | |
| version: '>= 416.0.0' | |
| - name: Prepare SSH key and deployment files | |
| env: | |
| SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} | |
| TWS_USERID: ${{ secrets.TWS_USERID }} | |
| TWS_PASSWORD: ${{ secrets.TWS_PASSWORD }} | |
| TOTP_SECRET: ${{ secrets.TOTP_SECRET }} | |
| VNC_SERVER_PASSWORD: ${{ secrets.VNC_SERVER_PASSWORD }} | |
| TRADING_MODE: ${{ secrets.TRADING_MODE }} | |
| ACCEPT_API_FROM_IP: ${{ secrets.ACCEPT_API_FROM_IP || '10.8.0.0/28' }} | |
| ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY: ${{ secrets.ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY || 'no' }} | |
| run: | | |
| set -euo pipefail | |
| install -d -m 700 "$RUNNER_TEMP/ssh" | |
| SSH_KEY_FILE="$RUNNER_TEMP/ssh/google_compute_engine" | |
| printf '%s\n' "$SSH_PRIVATE_KEY" | tr -d '\r' > "$SSH_KEY_FILE" | |
| chmod 600 "$SSH_KEY_FILE" | |
| if ! ssh-keygen -y -f "$SSH_KEY_FILE" > "$SSH_KEY_FILE.pub"; then | |
| echo "SSH_PRIVATE_KEY is invalid or passphrase-protected; provide an unencrypted private key that matches the VM authorized_keys entry." >&2 | |
| exit 1 | |
| fi | |
| chmod 644 "$SSH_KEY_FILE.pub" | |
| echo "SSH_KEY_FILE=$SSH_KEY_FILE" >> "$GITHUB_ENV" | |
| ENV_FILE="$RUNNER_TEMP/ibkr-gateway.env" | |
| export ENV_FILE | |
| python3 - <<'PY' | |
| import os | |
| env_file = os.environ["ENV_FILE"] | |
| keys = [ | |
| "TWS_USERID", | |
| "TWS_PASSWORD", | |
| "TOTP_SECRET", | |
| "VNC_SERVER_PASSWORD", | |
| "TRADING_MODE", | |
| "ACCEPT_API_FROM_IP", | |
| "ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY", | |
| ] | |
| def quote(value: str) -> str: | |
| return "'" + value.replace("'", "'\"'\"'") + "'" | |
| with open(env_file, "w", encoding="utf-8") as fh: | |
| for key in keys: | |
| fh.write(f"{key}={quote(os.environ[key])}\n") | |
| PY | |
| echo "ENV_FILE=$ENV_FILE" >> "$GITHUB_ENV" | |
| DEPLOY_SCRIPT="$RUNNER_TEMP/ibkr-gateway-deploy.sh" | |
| cat > "$DEPLOY_SCRIPT" <<'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| cd "${DEPLOY_PATH}" | |
| sudo docker compose build | |
| sudo docker compose down | |
| sudo docker compose up -d | |
| echo "Waiting for container to stabilize..." | |
| sleep 30 | |
| sudo bash ./scripts/install_2fa_bot_watcher.sh | |
| sleep 5 | |
| sudo systemctl status ibkr-2fa-bot.timer --no-pager | |
| echo "Deployment complete. Bot is monitoring the login window." | |
| echo "Run 'docker exec ib-gateway tail -f /home/ibgateway/2fa.log' for real-time status." | |
| EOF | |
| chmod 700 "$DEPLOY_SCRIPT" | |
| echo "DEPLOY_SCRIPT=$DEPLOY_SCRIPT" >> "$GITHUB_ENV" | |
| - name: Deploy and Log Setup | |
| run: | | |
| set -euo pipefail | |
| REMOTE_TARGET="${GCE_USER}@${GCE_INSTANCE_NAME}" | |
| REMOTE_SYNC_COMMAND=$(cat <<EOF | |
| set -euo pipefail | |
| sudo mkdir -p '${DEPLOY_PATH}' | |
| sudo chown -R '${GCE_USER}':'${GCE_USER}' '${DEPLOY_PATH}' | |
| if [ -d '${DEPLOY_PATH}/.git' ]; then | |
| cd '${DEPLOY_PATH}' | |
| git fetch --all | |
| git reset --hard origin/main | |
| else | |
| rm -rf '${DEPLOY_PATH}' | |
| git clone 'https://github.com/${{ github.repository }}.git' '${DEPLOY_PATH}' | |
| fi | |
| EOF | |
| ) | |
| gcloud compute ssh "${REMOTE_TARGET}" \ | |
| --project "${{ steps.auth.outputs.project_id }}" \ | |
| --zone "${GCE_ZONE}" \ | |
| --quiet \ | |
| --tunnel-through-iap \ | |
| --ssh-key-file "${SSH_KEY_FILE}" \ | |
| --command "${REMOTE_SYNC_COMMAND}" | |
| gcloud compute scp "${ENV_FILE}" "${REMOTE_TARGET}:${DEPLOY_PATH}/.env" \ | |
| --project "${{ steps.auth.outputs.project_id }}" \ | |
| --zone "${GCE_ZONE}" \ | |
| --quiet \ | |
| --tunnel-through-iap \ | |
| --ssh-key-file "${SSH_KEY_FILE}" | |
| gcloud compute scp "${DEPLOY_SCRIPT}" "${REMOTE_TARGET}:${DEPLOY_PATH}/ibkr-gateway-deploy.sh" \ | |
| --project "${{ steps.auth.outputs.project_id }}" \ | |
| --zone "${GCE_ZONE}" \ | |
| --quiet \ | |
| --tunnel-through-iap \ | |
| --ssh-key-file "${SSH_KEY_FILE}" | |
| gcloud compute ssh "${REMOTE_TARGET}" \ | |
| --project "${{ steps.auth.outputs.project_id }}" \ | |
| --zone "${GCE_ZONE}" \ | |
| --quiet \ | |
| --tunnel-through-iap \ | |
| --ssh-key-file "${SSH_KEY_FILE}" \ | |
| --command "export DEPLOY_PATH='${DEPLOY_PATH}' && bash '${DEPLOY_PATH}/ibkr-gateway-deploy.sh' && rm -f '${DEPLOY_PATH}/ibkr-gateway-deploy.sh'" |