Skip to content

Deploy and Keep Active #99

Deploy and Keep Active

Deploy and Keep Active #99

Workflow file for this run

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: ${{ vars.IB_GATEWAY_GCE_USER }}
GCE_INSTANCE_NAME: ${{ vars.IB_GATEWAY_INSTANCE_NAME }}
GCE_ZONE: ${{ vars.IB_GATEWAY_ZONE }}
DEPLOY_PATH: ${{ vars.IB_GATEWAY_DEPLOY_PATH }}
IB_GATEWAY_MODE: ${{ vars.IB_GATEWAY_MODE }}
CLOUD_RUN_EGRESS_CIDR: ${{ vars.IB_GATEWAY_CLOUD_RUN_EGRESS_CIDR }}
ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY: ${{ vars.IB_GATEWAY_ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY }}
TWS_ACCEPT_INCOMING: ${{ vars.IB_GATEWAY_TWS_ACCEPT_INCOMING }}
READ_ONLY_API: ${{ vars.IB_GATEWAY_READ_ONLY_API }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- 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 }}
run: |
set -euo pipefail
required_vars=(
GCE_USER
GCE_INSTANCE_NAME
GCE_ZONE
DEPLOY_PATH
SSH_PRIVATE_KEY
TWS_USERID
TWS_PASSWORD
TOTP_SECRET
VNC_SERVER_PASSWORD
IB_GATEWAY_MODE
CLOUD_RUN_EGRESS_CIDR
ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY
)
for var_name in "${required_vars[@]}"; do
if [ -z "${!var_name:-}" ]; then
echo "${var_name} is required. Set it in GitHub Actions secrets before running this workflow." >&2
exit 1
fi
done
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"]
values = {
"TWS_USERID": os.environ["TWS_USERID"],
"TWS_PASSWORD": os.environ["TWS_PASSWORD"],
"TOTP_SECRET": os.environ["TOTP_SECRET"],
"VNC_SERVER_PASSWORD": os.environ["VNC_SERVER_PASSWORD"],
"TRADING_MODE": os.environ["IB_GATEWAY_MODE"],
"ACCEPT_API_FROM_IP": os.environ["CLOUD_RUN_EGRESS_CIDR"],
"ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY": os.environ["ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY"],
"TWS_ACCEPT_INCOMING": os.environ["TWS_ACCEPT_INCOMING"],
"READ_ONLY_API": os.environ["READ_ONLY_API"],
}
def quote(value: str) -> str:
return "'" + value.replace("'", "'\"'\"'") + "'"
with open(env_file, "w", encoding="utf-8") as fh:
for key, value in values.items():
fh.write(f"{key}={quote(value)}\n")
PY
echo "ENV_FILE=$ENV_FILE" >> "$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}"
REMOTE_DEPLOY_COMMAND=$(cat <<EOF
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
)
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_DEPLOY_COMMAND}"