name: Build App on: push: branches: [main] paths: - "app/**" - ".gitea/workflows/build-app.yml" pull_request: branches: [main] paths: - "app/**" workflow_dispatch: env: GITEA_URL: ${{ gitea.server_url }} REPO: ${{ gitea.repository }} jobs: build-linux: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.VERSION }} steps: - name: Install Node.js 22 run: | NEED_INSTALL=false if command -v node >/dev/null 2>&1; then NODE_MAJOR=$(node --version | sed 's/v\([0-9]*\).*/\1/') OLD_NODE_DIR=$(dirname "$(which node)") echo "Found Node.js $(node --version) at $(which node) (major: ${NODE_MAJOR})" if [ "$NODE_MAJOR" -lt 22 ]; then echo "Node.js ${NODE_MAJOR} is too old, removing before installing 22..." sudo rm -f "${OLD_NODE_DIR}/node" "${OLD_NODE_DIR}/npm" "${OLD_NODE_DIR}/npx" "${OLD_NODE_DIR}/corepack" hash -r NEED_INSTALL=true fi else echo "Node.js not found, installing 22..." NEED_INSTALL=true fi if [ "$NEED_INSTALL" = true ]; then curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs hash -r fi echo "Node.js at: $(which node)" node --version npm --version - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Compute version id: version run: | COMMIT_COUNT=$(git rev-list --count HEAD) VERSION="0.1.${COMMIT_COUNT}" echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT echo "Computed version: ${VERSION}" - name: Set app version run: | VERSION="${{ steps.version.outputs.VERSION }}" sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" app/src-tauri/tauri.conf.json sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" app/package.json sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" app/src-tauri/Cargo.toml echo "Patched version to ${VERSION}" - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y \ libgtk-3-dev \ libwebkit2gtk-4.1-dev \ libayatana-appindicator3-dev \ librsvg2-dev \ libsoup-3.0-dev \ libssl-dev \ libxdo-dev \ patchelf \ pkg-config \ build-essential \ curl \ wget \ file \ xdg-utils - name: Install Rust stable run: | if command -v rustup >/dev/null 2>&1; then echo "Rust already installed: $(rustc --version)" rustup update stable rustup default stable else curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable fi export PATH="$HOME/.cargo/bin:$PATH" rustc --version cargo --version - name: Install frontend dependencies working-directory: ./app run: | rm -rf node_modules package-lock.json npm install - name: Install Tauri CLI working-directory: ./app run: | export PATH="$HOME/.cargo/bin:$PATH" npx tauri --version || npm install @tauri-apps/cli - name: Build Tauri app working-directory: ./app run: | export PATH="$HOME/.cargo/bin:$PATH" npx tauri build - name: Collect artifacts run: | mkdir -p artifacts cp app/src-tauri/target/release/bundle/appimage/*.AppImage artifacts/ 2>/dev/null || true cp app/src-tauri/target/release/bundle/deb/*.deb artifacts/ 2>/dev/null || true cp app/src-tauri/target/release/bundle/rpm/*.rpm artifacts/ 2>/dev/null || true ls -la artifacts/ - name: Upload to Gitea release if: gitea.event_name == 'push' env: TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | TAG="v${{ steps.version.outputs.VERSION }}" # Create release curl -s -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\": \"${TAG}\", \"name\": \"Triple-C ${TAG} (Linux)\", \"body\": \"Automated build from commit ${{ gitea.sha }}\"}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases" > release.json RELEASE_ID=$(cat release.json | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*') echo "Release ID: ${RELEASE_ID}" # Upload each artifact for file in artifacts/*; do [ -f "$file" ] || continue filename=$(basename "$file") echo "Uploading ${filename}..." curl -s -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${file}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}" done build-macos: runs-on: macos-latest steps: - name: Install Node.js 22 run: | NEED_INSTALL=false if command -v node >/dev/null 2>&1; then NODE_MAJOR=$(node --version | sed 's/v\([0-9]*\).*/\1/') echo "Found Node.js $(node --version) (major: ${NODE_MAJOR})" if [ "$NODE_MAJOR" -lt 22 ]; then echo "Node.js ${NODE_MAJOR} is too old, upgrading to 22..." NEED_INSTALL=true fi else echo "Node.js not found, installing 22..." NEED_INSTALL=true fi if [ "$NEED_INSTALL" = true ]; then brew install node@22 brew link --overwrite node@22 fi node --version npm --version - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Compute version id: version run: | COMMIT_COUNT=$(git rev-list --count HEAD) VERSION="0.1.${COMMIT_COUNT}" echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT echo "Computed version: ${VERSION}" - name: Set app version run: | VERSION="${{ steps.version.outputs.VERSION }}" sed -i '' "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" app/src-tauri/tauri.conf.json sed -i '' "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" app/package.json sed -i '' "s/^version = \".*\"/version = \"${VERSION}\"/" app/src-tauri/Cargo.toml echo "Patched version to ${VERSION}" - name: Install Rust stable run: | if command -v rustup >/dev/null 2>&1; then echo "Rust already installed: $(rustc --version)" rustup update stable rustup default stable else curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable fi export PATH="$HOME/.cargo/bin:$PATH" rustup target add aarch64-apple-darwin x86_64-apple-darwin rustc --version cargo --version - name: Install frontend dependencies working-directory: ./app run: | rm -rf node_modules npm install - name: Install Tauri CLI working-directory: ./app run: | export PATH="$HOME/.cargo/bin:$PATH" npx tauri --version || npm install @tauri-apps/cli - name: Build Tauri app (universal) working-directory: ./app run: | export PATH="$HOME/.cargo/bin:$PATH" npx tauri build --target universal-apple-darwin - name: Collect artifacts run: | mkdir -p artifacts cp app/src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg artifacts/ 2>/dev/null || true cp app/src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz artifacts/ 2>/dev/null || true ls -la artifacts/ - name: Upload to Gitea release if: gitea.event_name == 'push' env: TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | TAG="v${{ steps.version.outputs.VERSION }}-mac" # Create release curl -s -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\": \"${TAG}\", \"name\": \"Triple-C v${{ steps.version.outputs.VERSION }} (macOS)\", \"body\": \"Automated build from commit ${{ gitea.sha }}\"}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases" > release.json RELEASE_ID=$(cat release.json | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*') echo "Release ID: ${RELEASE_ID}" # Upload each artifact for file in artifacts/*; do [ -f "$file" ] || continue filename=$(basename "$file") echo "Uploading ${filename}..." curl -s -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${file}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}" done build-windows: runs-on: windows-latest defaults: run: shell: cmd steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Compute version id: version run: | for /f %%i in ('git rev-list --count HEAD') do set "COMMIT_COUNT=%%i" set "VERSION=0.1.%COMMIT_COUNT%" echo VERSION=%VERSION%>> %GITHUB_OUTPUT% echo Computed version: %VERSION% - name: Set app version shell: powershell run: | $version = "${{ steps.version.outputs.VERSION }}" (Get-Content app/src-tauri/tauri.conf.json) -replace '"version": ".*?"', "`"version`": `"$version`"" | Set-Content app/src-tauri/tauri.conf.json (Get-Content app/package.json) -replace '"version": ".*?"', "`"version`": `"$version`"" | Set-Content app/package.json (Get-Content app/src-tauri/Cargo.toml) -replace '^version = ".*?"', "version = `"$version`"" | Set-Content app/src-tauri/Cargo.toml Write-Host "Patched version to $version" - name: Install Rust stable run: | where rustup >nul 2>&1 && ( rustup update stable rustup default stable ) || ( curl -fSL -o rustup-init.exe https://win.rustup.rs/x86_64 rustup-init.exe -y --default-toolchain stable del rustup-init.exe ) - name: Install Node.js run: | where node >nul 2>&1 && ( node --version ) || ( curl -fSL -o node-install.msi "https://nodejs.org/dist/v22.14.0/node-v22.14.0-x64.msi" msiexec /i node-install.msi /quiet /norestart del node-install.msi ) - name: Verify tools run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" rustc --version cargo --version node --version npm --version - name: Install Tauri CLI via cargo run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" cargo install tauri-cli --version "^2" - name: Fix npm platform detection run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" npm config set os win32 npm config list - name: Install frontend dependencies working-directory: ./app run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" if exist node_modules rmdir /s /q node_modules npm ci - name: Build frontend working-directory: ./app run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" npm run build - name: Build Tauri app working-directory: ./app env: TAURI_CONFIG: "{\"build\":{\"beforeBuildCommand\":\"\"}}" run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" cargo tauri build - name: Collect artifacts run: | set "PATH=%USERPROFILE%\.cargo\bin;C:\Program Files\nodejs;%PATH%" mkdir artifacts copy app\src-tauri\target\release\bundle\msi\*.msi artifacts\ 2>nul copy app\src-tauri\target\release\bundle\nsis\*.exe artifacts\ 2>nul dir artifacts\ - name: Upload to Gitea release if: gitea.event_name == 'push' env: TOKEN: ${{ secrets.REGISTRY_TOKEN }} COMMIT_SHA: ${{ gitea.sha }} run: | set "TAG=v${{ steps.version.outputs.VERSION }}-win" echo Creating release %TAG%... curl -s -X POST -H "Authorization: token %TOKEN%" -H "Content-Type: application/json" -d "{\"tag_name\": \"%TAG%\", \"name\": \"Triple-C v${{ steps.version.outputs.VERSION }} (Windows)\", \"body\": \"Automated build from commit %COMMIT_SHA%\"}" "%GITEA_URL%/api/v1/repos/%REPO%/releases" > release.json for /f "tokens=2 delims=:," %%a in ('findstr /c:"\"id\"" release.json') do set "RELEASE_ID=%%a" & goto :found :found echo Release ID: %RELEASE_ID% for %%f in (artifacts\*) do ( echo Uploading %%~nxf... curl -s -X POST -H "Authorization: token %TOKEN%" -H "Content-Type: application/octet-stream" --data-binary "@%%f" "%GITEA_URL%/api/v1/repos/%REPO%/releases/%RELEASE_ID%/assets?name=%%~nxf" ) sync-to-github: runs-on: ubuntu-latest needs: [build-linux, build-macos, build-windows] if: gitea.event_name == 'push' env: GH_PAT: ${{ secrets.GH_PAT }} GITHUB_REPO: shadowdao/triple-c steps: - name: Download artifacts from Gitea releases env: TOKEN: ${{ secrets.REGISTRY_TOKEN }} VERSION: ${{ needs.build-linux.outputs.version }} run: | set -e mkdir -p artifacts # Download assets from all 3 platform releases for TAG_SUFFIX in "" "-mac" "-win"; do TAG="v${VERSION}${TAG_SUFFIX}" echo "==> Fetching assets for release ${TAG}..." RELEASE_JSON=$(curl -sf \ -H "Authorization: token ${TOKEN}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${TAG}" 2>/dev/null || echo "{}") echo "$RELEASE_JSON" | jq -r '.assets[]? | "\(.name) \(.browser_download_url)"' | while read -r NAME URL; do [ -z "$NAME" ] && continue echo " Downloading ${NAME}..." curl -sfL \ -H "Authorization: token ${TOKEN}" \ -o "artifacts/${NAME}" \ "$URL" done done echo "==> All downloaded artifacts:" ls -la artifacts/ - name: Create GitHub release and upload artifacts env: VERSION: ${{ needs.build-linux.outputs.version }} COMMIT_SHA: ${{ gitea.sha }} run: | set -e TAG="v${VERSION}" echo "==> Creating unified release ${TAG} on GitHub..." # Delete existing release if present (idempotent re-runs) EXISTING=$(curl -sf \ -H "Authorization: Bearer ${GH_PAT}" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG}" 2>/dev/null || echo "{}") EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty') if [ -n "$EXISTING_ID" ]; then echo " Deleting existing GitHub release ${TAG} (id: ${EXISTING_ID})..." curl -sf -X DELETE \ -H "Authorization: Bearer ${GH_PAT}" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${GITHUB_REPO}/releases/${EXISTING_ID}" fi RESPONSE=$(curl -sf -X POST \ -H "Authorization: Bearer ${GH_PAT}" \ -H "Accept: application/vnd.github+json" \ -H "Content-Type: application/json" \ "https://api.github.com/repos/${GITHUB_REPO}/releases" \ -d "{ \"tag_name\": \"${TAG}\", \"name\": \"Triple-C ${TAG}\", \"body\": \"Automated build from commit ${COMMIT_SHA}\n\nIncludes Linux, macOS, and Windows artifacts.\", \"draft\": false, \"prerelease\": false }") UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.upload_url' | sed 's/{?name,label}//') echo "==> Upload URL: ${UPLOAD_URL}" for file in artifacts/*; do [ -f "$file" ] || continue FILENAME=$(basename "$file") MIME="application/octet-stream" echo "==> Uploading ${FILENAME}..." curl -sf -X POST \ -H "Authorization: Bearer ${GH_PAT}" \ -H "Accept: application/vnd.github+json" \ -H "Content-Type: ${MIME}" \ --data-binary "@${file}" \ "${UPLOAD_URL}?name=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "${FILENAME}")" done echo "==> GitHub release sync complete."