|
5 | 5 | types: [opened, synchronize, reopened, closed] |
6 | 6 |
|
7 | 7 | jobs: |
8 | | - deploy-preview: |
9 | | - if: github.event.action != 'closed' |
10 | | - runs-on: ubuntu-latest |
11 | | - steps: |
12 | | - - uses: actions/checkout@v4 |
13 | | - |
14 | | - - name: Deploy preview environment |
15 | | - uses: appleboy/ssh-action@v1 |
16 | | - with: |
17 | | - host: ${{ secrets.SERVER_HOST }} |
18 | | - username: ${{ secrets.SERVER_USER }} |
19 | | - key: ${{ secrets.SERVER_SSH_KEY }} |
20 | | - script: | |
21 | | - set -e |
22 | | - APP_NAME="${{ github.event.repository.name }}" |
23 | | - PR_NUMBER="${{ github.event.number }}" |
24 | | - PREVIEW_DOMAIN="pr-${PR_NUMBER}.preview.${{ secrets.PREVIEW_DOMAIN }}" |
25 | | - APP_DB=$(echo "${APP_NAME}" | tr '-' '_')_db |
26 | | - SCHEMA_NAME="pr_${PR_NUMBER}" |
27 | | -
|
28 | | - PREVIEW_DIR="/opt/apps/${APP_NAME}-pr-${PR_NUMBER}" |
29 | | -
|
30 | | - # Clone the repo into a dedicated preview directory (or pull if it exists) |
31 | | - if [ -d "${PREVIEW_DIR}" ]; then |
32 | | - cd "${PREVIEW_DIR}" |
33 | | - git fetch origin pull/${PR_NUMBER}/head:pr-${PR_NUMBER} --force |
34 | | - git checkout pr-${PR_NUMBER} |
35 | | - else |
36 | | - git clone /opt/apps/${APP_NAME} "${PREVIEW_DIR}" |
37 | | - cd "${PREVIEW_DIR}" |
38 | | - git fetch origin pull/${PR_NUMBER}/head:pr-${PR_NUMBER} --force |
39 | | - git checkout pr-${PR_NUMBER} |
40 | | - fi |
41 | | -
|
42 | | - # Create preview schema in the app's database |
43 | | - docker compose -f /opt/platform/docker-compose.yml exec -T postgres \ |
44 | | - psql -U postgres -d ${APP_DB} -c "CREATE SCHEMA IF NOT EXISTS ${SCHEMA_NAME}" |
45 | | -
|
46 | | - # Copy production .env as base for preview config |
47 | | - if [ ! -f /opt/apps/${APP_NAME}/deploy/.env ]; then |
48 | | - echo "ERROR: /opt/apps/${APP_NAME}/deploy/.env not found. Deploy production first." |
49 | | - exit 1 |
50 | | - fi |
51 | | -
|
52 | | - # Generate preview .env with schema-aware DATABASE_URL |
53 | | - sed "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://postgres:$(grep POSTGRES_PASSWORD /opt/platform/.env | cut -d= -f2)@postgres:5432/${APP_DB}?options=-csearch_path%3D${SCHEMA_NAME}|" \ |
54 | | - /opt/apps/${APP_NAME}/deploy/.env > deploy/.env.pr-${PR_NUMBER} |
55 | | -
|
56 | | - # Build and start the app container (skip optional services like celery-worker) |
57 | | - docker compose -p ${APP_NAME}-pr-${PR_NUMBER} -f deploy/docker-compose.yml \ |
58 | | - --env-file deploy/.env.pr-${PR_NUMBER} up -d --build app |
59 | | -
|
60 | | - # Run database migrations against the preview schema |
61 | | - docker compose -p ${APP_NAME}-pr-${PR_NUMBER} -f deploy/docker-compose.yml \ |
62 | | - exec -T app alembic -c app/alembic.ini upgrade head |
63 | | -
|
64 | | - # Generate Caddyfile for preview domain |
65 | | - cat > /opt/platform/caddy-apps/${APP_NAME}-pr-${PR_NUMBER}.caddy <<CADDYEOF |
66 | | - ${PREVIEW_DOMAIN} { |
67 | | - import security_headers |
68 | | - reverse_proxy ${APP_NAME}-pr-${PR_NUMBER}-app-1:8000 |
69 | | - } |
70 | | - CADDYEOF |
71 | | -
|
72 | | - # Reload Caddy to pick up new route |
73 | | - docker compose -f /opt/platform/docker-compose.yml exec -T caddy \ |
74 | | - caddy reload --config /etc/caddy/Caddyfile |
75 | | -
|
76 | | - - name: Post preview URL |
77 | | - uses: actions/github-script@v7 |
78 | | - with: |
79 | | - script: | |
80 | | - const previewDomain = `pr-${{ github.event.number }}.preview.${{ secrets.PREVIEW_DOMAIN }}`; |
81 | | - const body = `Preview deployed: https://${previewDomain}\n\nHealth check: https://${previewDomain}/health`; |
82 | | - const { data: comments } = await github.rest.issues.listComments({ |
83 | | - owner: context.repo.owner, |
84 | | - repo: context.repo.repo, |
85 | | - issue_number: context.issue.number, |
86 | | - }); |
87 | | - const existing = comments.find(c => c.body.startsWith('Preview deployed:')); |
88 | | - if (existing) { |
89 | | - await github.rest.issues.updateComment({ |
90 | | - owner: context.repo.owner, |
91 | | - repo: context.repo.repo, |
92 | | - comment_id: existing.id, |
93 | | - body, |
94 | | - }); |
95 | | - } else { |
96 | | - await github.rest.issues.createComment({ |
97 | | - owner: context.repo.owner, |
98 | | - repo: context.repo.repo, |
99 | | - issue_number: context.issue.number, |
100 | | - body, |
101 | | - }); |
102 | | - } |
103 | | -
|
104 | | - cleanup-preview: |
105 | | - if: github.event.action == 'closed' |
106 | | - runs-on: ubuntu-latest |
107 | | - steps: |
108 | | - - name: Clean up preview environment |
109 | | - uses: appleboy/ssh-action@v1 |
110 | | - with: |
111 | | - host: ${{ secrets.SERVER_HOST }} |
112 | | - username: ${{ secrets.SERVER_USER }} |
113 | | - key: ${{ secrets.SERVER_SSH_KEY }} |
114 | | - script: | |
115 | | - set -e |
116 | | - APP_NAME="${{ github.event.repository.name }}" |
117 | | - PR_NUMBER="${{ github.event.number }}" |
118 | | - APP_DB=$(echo "${APP_NAME}" | tr '-' '_')_db |
119 | | - SCHEMA_NAME="pr_${PR_NUMBER}" |
120 | | -
|
121 | | - PREVIEW_DIR="/opt/apps/${APP_NAME}-pr-${PR_NUMBER}" |
122 | | -
|
123 | | - # Stop and remove preview containers |
124 | | - if [ -d "${PREVIEW_DIR}" ]; then |
125 | | - cd "${PREVIEW_DIR}" |
126 | | - docker compose -p ${APP_NAME}-pr-${PR_NUMBER} -f deploy/docker-compose.yml down --rmi local || true |
127 | | - fi |
128 | | -
|
129 | | - # Drop the preview schema |
130 | | - docker compose -f /opt/platform/docker-compose.yml exec -T postgres \ |
131 | | - psql -U postgres -d ${APP_DB} -c "DROP SCHEMA IF EXISTS ${SCHEMA_NAME} CASCADE" |
132 | | -
|
133 | | - # Remove preview Caddyfile |
134 | | - rm -f /opt/platform/caddy-apps/${APP_NAME}-pr-${PR_NUMBER}.caddy |
135 | | -
|
136 | | - # Reload Caddy |
137 | | - docker compose -f /opt/platform/docker-compose.yml exec -T caddy \ |
138 | | - caddy reload --config /etc/caddy/Caddyfile |
139 | | -
|
140 | | - # Remove the preview directory entirely |
141 | | - rm -rf "${PREVIEW_DIR}" |
| 8 | + preview: |
| 9 | + uses: towlion/.github/.github/workflows/preview.yml@main |
| 10 | + with: |
| 11 | + caddyfile-template: | |
| 12 | + __APP_DOMAIN__ { |
| 13 | + import security_headers |
| 14 | + reverse_proxy __APP_NAME__-app-1:8000 |
| 15 | + } |
| 16 | + secrets: |
| 17 | + SERVER_HOST: ${{ secrets.SERVER_HOST }} |
| 18 | + SERVER_USER: ${{ secrets.SERVER_USER }} |
| 19 | + SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }} |
| 20 | + PREVIEW_DOMAIN: ${{ secrets.PREVIEW_DOMAIN }} |
0 commit comments