@@ -101,7 +101,7 @@ jobs:
101101 needs : build-and-push
102102 environment :
103103 name : staging
104- url : http ://${{ secrets.STAGING_HOST }}:8080
104+ url : https ://agronom.fastergo.uz
105105 if : >-
106106 github.ref == 'refs/heads/master' ||
107107 startsWith(github.ref, 'refs/tags/v') ||
@@ -142,6 +142,17 @@ jobs:
142142 ssh-keyscan -H "${SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
143143 echo "SSH configured for ${SSH_HOST}"
144144
145+ - name : Copy nginx config to staging server
146+ env :
147+ SSH_HOST : ${{ secrets.STAGING_HOST }}
148+ SSH_USER : ${{ secrets.STAGING_USER }}
149+ run : |
150+ ssh -i ~/.ssh/deploy_key "${SSH_USER}@${SSH_HOST}" \
151+ mkdir -p /opt/aqlliagronom/staging/nginx
152+ scp -i ~/.ssh/deploy_key \
153+ nginx/conf.d/staging.conf \
154+ "${SSH_USER}@${SSH_HOST}:/opt/aqlliagronom/staging/nginx/app.conf"
155+
145156 - name : Deploy to staging via SSH
146157 env :
147158 SSH_HOST : ${{ secrets.STAGING_HOST }}
@@ -289,7 +300,7 @@ jobs:
289300 -e "Qdrant__Host=\${QDRANT_CONTAINER}" \
290301 -e "Qdrant__ApiKey=\${QDRANT_KEY}" \
291302 -e "FileStorage__BasePath=/app/uploads" \
292- -e "FileStorage__BaseUrl=http ://\${SSH_HOST}:8080 /uploads" \
303+ -e "FileStorage__BaseUrl=https ://agronom.fastergo.uz /uploads" \
293304 -v /opt/aqlliagronom/staging/logs:/app/logs \
294305 -v staging_uploads_data:/app/uploads \
295306 "\${IMAGE_FULL}"
@@ -311,28 +322,75 @@ jobs:
311322 sleep 5
312323 done
313324
314- # ── Nginx / SSL — skipped for staging (no domain, accessed via IP:8080) ──
315- echo "==> Staging accessible at http://\$(hostname -I | awk '{print \$1}'):8080"
325+ # ── Nginx + Certbot (SSL) ─────────────────────────────────────────────
326+ NGINX_DOMAIN="agronom.fastergo.uz"
327+ NGINX_CONF_DIR="/opt/aqlliagronom/staging/nginx"
328+
329+ docker volume create certbot_staging_certs 2>/dev/null || true
330+ docker volume create certbot_staging_www 2>/dev/null || true
331+
332+ # Generate temporary self-signed cert on first deploy so nginx can start
333+ if ! docker run --rm -v certbot_staging_certs:/etc/letsencrypt alpine \
334+ test -f "/etc/letsencrypt/live/\${NGINX_DOMAIN}/fullchain.pem" 2>/dev/null; then
335+ echo "==> Generating temporary self-signed cert for \${NGINX_DOMAIN}..."
336+ docker run --rm -v certbot_staging_certs:/etc/letsencrypt alpine sh -c \
337+ "apk add --no-cache openssl >/dev/null 2>&1 && \
338+ mkdir -p /etc/letsencrypt/live/\${NGINX_DOMAIN} && \
339+ openssl req -x509 -nodes -newkey rsa:2048 \
340+ -keyout /etc/letsencrypt/live/\${NGINX_DOMAIN}/privkey.pem \
341+ -out /etc/letsencrypt/live/\${NGINX_DOMAIN}/fullchain.pem \
342+ -days 1 -subj /CN=\${NGINX_DOMAIN} 2>/dev/null"
343+ fi
344+
345+ if docker ps --format '{{.Names}}' | grep -q '^aqlliagronom-nginx\$'; then
346+ echo "==> Reloading nginx config..."
347+ docker exec aqlliagronom-nginx nginx -s reload || true
348+ else
349+ docker stop aqlliagronom-nginx 2>/dev/null || true
350+ docker rm aqlliagronom-nginx 2>/dev/null || true
351+ echo "==> Starting nginx..."
352+ docker run -d \
353+ --name aqlliagronom-nginx \
354+ --restart unless-stopped \
355+ --network aqlliagronom-network \
356+ -p 80:80 -p 443:443 \
357+ -v "\${NGINX_CONF_DIR}:/etc/nginx/conf.d:ro" \
358+ -v certbot_staging_certs:/etc/letsencrypt:ro \
359+ -v certbot_staging_www:/var/www/certbot:ro \
360+ -v staging_uploads_data:/app/uploads:ro \
361+ nginx:1.28-alpine
362+ fi
363+
364+ if [ -n "\${CERTBOT_EMAIL}" ]; then
365+ echo "==> Running certbot for \${NGINX_DOMAIN}..."
366+ docker run --rm \
367+ -v certbot_staging_certs:/etc/letsencrypt \
368+ -v certbot_staging_www:/var/www/certbot \
369+ certbot/certbot certonly \
370+ --webroot -w /var/www/certbot \
371+ -d "\${NGINX_DOMAIN}" \
372+ --non-interactive --agree-tos \
373+ -m "\${CERTBOT_EMAIL}" \
374+ --keep-until-expiring 2>&1 | tail -5 || true
375+ docker exec aqlliagronom-nginx nginx -s reload 2>/dev/null || true
376+ fi
377+ echo "==> https://\${NGINX_DOMAIN} ready"
316378
317379 docker image prune -f || true
318380 ENDSSH
319381
320382 # ── 3. Smoke Test Staging ─────────────────────────────────────────────────────
321- # Tests against http://<host>:8080 (direct Docker port) — no nginx/TLS needed.
322- # Switch to https://staging-api.aqlliagronom.uz once a reverse proxy is set up.
323383 smoke-test-staging :
324384 name : Smoke Test — Staging
325385 runs-on : ubuntu-latest
326386 needs : deploy-staging
327387
328388 steps :
329389 - name : Health check with retries
330- env :
331- STAGING_HOST : ${{ secrets.STAGING_HOST }}
332390 run : |
333- BASE="http ://${STAGING_HOST}:8080 "
391+ BASE="https ://agronom.fastergo.uz "
334392 for i in $(seq 1 6); do
335- STATUS=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 "${BASE}/health/live")
393+ STATUS=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 10 "${BASE}/health/live")
336394 if [ "$STATUS" = "200" ]; then
337395 echo "Health check passed (HTTP $STATUS)"
338396 break
@@ -346,11 +404,9 @@ jobs:
346404 done
347405
348406 - name : API smoke test
349- env :
350- STAGING_HOST : ${{ secrets.STAGING_HOST }}
351407 run : |
352- BASE="http ://${STAGING_HOST}:8080 "
353- STATUS=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 \
408+ BASE="https ://agronom.fastergo.uz "
409+ STATUS=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 10 \
354410 -X POST "${BASE}/api/v1/auth/register" \
355411 -H "Content-Type: application/json" \
356412 -d '{"fullName":"","phone":"","password":""}')
0 commit comments