Skip to content

deploy

deploy #9

Workflow file for this run

name: deploy
on:
push:
branches: [master]
workflow_dispatch:
concurrency:
group: deploy-x402
cancel-in-progress: false
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm test
build-and-push:
needs: verify
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
run: |
short_sha="${GITHUB_SHA::7}"
owner_lc="${GITHUB_REPOSITORY_OWNER,,}"
echo "tag=sha-${short_sha}" >> "$GITHUB_OUTPUT"
echo "owner=${owner_lc}" >> "$GITHUB_OUTPUT"
- uses: docker/build-push-action@v6
with:
context: .
push: true
# GHCR rejects uppercase repo paths, so the image lives under the
# lowercased owner regardless of how the repo is spelled.
tags: |
ghcr.io/${{ steps.meta.outputs.owner }}/x402-aegent-dev:latest
ghcr.io/${{ steps.meta.outputs.owner }}/x402-aegent-dev:${{ steps.meta.outputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
- name: Add host to known_hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
- uses: actions/checkout@v4
- name: Bootstrap host directory + sync managed files
env:
USER_AT_HOST: ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}
run: |
# Idempotent bootstrap. Creates /srv/x402 if missing; the data
# subdir holds sqlite WAL across deploys. Owned by the SSH user
# so subsequent scp doesn't need sudo.
ssh -o BatchMode=yes "$USER_AT_HOST" \
'mkdir -p /srv/x402/data && chmod 755 /srv/x402'
# Files in deploy/ are managed by CI — overwritten on every run.
# .env is NOT in deploy/, so this never touches the host's secrets.
scp -o BatchMode=yes \
deploy/docker-compose.yml \
deploy/deploy.sh \
deploy/x402.caddyfile \
"$USER_AT_HOST:/srv/x402/"
- name: Verify .env exists, install Caddy site, and deploy
env:
USER_AT_HOST: ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}
# Single SSH session that:
# 1. Refuses to proceed if /srv/x402/.env is missing — secrets are
# managed manually on the host, not by CI.
# 2. chmods deploy.sh.
# 3. Installs the Caddy snippet idempotently (only reloads Caddy
# if the file actually changed).
# 4. Runs the deploy script.
run: |
ssh -o BatchMode=yes "$USER_AT_HOST" 'bash -se' <<'REMOTE'
set -euo pipefail
if [ ! -f /srv/x402/.env ]; then
echo "ERROR: /srv/x402/.env is missing on the host." >&2
echo "Create it from deploy/env.template before deploying:" >&2
echo " scp deploy/env.template <user>@<host>:/srv/x402/.env" >&2
echo " ssh <user>@<host> 'chmod 600 /srv/x402/.env && \$EDITOR /srv/x402/.env'" >&2
exit 1
fi
chmod 700 /srv/x402/deploy.sh
if ! cmp -s /srv/x402/x402.caddyfile /etc/caddy/conf.d/x402.caddyfile 2>/dev/null; then
echo "Caddy snippet changed; installing and reloading."
cp /srv/x402/x402.caddyfile /etc/caddy/conf.d/x402.caddyfile
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy
fi
/srv/x402/deploy.sh
REMOTE