-
Notifications
You must be signed in to change notification settings - Fork 0
242 lines (207 loc) Β· 8.29 KB
/
Copy pathci.yml
File metadata and controls
242 lines (207 loc) Β· 8.29 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
name: TradeOffStack CI/CD Pipeline
on:
push:
branches: [ "main", "feature/postman-tests" ]
pull_request:
branches: [ "main", "feature/postman-tests" ]
jobs:
build-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: π₯ Checkout repository
uses: actions/checkout@v4
- name: βοΈ Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: π¦ Restore backend dependencies
run: dotnet restore TradeOffStackAPI.sln
- name: π¨ Build API
run: dotnet build TradeOffStackAPI.sln --no-restore --configuration Release
- name: π§ͺ Run C# Backend Tests
run: dotnet test TradeOffStackAPI.sln --no-build --configuration Release --verbosity normal
- name: π Start Services (API & Database) via Docker Compose
run: |
echo "POSTGRES_USER=tradeoff_admin" >> .env
echo "POSTGRES_PASSWORD=Tr@de0ff_Secure!2026_Db#X9" >> .env
echo "POSTGRES_DB=tradeoffstack" >> .env
echo "POSTGRES_PORT=5432" >> .env
echo "JWT_SECRET_KEY=Tr@de0ff_Super_Secret_Key_For_JWT_Auth_2026!" >> .env
docker compose up -d --build
- name: π©Ί Wait for API to be healthy
run: |
echo "Waiting for API to respond at http://localhost:5000/api/equipment..."
for i in {1..30}; do
if curl -s http://localhost:5000/api/equipment > /dev/null; then
echo "API is up and running!"
exit 0
fi
sleep 2
done
echo "API failed to start in time."
docker compose logs
exit 1
- name: π’ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: π¦ Install Frontend Dependencies
run: |
cd frontend
npm ci
- name: π Install Playwright Browsers
run: |
cd frontend
npx playwright install --with-deps chromium
- name: β‘ Start Frontend Vite Server
run: |
cd frontend
npm run dev &
echo "Waiting for Vite frontend to be responsive..."
for i in {1..15}; do
if curl -s http://localhost:5173 > /dev/null; then
echo "Vite is up!"
exit 0
fi
sleep 1
done
- name: π§ͺ Run Playwright E2E UI Tests
run: |
cd frontend
node ui_test.js
- name: π§ͺ Run Playwright E2E Global CRUD Lifecycle Tests
run: |
cd frontend
node ui_global_test.js
- name: π€ Upload E2E Screenshots
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-screenshots
path: frontend/screenshots/
# ==========================================
# DOCKER PUBLISH TO GHCR (Only on push to main or feature/postman-tests)
# ==========================================
- name: π Log in to GitHub Container Registry
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feature/postman-tests')
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: π³ Build and Push Docker Images to GHCR
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feature/postman-tests')
run: |
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "Building and Pushing API Image..."
docker build -t ghcr.io/$OWNER_LC/tradeoffstack-api:latest -f Dockerfile .
docker push ghcr.io/$OWNER_LC/tradeoffstack-api:latest
echo "Building and Pushing Frontend Image..."
docker build -t ghcr.io/$OWNER_LC/tradeoffstack-frontend:latest -f frontend/Dockerfile frontend/
docker push ghcr.io/$OWNER_LC/tradeoffstack-frontend:latest
# ==========================================
# π CONTINUOUS DEPLOYMENT (CD) TO HOSTINGER VPS
# ==========================================
- name: π Diagnose VPS Connectivity
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feature/postman-tests')
run: |
echo "Testing connectivity to VPS..."
echo "Target: ${{ secrets.VPS_IP }}:22"
timeout 15 bash -c "cat < /dev/null > /dev/tcp/${{ secrets.VPS_IP }}/22" 2>&1 && echo "β
Port 22 is OPEN" || echo "β Port 22 is UNREACHABLE"
- name: π Deploy to VPS (All-in-One SSH)
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feature/postman-tests')
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_IP }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
timeout: 60s
command_timeout: 10m
script: |
# Create deployment directory
mkdir -p /app/tradeoffstack
cd /app/tradeoffstack
# Write Caddyfile directly (no SCP needed)
cat > Caddyfile << 'CADDY_EOF'
tradeoffstack.dordorapps.tech {
handle /api/* {
reverse_proxy api:8080
}
handle {
reverse_proxy frontend:80
}
encode gzip zstd
}
CADDY_EOF
# Write docker-compose.prod.yml directly (no SCP needed)
cat > docker-compose.prod.yml << 'COMPOSE_EOF'
version: '3.8'
services:
db:
image: postgres:17-alpine
container_name: tradeoffstack-db-prod
environment:
POSTGRES_USER: ${POSTGRES_USER:-tradeoff_admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: tradeoffstack
volumes:
- pg_prod_data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tradeoff_admin} -d tradeoffstack"]
interval: 5s
timeout: 5s
retries: 5
api:
image: ghcr.io/dordormin/tradeoffstack-api:latest
container_name: tradeoffstack-api-prod
depends_on:
db:
condition: service_healthy
environment:
ConnectionStrings__CoreConnection: Host=db;Database=tradeoffstack_core;Username=${POSTGRES_USER:-tradeoff_admin};Password=${POSTGRES_PASSWORD}
ConnectionStrings__AssetConnection: Host=db;Database=tradeoffstack_assetportal;Username=${POSTGRES_USER:-tradeoff_admin};Password=${POSTGRES_PASSWORD}
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
ASPNETCORE_ENVIRONMENT: Production
restart: always
frontend:
image: ghcr.io/dordormin/tradeoffstack-frontend:latest
container_name: tradeoffstack-frontend-prod
restart: always
caddy:
image: caddy:2-alpine
container_name: tradeoffstack-proxy-prod
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- api
- frontend
restart: always
volumes:
pg_prod_data:
caddy_data:
caddy_config:
COMPOSE_EOF
# Inject production environment variables
cat > .env << ENV_EOF
POSTGRES_USER=tradeoff_admin
POSTGRES_PASSWORD=${{ secrets.DB_PASSWORD }}
JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }}
ENV_EOF
# Login to GHCR to pull private images
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
# Pull latest images and update containers
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans
# Cleanup unused images to keep disk space free
docker image prune -f