diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..934bc45 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI/CD + +on: + push: + branches: [dev, main] + pull_request: + branches: [main] + +env: + REGISTRY: gitea.jeffemmett.com + IMAGE: gitea.jeffemmett.com/jeffemmett/canvas-website + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1 + git clone --depth 1 --branch ${{ github.ref_name }} http://token:${{ github.token }}@server:3000/${{ github.repository }}.git . + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Type check + run: npx tsc --noEmit + + - name: Unit tests + run: npx vitest run + + - name: Worker tests + run: npx vitest run --config vitest.worker.config.ts + + build-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1 + git clone --depth 1 --branch ${{ github.ref_name }} http://token:${{ github.token }}@server:3000/${{ github.repository }}.git . + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Build + run: npm run build + env: + NODE_OPTIONS: "--max-old-space-size=4096" + + deploy: + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + needs: [test, build-check] + runs-on: ubuntu-latest + container: + image: docker:24-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + steps: + - name: Setup tools + run: apk add --no-cache git openssh-client curl + + - name: Checkout + run: git clone --depth 1 --branch ${{ github.ref_name }} http://token:${{ github.token }}@server:3000/${{ github.repository }}.git . + + - name: Set image tag + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8) + echo "IMAGE_TAG=${SHORT_SHA}" >> $GITHUB_ENV + echo "Building image tag: ${SHORT_SHA}" + + - name: Build image + run: docker build -t ${{ env.IMAGE }}:${{ env.IMAGE_TAG }} -t ${{ env.IMAGE }}:latest . + + - name: Push to registry + run: | + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.REGISTRY_USER }} --password-stdin + docker push ${{ env.IMAGE }}:${{ env.IMAGE_TAG }} + docker push ${{ env.IMAGE }}:latest + + - name: Deploy to server + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_SSH_KEY }}" | base64 -d > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key root@${{ secrets.DEPLOY_HOST }} " + cd /opt/websites/canvas-website-staging + cat .last-deployed-tag 2>/dev/null > .rollback-tag || true + echo '${{ env.IMAGE_TAG }}' > .last-deployed-tag + docker pull ${{ env.IMAGE }}:${{ env.IMAGE_TAG }} + IMAGE_TAG=${{ env.IMAGE_TAG }} docker compose up -d --no-build + " + + - name: Smoke test + run: | + sleep 10 + HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" --max-time 15 https://jeffemmett.com/ 2>/dev/null || echo "000") + if [ "$HTTP_CODE" != "200" ]; then + echo "Smoke test failed (HTTP $HTTP_CODE) — rolling back" + ROLLBACK_TAG=$(ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key root@${{ secrets.DEPLOY_HOST }} "cat /opt/websites/canvas-website-staging/.rollback-tag 2>/dev/null") + if [ -n "$ROLLBACK_TAG" ]; then + ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key root@${{ secrets.DEPLOY_HOST }} \ + "cd /opt/websites/canvas-website-staging && IMAGE_TAG=$ROLLBACK_TAG docker compose up -d --no-build" + echo "Rolled back to $ROLLBACK_TAG" + fi + exit 1 + fi + echo "Smoke test passed (HTTP $HTTP_CODE)"