Skip to content

feat: add standalone Rust CLI binary (awn) for agent-native AWN inter… #22

feat: add standalone Rust CLI binary (awn) for agent-native AWN inter…

feat: add standalone Rust CLI binary (awn) for agent-native AWN inter… #22

name: Deploy Gateway
on:
push:
branches: [main]
paths:
- "gateway/**"
- "packages/agent-world-sdk/src/**"
workflow_dispatch:
concurrency:
group: deploy-gateway
cancel-in-progress: true
permissions:
id-token: write
contents: read
jobs:
deploy:
name: Build & Deploy Gateway
runs-on: ubuntu-latest
env:
ECR_REPOSITORY: awn-gateway
INSTANCE_ID: i-04670f4d1a72c7d5d
GATEWAY_URL: ${{ vars.GATEWAY_URL || 'https://gateway.agentworlds.ai' }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
aws-region: us-east-2
- name: Update Cloudflare DNS
env:
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
run: |
GATEWAY_HOST=$(echo "$GATEWAY_URL" | sed 's|^https\?://||' | cut -d'/' -f1)
EC2_IP="${{ vars.EC2_PUBLIC_IP }}"
if [ -z "$EC2_IP" ]; then
echo "::error::EC2_PUBLIC_IP variable is not set. Add it in repo Settings -> Variables."
exit 1
fi
echo "EC2 public IP: $EC2_IP Gateway: $GATEWAY_HOST"
PAYLOAD="{\"type\":\"A\",\"name\":\"$GATEWAY_HOST\",\"content\":\"$EC2_IP\",\"ttl\":1,\"proxied\":true}"
EXISTING=$(curl -s \
"https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?type=A&name=$GATEWAY_HOST" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json")
if ! echo "$EXISTING" | jq -e '.success == true' > /dev/null 2>&1; then
echo "::warning::Cloudflare API error: $(echo "$EXISTING" | jq -rc '.errors // .')"
echo "Skipping DNS update — record may already be correct."
else
RECORD_ID=$(echo "$EXISTING" | jq -r '.result[0].id // empty')
CURRENT_IP=$(echo "$EXISTING" | jq -r '.result[0].content // empty')
if [ "$CURRENT_IP" = "$EC2_IP" ]; then
echo "DNS already up to date ($GATEWAY_HOST → $EC2_IP), skipping update."
elif [ -n "$RECORD_ID" ]; then
curl -s -X PUT \
"https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" | jq -c '{success,errors}'
echo "Updated DNS A record $RECORD_ID → $EC2_IP"
else
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" | jq -c '{success,errors}'
echo "Created DNS A record $GATEWAY_HOST → $EC2_IP"
fi
fi
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Build & push Docker image
env:
REGISTRY: ${{ steps.ecr-login.outputs.registry }}
run: |
IMAGE="$REGISTRY/$ECR_REPOSITORY"
docker build -f gateway/Dockerfile -t "$IMAGE:${{ github.sha }}" -t "$IMAGE:latest" .
docker push "$IMAGE:${{ github.sha }}"
docker push "$IMAGE:latest"
- name: Deploy via SSM
env:
REGISTRY: ${{ steps.ecr-login.outputs.registry }}
run: |
IMAGE="$REGISTRY/$ECR_REPOSITORY:${{ github.sha }}"
# Build SSM parameters via jq to avoid quoting issues.
# The instance role has ecr-pull permissions, so we use the ECR
# credential helper for authentication instead of the aws CLI.
PARAMS=$(jq -cn \
--arg pull "docker pull $IMAGE" \
--arg run "docker run -d --name awn-gateway --restart unless-stopped -p 80:8100 -v /opt/awn-gateway/data:/data -e HTTP_PORT=8100 -e DATA_DIR=/data -e PUBLIC_URL=$GATEWAY_URL -e PUBLIC_ADDR=$(echo $GATEWAY_URL | sed 's|^https\?://||' | cut -d'/' -f1) $IMAGE" \
'{commands: [
"command -v amazon-ecr-credential-helper >/dev/null 2>&1 || apt-get install -y amazon-ecr-credential-helper",
"mkdir -p /root/.docker && printf '"'"'{\"credsStore\":\"ecr-login\"}'"'"' > /root/.docker/config.json",
$pull,
"docker stop awn-gateway 2>/dev/null || true",
"docker rm awn-gateway 2>/dev/null || true",
"fuser -k 80/tcp 2>/dev/null || true",
$run
]}')
COMMAND_ID=$(aws ssm send-command \
--instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \
--parameters "$PARAMS" \
--region us-east-2 \
--query "Command.CommandId" \
--output text)
echo "SSM Command ID: $COMMAND_ID"
sleep 5
for i in $(seq 1 30); do
if STATUS=$(aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--region us-east-2 \
--query "Status" \
--output text 2>&1); then
echo "Attempt $i: Status=$STATUS"
if [ "$STATUS" = "Success" ]; then
echo "Deploy command succeeded"
exit 0
elif [ "$STATUS" = "Failed" ] || [ "$STATUS" = "Cancelled" ] || [ "$STATUS" = "TimedOut" ]; then
echo "Deploy command failed with status: $STATUS"
aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--region us-east-2 \
--query "StandardErrorContent" \
--output text
exit 1
fi
else
echo "Attempt $i: GetCommandInvocation error (will retry): $STATUS"
fi
sleep 10
done
echo "Timed out waiting for deploy command"
exit 1
- name: Health check
run: |
COMMAND_ID=$(aws ssm send-command \
--instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \
--parameters commands='["sleep 5","curl -sf http://localhost/health"]' \
--region us-east-2 \
--query "Command.CommandId" \
--output text)
sleep 15
STATUS=$(aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--region us-east-2 \
--query "Status" \
--output text)
if [ "$STATUS" = "Success" ]; then
echo "Health check passed"
else
echo "Health check failed (status: $STATUS)"
aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--region us-east-2 \
--query "StandardErrorContent" \
--output text
exit 1
fi