Repository navigation
159 lines (139 loc) · 5.53 KB
/
Copy pathdeploy.yml
File metadata and controls
159 lines (139 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
name: Build and Deploy
# Manual trigger. Builds the server image (API + baked-in SPA), generates the
# docker-compose via `aspire publish`, then SSHes to the server and rolls the stack.
# Modelled on Asictron/NightTax.
on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAMESPACE: ${{ github.repository_owner }}
SERVER_HOST: ${{ vars.SERVER_HOST }}
SERVER_USER: ${{ vars.SERVER_USER }}
REMOTE_DIR: ${{ vars.BUSINESSPORTAL_REMOTE_DIR }}
PUBLIC_HOST: ${{ vars.BUSINESSPORTAL_PUBLIC_HOST }}
jobs:
build-and-push:
name: Build & push server image (API + SPA)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
cache: true
cache-dependency-path: '**/*.csproj'
# The Web project's publish target runs `npm ci && npm run build` to stage the
# React app into wwwroot, so the runner needs Node.
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: src/Web/ClientApp/package-lock.json
- name: Compute image tag
id: tag
run: echo "tag=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & push business-portal-server (bakes in the SPA)
run: |
dotnet publish src/Web/Web.csproj \
-c Release \
/t:PublishContainer \
/p:ContainerRegistry=${{ env.REGISTRY }} \
/p:ContainerRepository=${{ env.IMAGE_NAMESPACE }}/business-portal-server \
/p:ContainerImageTags='"${{ steps.tag.outputs.tag }};latest"'
generate-compose:
name: Generate docker-compose
runs-on: ubuntu-latest
needs: build-and-push
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
cache: true
cache-dependency-path: '**/*.csproj'
- name: Install Aspire CLI
run: dotnet tool install -g Aspire.Cli
- name: Generate publish artifacts
env:
PublicHost: ${{ env.PUBLIC_HOST }}
run: aspire publish --project src/AppHost/AppHost.csproj --output-path ./publish
- name: Write .env
run: |
# 2Captcha / Renewtron / email credentials are entered from the admin
# Settings UI and persisted on the businessportal-data volume — no longer
# passed at deploy time.
cat > publish/.env <<EOF
BUSINESSPORTAL_SERVER_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/business-portal-server:${{ needs.build-and-push.outputs.image_tag }}
POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
SIGN_IN_LINK_SIGNING_KEY=${{ secrets.SIGN_IN_LINK_SIGNING_KEY }}
RENEWTRON_PARTNER_API_KEY=${{ secrets.RENEWTRON_PARTNER_API_KEY }}
RENEWTRON_SYNC_FROM=${{ vars.RENEWTRON_SYNC_FROM }}
SEED_ADMIN_EMAIL=${{ vars.BUSINESSPORTAL_ADMIN_EMAIL }}
SEED_ADMIN_PASSWORD=${{ secrets.BUSINESSPORTAL_ADMIN_PASSWORD }}
EOF
- uses: actions/upload-artifact@v4
with:
name: compose
path: publish/
include-hidden-files: true
deploy:
name: Deploy to server
runs-on: ubuntu-latest
needs: [build-and-push, generate-compose]
steps:
- uses: actions/download-artifact@v4
with:
name: compose
path: publish/
# Strip stray whitespace/newlines a repo Variable may have been pasted with.
- name: Sanitize deploy vars
run: |
{
echo "SERVER_HOST=$(printf '%s' "$SERVER_HOST" | tr -d '[:space:]')"
echo "SERVER_USER=$(printf '%s' "$SERVER_USER" | tr -d '[:space:]')"
echo "REMOTE_DIR=$(printf '%s' "$REMOTE_DIR" | tr -d '[:space:]')"
} >> "$GITHUB_ENV"
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ env.SERVER_HOST }}" >> ~/.ssh/known_hosts
- name: Bootstrap server (Docker + caddy network if missing)
run: |
ssh -i ~/.ssh/deploy_key "${{ env.SERVER_USER }}@${{ env.SERVER_HOST }}" bash <<'EOF'
set -e
if ! command -v docker >/dev/null 2>&1; then
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
fi
sudo docker network inspect caddy >/dev/null 2>&1 || sudo docker network create caddy
mkdir -p "${{ env.REMOTE_DIR }}"
EOF
- name: Copy compose files
run: |
scp -i ~/.ssh/deploy_key \
publish/docker-compose.yaml \
publish/.env \
"${{ env.SERVER_USER }}@${{ env.SERVER_HOST }}:${{ env.REMOTE_DIR }}/"
- name: Deploy
run: |
ssh -i ~/.ssh/deploy_key "${{ env.SERVER_USER }}@${{ env.SERVER_HOST }}" bash <<EOF
set -e
cd "${{ env.REMOTE_DIR }}"
echo "${{ secrets.GITHUB_TOKEN }}" | sudo docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
sudo docker compose pull
sudo docker compose up -d --remove-orphans
sudo docker image prune -f
EOF