name: Build and Release on: push: tags: - v* env: REGISTRY_HOST: git.ivanch.me REGISTRY_USERNAME: ivanch IMAGE_NAME: ${{ env.REGISTRY_HOST }}/ivanch/vision-start IMAGE_TAG: latest jobs: build: runs-on: ubuntu-latest outputs: zip-file: ${{ steps.set-archive.outputs.archive-name }} steps: - name: Check out repository code uses: actions/checkout@v4 - name: Set version from tag env: RELEASE_TAG: ${{ gitea.ref_name }} run: | if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Expected a vX.Y.Z tag, got: $RELEASE_TAG" exit 1 fi VERSION="${RELEASE_TAG#v}" sed -i -e "s/\"version\": \"0\.0\.0\"/\"version\": \"$VERSION\"/" manifest.json - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' - name: Setup required tools run: sudo apt-get install zip unzip jq curl -y - name: Install JS dependencies run: npm ci - name: Run build run: | bash scripts/prepare_release.sh npm run build - name: Prepare Chrome Web Store package run: | mkdir vision-start cp -a dist/. vision-start/ cp -a extension vision-start/ cp manifest.json vision-start/ - name: Set archive name id: set-archive run: | SAFE_REF="${GITEA_REF_NAME//\//-}" ARCHIVE="vision-start-${SAFE_REF}.zip" if [ -n "${GITEA_ENV:-}" ]; then echo "ARCHIVE_NAME=${ARCHIVE}" >> "$GITEA_ENV"; fi if [ -n "${GITHUB_ENV:-}" ]; then echo "ARCHIVE_NAME=${ARCHIVE}" >> "$GITHUB_ENV"; fi if [ -n "${GITEA_OUTPUT:-}" ]; then echo "archive-name=${ARCHIVE}" >> "$GITEA_OUTPUT"; fi if [ -n "${GITHUB_OUTPUT:-}" ]; then echo "archive-name=${ARCHIVE}" >> "$GITHUB_OUTPUT"; fi env: GITEA_REF_NAME: ${{ gitea.ref_name }} - name: Create zip archive run: | cd vision-start zip -r -X "../${ARCHIVE_NAME}" . - name: Validate Chrome Web Store archive env: RELEASE_TAG: ${{ gitea.ref_name }} run: | set -euo pipefail unzip -t "$ARCHIVE_NAME" PACKAGE_DIR=$(mktemp -d) unzip -q "$ARCHIVE_NAME" -d "$PACKAGE_DIR" node --input-type=module - "$PACKAGE_DIR" "${RELEASE_TAG#v}" <<'NODE' import assert from 'node:assert/strict'; import { readFileSync, statSync } from 'node:fs'; import { resolve, sep } from 'node:path'; const [directory, version] = process.argv.slice(2); const manifest = JSON.parse(readFileSync(resolve(directory, 'manifest.json'), 'utf8')); assert.equal(manifest.manifest_version, 3); assert.equal(manifest.version, version); assert.match(version, /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/); const parts = version.split('.').map(Number); assert(parts.every(part => part <= 65535) && parts.some(part => part > 0), 'Invalid Chrome extension version'); assert(typeof manifest.name === 'string' && manifest.name.length > 0); assert(typeof manifest.description === 'string' && manifest.description.length <= 132); assert.equal(manifest.chrome_url_overrides.newtab, 'index.html'); assert(manifest.icons['128'], 'Missing store icon'); for (const file of [manifest.chrome_url_overrides.newtab, ...Object.values(manifest.icons)]) { const path = resolve(directory, file); assert(path.startsWith(resolve(directory) + sep), `Invalid package path: ${file}`); assert(statSync(path).isFile(), `Missing packaged file: ${file}`); } console.log('Chrome Web Store archive structure and manifest validated'); NODE - name: Upload artifact uses: actions/upload-artifact@v3 with: name: release-zip path: ${{ env.ARCHIVE_NAME }} virus-total-check: runs-on: ubuntu-latest needs: build outputs: analysis-url: ${{ steps.vt-check.outputs.analysis-url }} detection-ratio: ${{ steps.vt-check.outputs.detection-ratio }} steps: - name: Check out repository code uses: actions/checkout@v4 - name: Setup required tools run: sudo apt-get install jq curl -y - name: Download artifact uses: actions/download-artifact@v3 with: name: release-zip - name: Run VirusTotal check id: vt-check env: virustotal_apikey: ${{ secrets.VIRUSTOTAL_APIKEY }} VIRUS_TOTAL_FILE: ${{ needs.build.outputs.zip-file }} run: | # Run the VirusTotal check script and capture output in real-time set -o pipefail bash scripts/check_virustotal.sh 2>&1 | tee vt_output.txt # Extract analysis URL and detection ratio from output ANALYSIS_URL=$(grep "Analysis URL:" vt_output.txt | cut -d' ' -f3- || echo "Not available") DETECTION_RATIO=$(grep "Detection ratio:" vt_output.txt | cut -d' ' -f3- || echo "Not available") # Set outputs for next job echo "analysis-url=$ANALYSIS_URL" >> $GITEA_OUTPUT echo "detection-ratio=$DETECTION_RATIO" >> $GITEA_OUTPUT release: runs-on: ubuntu-latest needs: [build, virus-total-check, capture_screenshots] steps: - name: Check out repository code uses: actions/checkout@v4 - name: Download artifact uses: actions/download-artifact@v3 with: name: release-zip - name: Download screenshot artifacts uses: actions/download-artifact@v3 with: name: release-screenshots path: release-screenshots - name: Release zip uses: akkuman/gitea-release-action@v1 with: body: | This is the release for version ${{ gitea.ref_name }}. **Virus Total Analysis URL:** ${{ needs.virus-total-check.outputs.analysis-url }} **Virus Total Detection Ratio:** ${{ needs.virus-total-check.outputs.detection-ratio }} name: ${{ gitea.ref_name }} tag_name: ${{ gitea.ref_name }} files: | ${{ needs.build.outputs.zip-file }} release-screenshots/home.png release-screenshots/editing.png release-screenshots/configuration.png build_vision_start: name: Build Vision Start Image runs-on: ubuntu-amd64 needs: [build, virus-total-check] steps: - name: Check out repository uses: actions/checkout@v2 - name: Set version from tag env: RELEASE_TAG: ${{ gitea.ref_name }} run: | if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Expected a vX.Y.Z tag, got: $RELEASE_TAG" exit 1 fi VERSION="${RELEASE_TAG#v}" sed -i -e "s/\"version\": \"0\.0\.0\"/\"version\": \"$VERSION\"/" manifest.json - name: Log in to Container Registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" \ | docker login "${{ env.REGISTRY_HOST }}" \ -u "${{ env.REGISTRY_USERNAME }}" \ --password-stdin - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and Push Multi-Arch Image uses: docker/build-push-action@v6 with: push: true context: . platforms: linux/amd64,linux/arm64 tags: | ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} deploy_vision_start: name: Deploy Vision Start (production) runs-on: ubuntu-amd64 needs: build_vision_start steps: - name: Recreate Container uses: appleboy/ssh-action@v0.1.7 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} script: | cd ${{ secrets.PROD_DIR }} docker compose pull docker compose up -d --force-recreate capture_screenshots: name: Capture Vision Start Screenshots runs-on: ubuntu-amd64 needs: deploy_vision_start steps: - name: Check out repository code uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' - name: Install JS dependencies run: npm ci - name: Install Playwright Chromium run: npx playwright install --with-deps chromium - name: Capture release screenshots env: SCREENSHOT_BASE_URL: http://vision-start.ivanch.me run: npm run capture:screenshots - name: Upload release screenshots uses: actions/upload-artifact@v3 with: name: release-screenshots retention-days: 30 path: | screenshots/home.png screenshots/editing.png screenshots/configuration.png