Files
jobtrackingapp/.gitea/workflows/ci-deploy.yml
T
cesnimda 4f98195592
CI and Deploy / test (pull_request) Successful in 1m58s
CI and Deploy / deploy (pull_request) Has been skipped
ci: retry frontend build once on silent failure
npm run build (Terser minify + fork-ts-checker workers) has now died three
distinct ways on this runner in this session: a printed Terser minify error,
an explicit SIGSEGV, and a fully silent kill with zero output between
'Creating an optimized production build...' and the failure line (OOM/SIGSEGV
signature — process killed before it could flush an error). All three are the
same resource-starved-runner class as the npm ci and dotnet-install flakes
already retried elsewhere in this workflow. Retry once, matching that pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 19:30:15 +02:00

141 lines
5.7 KiB
YAML

name: CI and Deploy
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET (resilient)
shell: bash
# actions/setup-dotnet on this single self-hosted runner intermittently
# leaves a partial extraction in the shared tool-cache ("tar: Cannot open:
# File exists") or corrupts the SDK download. Install into a clean private
# dir via dotnet-install.sh and retry once on failure, mirroring the
# npm ci / NuGet retries elsewhere in this workflow.
run: |
install() {
curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
rm -rf "$HOME/.dotnet"
bash /tmp/dotnet-install.sh --channel 9.0 --install-dir "$HOME/.dotnet"
}
install || ( echo "dotnet install failed ($?) — retrying once..." && install )
echo "$HOME/.dotnet" >> "$GITHUB_PATH"
"$HOME/.dotnet/dotnet" --info
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: job-tracker-ui/package-lock.json
- name: Build backend
run: dotnet build JobTrackerApi/JobTrackerApi.csproj --configuration Release
- name: Test backend
run: dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --configuration Release --no-build
- name: Install frontend deps
working-directory: job-tracker-ui
env:
npm_config_audit: 'false'
npm_config_fund: 'false'
run: |
node -v
npm -v
# npm ci occasionally segfaults on the runner (SIGSEGV/139, a memory/native
# flake). Retry once with a clean node_modules before failing the job.
npm ci --no-audit --no-fund \
|| ( echo "npm ci failed ($?) — cleaning node_modules and retrying once..." \
&& rm -rf node_modules \
&& npm ci --no-audit --no-fund )
- name: Test frontend
working-directory: job-tracker-ui
# Run the WHOLE suite. Never whitelist test files here again: the previous
# whitelist silently skipped new suites and let two regressions reach main.
run: npm test -- --watchAll=false --runInBand
- name: Build frontend
working-directory: job-tracker-ui
env:
CI: 'false'
GENERATE_SOURCEMAP: 'false'
NODE_OPTIONS: --max-old-space-size=4096
# CRA's build (Terser minify + fork-ts-checker workers) has repeatedly died silently on
# this runner with no error output (OOM/SIGSEGV signature — same resource-starved-runner
# class as the npm ci and dotnet-install flakes elsewhere in this workflow). Retry once.
run: |
npm run build \
|| ( echo "Frontend build failed ($?) — retrying once..." && npm run build )
deploy:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Run remote deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
command_timeout: 40m
script: |
set -euo pipefail
if [ ! -d /opt/job-tracker/app/.git ]; then
echo "Expected git checkout at /opt/job-tracker/app but .git was not found."
exit 1
fi
cd /opt/job-tracker/app
if ! git fetch --all --prune; then
echo "git fetch failed on server. Check remote auth/URL for /opt/job-tracker/app."
exit 1
fi
if ! git rev-parse --verify --quiet ${{ github.sha }} >/dev/null; then
echo "Commit ${{ github.sha }} is not available in the server checkout after fetch."
exit 1
fi
git reset --hard ${{ github.sha }}
git clean -fd
chmod +x deploy/deploy.sh
APP_VERSION=${{ github.run_number }} \
APP_COMMIT_SHA=${{ github.sha }} \
APP_BUILD_STAMP="$(date -u +'%Y-%m-%d %H:%M UTC')" \
./deploy/deploy.sh
docker compose ps
AI_CONTAINER_ID="$(docker compose ps -q ai-service)"
if [ -z "$AI_CONTAINER_ID" ]; then
echo "AI service container id could not be resolved after deploy. Continuing because AI is not a deploy gate for the core app."
else
ATTEMPTS=90
SLEEP_SECS=2
i=1
while [ "$i" -le "$ATTEMPTS" ]; do
HEALTH_STATUS="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$AI_CONTAINER_ID" 2>/dev/null || echo unknown)"
if [ "$HEALTH_STATUS" = "healthy" ]; then
break
fi
if [ "$HEALTH_STATUS" = "unhealthy" ]; then
echo "AI service became unhealthy during deploy readiness wait. Continuing because AI is not a deploy gate for the core app."
docker compose logs --tail=200 ai-service || true
break
fi
sleep "$SLEEP_SECS"
i=$((i + 1))
done
if [ "${HEALTH_STATUS:-unknown}" != "healthy" ]; then
echo "AI service did not become healthy within $((ATTEMPTS * SLEEP_SECS)) seconds. Final status: ${HEALTH_STATUS:-unknown}. Continuing because AI is not a deploy gate for the core app."
docker compose ps
docker compose logs --tail=200 ai-service || true
fi
fi