name: Build

on:
  push:
    branches-ignore:
      - 'weblate'
    paths:
      - "app/**/*.js"
      - "app/**/*.ts"
      - "app/**/*.vue"
      - "app/src/language/**/*.po"
      - "app/i18n.json"
      - "app/package.json"
      - "app/.env*"
      - "**/*.go"
      - "go.mod"
      - "go.sum"
      - ".github/workflows/build*.yml"
      - "resources/docker/docker/*"
      - "resources/development/*"
      - "resources/demo/*"
      - "Dockerfile"
      - "demo.Dockerfile"
  pull_request:
    types: [ opened, synchronize, reopened ]
    paths:
      - "**/*.js"
      - "**/*.vue"
      - "app/package.json"
      - "app/.env*"
      - "**/*.go"
      - "go.mod"
      - "go.sum"
      - ".github/workflows/*.yml"
      - "resources/docker/docker/*"
      - "resources/development/*"
      - "resources/demo/*"
  release:
    types:
      - published

jobs:
  build_app:
    runs-on: macos-14
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up nodejs
        uses: actions/setup-node@v4
        with:
          node-version: current

      - name: Install dependencies
        run: |
          corepack enable
          corepack prepare pnpm@latest --activate
          pnpm install
        working-directory: app

      - name: Check frontend code style
        run: |
          pnpm run lint
        working-directory: app

      - name: Check frontend types
        run: |
          pnpm run typecheck
        working-directory: app

      - name: Build
        run: |
          npx update-browserslist-db@latest
          pnpm build
        working-directory: app

      - name: Archive app artifacts
        uses: actions/upload-artifact@v4
        with:
          name: app-dist
          path: app/dist

      - name: Prepare publish
        if: github.event_name == 'release'
        run: |
          cp README*.md app/dist
          find app/dist -printf '%P\n' | tar -C app/dist --no-recursion -zcvf app-dist.tar.gz -T -

      - name: Publish
        uses: softprops/action-gh-release@v2
        if: github.event_name == 'release'
        with:
          files: app-dist.tar.gz

  build:
    runs-on: ubuntu-latest
    needs: build_app
    strategy:
      matrix:
        goos: [ linux, darwin, windows ]
        goarch: [ amd64, 386, arm64 ]
        exclude:
          # Exclude i386 on darwin.
          - goarch: 386
            goos: darwin
        include:
          # BEGIN Linux ARM 5 6 7
          - goos: linux
            goarch: arm
            goarm: 7
          - goos: linux
            goarch: arm
            goarm: 6
          - goos: linux
            goarch: arm
            goarm: 5
          # END Linux ARM 5 6 7
          - goos: linux
            goarch: riscv64
          - goos: linux
            goarch: loong64
          # BEGIN MIPS
          - goos: linux
            goarch: mips64
          - goos: linux
            goarch: mips64le
          - goos: linux
            goarch: mipsle
          - goos: linux
            goarch: mips
          # END MIPS
    env:
      CGO_ENABLED: 1
      GOOS: ${{ matrix.goos }}
      GOARCH: ${{ matrix.goarch }}
      GOARM: ${{ matrix.goarm }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ^1.24.4
          cache: false

      - name: Setup environment
        id: info
        run: |
          export _NAME=$(jq ".$GOOS[\"$GOARCH$GOARM\"].name" -r < .github/build/build_info.json)
          export _ARCH=$(jq ".$GOOS[\"$GOARCH$GOARM\"].arch" -r < .github/build/build_info.json)
          export _ABI=$(jq ".$GOOS[\"$GOARCH$GOARM\"].abi // \"\"" -r < .github/build/build_info.json)
          export _ARTIFACT=nginx-ui-$GOOS-$GOARCH$(if [[ "$GOARM" ]]; then echo "v$GOARM"; fi)
          export _BINARY=nginx-ui$(if [[ "$GOOS" == "windows" ]]; then echo ".exe"; fi)
          echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, ABI: $_ABI, RELEASE_NAME: $_NAME, ARTIFACT_NAME: $_ARTIFACT, BINARY_NAME: $_BINARY"
          echo "CACHE_NAME=$_NAME" >> $GITHUB_ENV
          echo "ARCH_NAME=$_ARCH" >> $GITHUB_ENV
          echo "ABI=$_ABI" >> $GITHUB_ENV
          echo "DIST=nginx-ui-$_NAME" >> $GITHUB_ENV
          echo "ARTIFACT=$_ARTIFACT" >> $GITHUB_ENV
          echo "BINARY_NAME=$_BINARY" >> $GITHUB_ENV

      - name: Setup Go modules cache
        uses: actions/cache@v4
        with:
          path: |
            ~/go/pkg/mod
          key: go-${{ runner.os }}-${{ runner.arch }}-mod-${{ hashFiles('go.mod') }}
          restore-keys: |
            go-${{ runner.os }}-${{ runner.arch }}-mod-

      - name: Setup Go build cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
          key: go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-${{ hashFiles('go.mod') }}
          restore-keys: |
              go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-

      - name: Download app artifacts
        uses: actions/download-artifact@v4
        with:
          name: app-dist
          path: app/dist

      - name: Generate files
        env:
          GOOS: linux
          GOARCH: amd64
        run: go generate cmd/version/generate.go

      - name: Install musl cross compiler
        if: env.GOOS == 'linux'
        uses: nginxui/musl-cross-compilers@v1
        id: musl
        with:
          target: ${{ env.ARCH_NAME }}-linux-musl${{ env.ABI }}
          variant: ${{ env.GOARCH == 'loong64' && 'userdocs/qbt-musl-cross-make' || 'richfelker/musl-cross-make' }}

      - name: Post install musl cross compiler
        if: env.GOOS == 'linux'
        run: |
          echo "PATH=${{ steps.musl.outputs.path }}:$PATH" >> $GITHUB_ENV
          echo "CC=${{ env.ARCH_NAME }}-linux-musl${{ env.ABI }}-gcc" >> $GITHUB_ENV
          echo "CXX=${{ env.ARCH_NAME }}-linux-musl${{ env.ABI }}-g++" >> $GITHUB_ENV
          echo "LD_FLAGS=-w --extldflags '-static'" >> $GITHUB_ENV

      - name: Install darwin cross compiler
        if: env.GOOS == 'darwin'
        run: |
          curl -L https://github.com/Hintay/crossosx/releases/latest/download/crossosx.tar.zst -o crossosx.tar.zst
          tar xvaf crossosx.tar.zst
          echo "LD_LIBRARY_PATH=$(pwd)/crossosx/lib/" >> $GITHUB_ENV
          echo "PATH=$(pwd)/crossosx/bin/:$PATH" >> $GITHUB_ENV
          echo "CC=${{ env.ARCH_NAME }}-clang" >> $GITHUB_ENV
          echo "CXX=${{ env.ARCH_NAME }}-clang++" >> $GITHUB_ENV
          echo "LD_FLAGS=-w" >> $GITHUB_ENV

      - name: Setup for Windows
        if: env.GOOS == 'windows'
        run: |
          echo "LD_FLAGS=-w" >> $GITHUB_ENV
          echo "CGO_ENABLED=1" >> $GITHUB_ENV

          # Install cross compilers based on architecture
          sudo apt-get update
          sudo apt-get install -y zip
          if [[ "$GOARCH" == "amd64" ]]; then
            echo "Installing x86_64 Windows cross compiler"
            sudo apt-get install -y gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64
            echo "CC=x86_64-w64-mingw32-gcc" >> $GITHUB_ENV
            echo "CXX=x86_64-w64-mingw32-g++" >> $GITHUB_ENV
          elif [[ "$GOARCH" == "386" ]]; then
            echo "Installing i686 Windows cross compiler"
            sudo apt-get install -y gcc-mingw-w64-i686 g++-mingw-w64-i686
            echo "CC=i686-w64-mingw32-gcc" >> $GITHUB_ENV
            echo "CXX=i686-w64-mingw32-g++" >> $GITHUB_ENV
          elif [[ "$GOARCH" == "arm64" ]]; then
            echo "Installing ARM64 Windows cross compiler"
            # Ubuntu's apt repositories don't have mingw for ARM64
            # Use llvm-mingw project instead
            mkdir -p $HOME/llvm-mingw
            wget -q https://github.com/mstorsjo/llvm-mingw/releases/download/20231128/llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz
            tar xf llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz -C $HOME/llvm-mingw --strip-components=1
            echo "PATH=$HOME/llvm-mingw/bin:$PATH" >> $GITHUB_ENV
            echo "CC=aarch64-w64-mingw32-clang" >> $GITHUB_ENV
            echo "CXX=aarch64-w64-mingw32-clang++" >> $GITHUB_ENV
          else
            echo "Unsupported Windows architecture: $GOARCH"
            exit 1
          fi

      - name: Build
        run: |
          mkdir -p dist
          go build -tags=jsoniter -ldflags "$LD_FLAGS -X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'" -o dist/$BINARY_NAME -v main.go

      - name: Archive backend artifacts
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.ARTIFACT }}
          path: dist/${{ env.BINARY_NAME }}

      - name: Prepare publish
        run: |
          cp README*.md ./dist
          find dist -printf '%P\n' | tar -C dist --no-recursion -zcvf ${{ env.DIST }}.tar.gz -T -
          openssl dgst -sha512 ${{ env.DIST }}.tar.gz | sed 's/([^)]*)//g' | awk '{print $NF}' >> ${{ env.DIST }}.tar.gz.digest

          # Create zip for Windows builds (for winget compatibility)
          if [[ "$GOOS" == "windows" ]]; then
            cd dist
            zip -r ../${{ env.DIST }}.zip .
            cd ..
            openssl dgst -sha512 ${{ env.DIST }}.zip | sed 's/([^)]*)//g' | awk '{print $NF}' >> ${{ env.DIST }}.zip.digest
          fi

      - name: Publish
        uses: softprops/action-gh-release@v2
        if: github.event_name == 'release'
        with:
          files: |
            ${{ env.DIST }}.tar.gz
            ${{ env.DIST }}.tar.gz.digest
            ${{ env.GOOS == 'windows' && format('{0}.zip', env.DIST) || '' }}
            ${{ env.GOOS == 'windows' && format('{0}.zip.digest', env.DIST) || '' }}

      - name: Set up nodejs
        uses: actions/setup-node@v4
        with:
          node-version: current

      - name: Install dependencies
        run: |
          corepack enable
          corepack prepare pnpm@latest --activate

      - name: Upload to R2
        if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev'
        uses: cloudflare/wrangler-action@v3
        env:
          WRANGLER_LOG: debug
          WRANGLER_LOG_SANITIZE: false
        with:
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          apiToken: ${{ secrets.CF_R2_API_TOKEN }}
          wranglerVersion: "4.21.1"
          command: |
            r2 object put nginx-ui-dev-build/${{ env.DIST }}.tar.gz --file ./${{ env.DIST }}.tar.gz --remote
            r2 object put nginx-ui-dev-build/${{ env.DIST }}.tar.gz.digest --file ./${{ env.DIST }}.tar.gz.digest --remote
            if [[ "$GOOS" == "windows" ]]; then
              r2 object put nginx-ui-dev-build/${{ env.DIST }}.zip --file ./${{ env.DIST }}.zip --remote
              r2 object put nginx-ui-dev-build/${{ env.DIST }}.zip.digest --file ./${{ env.DIST }}.zip.digest --remote
            fi

  docker-build:
    if: github.event_name != 'pull_request'
    runs-on: ubuntu-latest
    needs: build
    env:
      PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/arm/v5
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            uozi/nginx-ui
          tags: |
            type=schedule
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{raw}}
            type=sha
            type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          path: ./dist

      - name: Prepare Artifacts
        run: chmod +x ./dist/nginx-ui-*/nginx-ui*

      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USER }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Prepare Dockerfile
        run: |
          cp ./Dockerfile ./dist
          cp -rp ./resources ./dist

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: ./dist
          file: ./dist/Dockerfile
          platforms: ${{ env.PLATFORMS }}
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      - name: Prepare Demo Dockerfile
        run: |
          cp ./demo.Dockerfile ./dist
          cp -rp ./resources ./dist

      - name: Build and push demo
        uses: docker/build-push-action@v6
        if: github.ref == 'refs/heads/dev'
        with:
          context: ./dist
          file: ./dist/demo.Dockerfile
          platforms: ${{ env.PLATFORMS }}
          push: 'true'
          tags: |
            uozi/nginx-ui-demo:latest
