mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-09-03 07:24:52 +08:00
1273 lines
46 KiB
YAML
1273 lines
46 KiB
YAML
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/**"
|
|
- "resources/development/*"
|
|
- "resources/demo/*"
|
|
- "Dockerfile"
|
|
- "demo.Dockerfile"
|
|
# Without this a Worker-only change never reaches the deploy-demo job, so
|
|
# the demo keeps serving the previous Worker until some unrelated commit
|
|
# happens to trigger a build.
|
|
- "cloudflare/**"
|
|
pull_request:
|
|
types: [ opened, synchronize, reopened ]
|
|
paths:
|
|
- "**/*.js"
|
|
- "**/*.vue"
|
|
- "app/package.json"
|
|
- "app/.env*"
|
|
- "**/*.go"
|
|
- "go.mod"
|
|
- "go.sum"
|
|
- ".github/workflows/*.yml"
|
|
- "resources/docker/**"
|
|
- "resources/development/*"
|
|
- "resources/demo/*"
|
|
release:
|
|
types:
|
|
- published
|
|
|
|
jobs:
|
|
build_app:
|
|
runs-on: macos-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Go
|
|
if: github.event_name == 'release'
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version: ^1.26.5
|
|
cache: false
|
|
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: 1.3.14
|
|
|
|
- name: Install dependencies
|
|
run: bun ci
|
|
|
|
- name: Check frontend code style
|
|
run: bun run lint
|
|
|
|
- name: Check frontend types
|
|
run: bun run typecheck
|
|
|
|
- name: Build
|
|
run: |
|
|
bunx update-browserslist-db@latest
|
|
bun run build
|
|
|
|
- name: Archive app artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: app-dist
|
|
path: |
|
|
app/dist
|
|
|
|
- name: Prepare publish
|
|
if: github.event_name == 'release'
|
|
run: |
|
|
set -euo pipefail
|
|
cp README*.md app/dist
|
|
tar -C app/dist -czf app-dist.tar.gz .
|
|
test -s app-dist.tar.gz
|
|
tar -tzf app-dist.tar.gz | grep -qx './index.html'
|
|
tar -tzf app-dist.tar.gz | grep -q '^./assets/'
|
|
tar -tzf app-dist.tar.gz | grep -qx './README.md'
|
|
|
|
- name: Sign publish archive
|
|
if: github.event_name == 'release'
|
|
env:
|
|
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
|
|
MINISIGN_PASSWORD: ${{ secrets.MINISIGN_PASSWORD }}
|
|
run: env CGO_ENABLED=0 go run ./cmd/sign-release app-dist.tar.gz
|
|
|
|
- name: Publish
|
|
uses: softprops/action-gh-release@v3
|
|
if: github.event_name == 'release'
|
|
with:
|
|
files: |
|
|
app-dist.tar.gz
|
|
app-dist.tar.gz.minisig
|
|
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
needs: build_app
|
|
strategy:
|
|
max-parallel: 4
|
|
matrix:
|
|
goos: [ linux, windows ]
|
|
goarch: [ amd64, 386, arm64 ]
|
|
# exclude:
|
|
# Exclude i386 on windows (if needed)
|
|
# - goarch: 386
|
|
# goos: windows
|
|
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@v7
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version: ^1.26.5
|
|
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@v6
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
key: go-${{ runner.os }}-${{ runner.arch }}-mod-${{ hashFiles('**/go.sum') }}
|
|
restore-keys: |
|
|
go-${{ runner.os }}-${{ runner.arch }}-mod-
|
|
|
|
- name: Setup Go build cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: |
|
|
~/.cache/go-build
|
|
key: go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-${{ hashFiles('**/go.sum') }}
|
|
restore-keys: |
|
|
go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-
|
|
|
|
- name: Download app artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: app-dist
|
|
path: frontend-dist
|
|
|
|
- name: Prepare frontend assets
|
|
run: |
|
|
rm -rf app/dist
|
|
mkdir -p app
|
|
if [[ -d frontend-dist/app/dist ]]; then
|
|
mv frontend-dist/app/dist app/dist
|
|
elif [[ -d frontend-dist/dist ]]; then
|
|
mv frontend-dist/dist app/dist
|
|
else
|
|
mv frontend-dist app/dist
|
|
fi
|
|
|
|
- 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: 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 -trimpath -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@v7
|
|
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: Sign publish archives
|
|
if: github.event_name == 'release' || (github.event_name != 'pull_request' && github.ref == 'refs/heads/dev')
|
|
env:
|
|
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
|
|
MINISIGN_PASSWORD: ${{ secrets.MINISIGN_PASSWORD }}
|
|
run: |
|
|
archives=("${{ env.DIST }}.tar.gz")
|
|
if [[ "$GOOS" == "windows" ]]; then
|
|
archives+=("${{ env.DIST }}.zip")
|
|
fi
|
|
env GOOS="$(go env GOHOSTOS)" GOARCH="$(go env GOHOSTARCH)" CGO_ENABLED=0 go run ./cmd/sign-release "${archives[@]}"
|
|
|
|
- name: Publish
|
|
uses: softprops/action-gh-release@v3
|
|
if: github.event_name == 'release'
|
|
with:
|
|
files: |
|
|
${{ env.DIST }}.tar.gz
|
|
${{ env.DIST }}.tar.gz.digest
|
|
${{ env.DIST }}.tar.gz.minisig
|
|
${{ env.GOOS == 'windows' && format('{0}.zip', env.DIST) || '' }}
|
|
${{ env.GOOS == 'windows' && format('{0}.zip.digest', env.DIST) || '' }}
|
|
${{ env.GOOS == 'windows' && format('{0}.zip.minisig', env.DIST) || '' }}
|
|
|
|
- name: Upload to R2 using S3 API
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev'
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: us-east-1
|
|
run: |
|
|
echo "Uploading ${{ env.DIST }}.tar.gz to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Uploading ${{ env.DIST }}.tar.gz.digest to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz.digest s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.digest --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Uploading ${{ env.DIST }}.tar.gz.minisig to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz.minisig s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.minisig --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Upload completed successfully"
|
|
|
|
build_termux:
|
|
runs-on: ubuntu-latest
|
|
needs: build_app
|
|
env:
|
|
CGO_ENABLED: 1
|
|
GOOS: android
|
|
GOARCH: arm64
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version: ^1.26.5
|
|
cache: false
|
|
|
|
- name: Setup environment
|
|
id: info_termux
|
|
run: |
|
|
export _NAME=$(jq ".termux[\"$GOARCH\"].name" -r < .github/build/build_info.json)
|
|
export _ARTIFACT=nginx-ui-$_NAME
|
|
export _BINARY=nginx-ui
|
|
echo "GOOS: $GOOS, GOARCH: $GOARCH, RELEASE_NAME: $_NAME, ARTIFACT_NAME: $_ARTIFACT, BINARY_NAME: $_BINARY"
|
|
echo "CACHE_NAME=$_NAME" >> $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@v6
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
key: go-${{ runner.os }}-${{ runner.arch }}-mod-${{ hashFiles('**/go.sum') }}
|
|
restore-keys: |
|
|
go-${{ runner.os }}-${{ runner.arch }}-mod-
|
|
|
|
- name: Setup Go build cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: |
|
|
~/.cache/go-build
|
|
key: go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-${{ hashFiles('**/go.sum') }}
|
|
restore-keys: |
|
|
go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-
|
|
|
|
- name: Download app artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: app-dist
|
|
path: frontend-dist
|
|
|
|
- name: Prepare frontend assets
|
|
run: |
|
|
rm -rf app/dist
|
|
mkdir -p app
|
|
if [[ -d frontend-dist/app/dist ]]; then
|
|
mv frontend-dist/app/dist app/dist
|
|
elif [[ -d frontend-dist/dist ]]; then
|
|
mv frontend-dist/dist app/dist
|
|
else
|
|
mv frontend-dist app/dist
|
|
fi
|
|
|
|
- name: Generate files
|
|
env:
|
|
GOOS: linux
|
|
GOARCH: amd64
|
|
run: go generate cmd/version/generate.go
|
|
|
|
- name: Set up Android NDK
|
|
id: ndk
|
|
uses: nttld/setup-ndk@v1
|
|
with:
|
|
ndk-version: r27c
|
|
|
|
- name: Configure Android toolchain
|
|
run: |
|
|
API_LEVEL=24
|
|
TOOLCHAIN="${{ steps.ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
|
echo "PATH=$TOOLCHAIN:$PATH" >> $GITHUB_ENV
|
|
echo "ANDROID_API_LEVEL=$API_LEVEL" >> $GITHUB_ENV
|
|
echo "CC=aarch64-linux-android${API_LEVEL}-clang" >> $GITHUB_ENV
|
|
echo "CXX=aarch64-linux-android${API_LEVEL}-clang++" >> $GITHUB_ENV
|
|
echo "LD_FLAGS=-w" >> $GITHUB_ENV
|
|
echo "GOOS=android" >> $GITHUB_ENV
|
|
echo "GOARCH=arm64" >> $GITHUB_ENV
|
|
echo "CGO_ENABLED=1" >> $GITHUB_ENV
|
|
|
|
- name: Build
|
|
env:
|
|
GOOS: android
|
|
GOARCH: arm64
|
|
CGO_ENABLED: 1
|
|
run: |
|
|
mkdir -p dist
|
|
go build -trimpath -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@v7
|
|
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
|
|
|
|
- name: Sign publish archive
|
|
if: github.event_name == 'release' || (github.event_name != 'pull_request' && github.ref == 'refs/heads/dev')
|
|
env:
|
|
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
|
|
MINISIGN_PASSWORD: ${{ secrets.MINISIGN_PASSWORD }}
|
|
run: env GOOS="$(go env GOHOSTOS)" GOARCH="$(go env GOHOSTARCH)" CGO_ENABLED=0 go run ./cmd/sign-release "${{ env.DIST }}.tar.gz"
|
|
|
|
- name: Publish
|
|
uses: softprops/action-gh-release@v3
|
|
if: github.event_name == 'release'
|
|
with:
|
|
files: |
|
|
${{ env.DIST }}.tar.gz
|
|
${{ env.DIST }}.tar.gz.digest
|
|
${{ env.DIST }}.tar.gz.minisig
|
|
|
|
- name: Upload to R2 using S3 API
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev'
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: us-east-1
|
|
run: |
|
|
echo "Uploading ${{ env.DIST }}.tar.gz to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Uploading ${{ env.DIST }}.tar.gz.digest to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz.digest s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.digest --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Uploading ${{ env.DIST }}.tar.gz.minisig to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz.minisig s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.minisig --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Upload completed successfully"
|
|
|
|
build_macos_native:
|
|
runs-on: macos-latest
|
|
needs: build_app
|
|
strategy:
|
|
matrix:
|
|
goarch: [amd64, arm64]
|
|
env:
|
|
CGO_ENABLED: 1
|
|
GOOS: darwin
|
|
GOARCH: ${{ matrix.goarch }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version: ^1.26.5
|
|
cache: false
|
|
|
|
- name: Setup environment
|
|
id: info
|
|
run: |
|
|
export _NAME=$(jq ".darwin[\"$GOARCH\"].name" -r < .github/build/build_info.json)
|
|
export _ARTIFACT=nginx-ui-darwin-$GOARCH
|
|
export _BINARY=nginx-ui
|
|
echo "GOOS: darwin, GOARCH: $GOARCH, RELEASE_NAME: $_NAME, ARTIFACT_NAME: $_ARTIFACT, BINARY_NAME: $_BINARY"
|
|
echo "CACHE_NAME=$_NAME" >> $GITHUB_ENV
|
|
echo "DIST=nginx-ui-$_NAME" >> $GITHUB_ENV
|
|
echo "ARTIFACT=$_ARTIFACT" >> $GITHUB_ENV
|
|
echo "BINARY_NAME=$_BINARY" >> $GITHUB_ENV
|
|
|
|
- name: Setup Go build cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-${{ hashFiles('go.sum') }}
|
|
restore-keys: |
|
|
go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-
|
|
|
|
- name: Download app artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: app-dist
|
|
path: frontend-dist
|
|
|
|
- name: Prepare frontend assets
|
|
run: |
|
|
rm -rf app/dist
|
|
mkdir -p app
|
|
if [[ -d frontend-dist/app/dist ]]; then
|
|
mv frontend-dist/app/dist app/dist
|
|
elif [[ -d frontend-dist/dist ]]; then
|
|
mv frontend-dist/dist app/dist
|
|
else
|
|
mv frontend-dist app/dist
|
|
fi
|
|
|
|
- name: Generate files
|
|
run: go generate cmd/version/generate.go
|
|
|
|
- name: Build with native CGO
|
|
run: |
|
|
mkdir -p dist
|
|
go build -trimpath -tags=jsoniter -ldflags "-w -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@v7
|
|
with:
|
|
name: ${{ env.ARTIFACT }}
|
|
path: dist/${{ env.BINARY_NAME }}
|
|
|
|
- name: Prepare publish
|
|
run: |
|
|
cp README*.md ./dist
|
|
cd dist && tar -zcvf ../${{ env.DIST }}.tar.gz .
|
|
cd ..
|
|
openssl dgst -sha512 ${{ env.DIST }}.tar.gz | sed 's/([^)]*)//g' | awk '{print $NF}' >> ${{ env.DIST }}.tar.gz.digest
|
|
|
|
- name: Sign publish archive
|
|
if: github.event_name == 'release' || (github.event_name != 'pull_request' && github.ref == 'refs/heads/dev')
|
|
env:
|
|
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
|
|
MINISIGN_PASSWORD: ${{ secrets.MINISIGN_PASSWORD }}
|
|
run: env GOOS="$(go env GOHOSTOS)" GOARCH="$(go env GOHOSTARCH)" CGO_ENABLED=0 go run ./cmd/sign-release "${{ env.DIST }}.tar.gz"
|
|
|
|
- name: Publish
|
|
uses: softprops/action-gh-release@v3
|
|
if: github.event_name == 'release'
|
|
with:
|
|
files: |
|
|
${{ env.DIST }}.tar.gz
|
|
${{ env.DIST }}.tar.gz.digest
|
|
${{ env.DIST }}.tar.gz.minisig
|
|
|
|
- name: Upload to R2 using S3 API
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev'
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: us-east-1
|
|
run: |
|
|
echo "Uploading ${{ env.DIST }}.tar.gz to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Uploading ${{ env.DIST }}.tar.gz.digest to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz.digest s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.digest --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Uploading ${{ env.DIST }}.tar.gz.minisig to R2..."
|
|
aws s3 cp ./${{ env.DIST }}.tar.gz.minisig s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.minisig --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }}
|
|
|
|
echo "Upload completed successfully"
|
|
|
|
docker-build:
|
|
if: github.event_name != 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
needs: [build, build_macos_native]
|
|
env:
|
|
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/arm/v5,linux/riscv64
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Docker meta
|
|
id: meta
|
|
uses: docker/metadata-action@v6
|
|
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@v8
|
|
with:
|
|
path: ./dist
|
|
|
|
- name: Prepare Artifacts
|
|
run: chmod +x ./dist/nginx-ui-*/nginx-ui*
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@v4
|
|
with:
|
|
platforms: arm,arm64,riscv64
|
|
|
|
- name: Set up Docker Buildx
|
|
id: buildx
|
|
uses: docker/setup-buildx-action@v4
|
|
|
|
- name: Login to DockerHub
|
|
uses: docker/login-action@v4
|
|
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@v7
|
|
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
|
|
if: github.ref == 'refs/heads/dev'
|
|
run: |
|
|
cp ./demo.Dockerfile ./dist
|
|
cp -rp ./resources ./dist
|
|
|
|
- name: Build and push demo
|
|
uses: docker/build-push-action@v7
|
|
if: github.ref == 'refs/heads/dev'
|
|
with:
|
|
context: ./dist
|
|
file: ./dist/demo.Dockerfile
|
|
# linux/amd64 only. The demo image exists to run on Cloudflare
|
|
# Containers, which is amd64; the other five platforms were built
|
|
# under QEMU and thrown away.
|
|
platforms: linux/amd64
|
|
push: 'true'
|
|
# The immutable tag is what the Worker deploys against — Cloudflare
|
|
# pins a container application to a specific image, so redeploying
|
|
# with an unchanged :latest would not roll instances onto new bytes.
|
|
tags: |
|
|
uozi/nginx-ui-demo:latest
|
|
uozi/nginx-ui-demo:sha-${{ github.sha }}
|
|
|
|
deploy-demo:
|
|
name: Deploy demo to Cloudflare
|
|
if: github.ref == 'refs/heads/dev' && github.event_name != 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
needs: docker-build
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Install Worker dependencies
|
|
working-directory: cloudflare
|
|
run: bun install --frozen-lockfile
|
|
|
|
# Deploys against the image docker-build just published rather than
|
|
# rebuilding it, so this step is a Worker update and takes seconds.
|
|
- name: Deploy
|
|
working-directory: cloudflare
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
|
run: sh ./deploy-published.sh "docker.io/uozi/nginx-ui-demo:sha-${{ github.sha }}"
|
|
|
|
helm-smoke:
|
|
name: Helm install and upgrade smoke test
|
|
if: github.event_name == 'release'
|
|
runs-on: ubuntu-latest
|
|
needs: docker-build
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Helm
|
|
uses: azure/setup-helm@v5
|
|
with:
|
|
version: v4.2.3
|
|
|
|
- name: Create kind cluster
|
|
uses: helm/kind-action@v1.14.0
|
|
with:
|
|
version: v0.31.0
|
|
kubectl_version: v1.35.0
|
|
cluster_name: nginx-ui-helm
|
|
wait: 120s
|
|
|
|
- name: Install, persist, and upgrade
|
|
env:
|
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${RELEASE_TAG#v}"
|
|
kubectl apply -f charts/nginx-ui/ci/smoke-pvs.yaml
|
|
|
|
helm install nginx-ui charts/nginx-ui \
|
|
--namespace nginx-ui \
|
|
--create-namespace \
|
|
--values charts/nginx-ui/ci/smoke-values.yaml \
|
|
--set-string image.tag="$version" \
|
|
--wait \
|
|
--timeout 10m
|
|
|
|
old_pod_uid="$(kubectl get pod -n nginx-ui -l app.kubernetes.io/instance=nginx-ui -o jsonpath='{.items[0].metadata.uid}')"
|
|
test -n "$old_pod_uid"
|
|
kubectl exec -n nginx-ui deploy/nginx-ui -- \
|
|
sh -c 'printf "%s\n" persisted > /etc/nginx-ui/.helm-smoke-marker'
|
|
|
|
helm upgrade nginx-ui charts/nginx-ui \
|
|
--namespace nginx-ui \
|
|
--values charts/nginx-ui/ci/smoke-values.yaml \
|
|
--set-string image.tag="$version" \
|
|
--set-string podAnnotations.nginx-ui-smoke-revision=2 \
|
|
--wait \
|
|
--timeout 10m
|
|
|
|
new_pod_uid="$(kubectl get pod -n nginx-ui -l app.kubernetes.io/instance=nginx-ui -o jsonpath='{.items[0].metadata.uid}')"
|
|
test -n "$new_pod_uid"
|
|
test "$new_pod_uid" != "$old_pod_uid"
|
|
kubectl exec -n nginx-ui deploy/nginx-ui -- \
|
|
test -f /etc/nginx-ui/.helm-smoke-marker
|
|
|
|
kubectl port-forward -n nginx-ui service/nginx-ui 18080:80 >/tmp/nginx-ui-port-forward.log 2>&1 &
|
|
port_forward_pid=$!
|
|
trap 'kill "$port_forward_pid" 2>/dev/null || true' EXIT
|
|
for attempt in {1..30}; do
|
|
if curl -fsS http://127.0.0.1:18080/healthz; then
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
cat /tmp/nginx-ui-port-forward.log
|
|
exit 1
|
|
|
|
- name: Collect failure diagnostics
|
|
if: failure()
|
|
run: |
|
|
kubectl get all,pvc,pv -A
|
|
kubectl describe pod -n nginx-ui -l app.kubernetes.io/instance=nginx-ui
|
|
kubectl logs -n nginx-ui deploy/nginx-ui --all-containers=true --tail=200 || true
|
|
|
|
publish-helm:
|
|
name: Publish Helm repository
|
|
if: github.event_name == 'release'
|
|
runs-on: ubuntu-latest
|
|
needs: helm-smoke
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Helm
|
|
uses: azure/setup-helm@v5
|
|
with:
|
|
version: v4.2.3
|
|
|
|
- name: Package chart
|
|
env:
|
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${RELEASE_TAG#v}"
|
|
mkdir -p helm-repository
|
|
helm package charts/nginx-ui \
|
|
--version "$version" \
|
|
--app-version "$version" \
|
|
--destination helm-repository
|
|
|
|
- name: Build repository index
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: us-east-1
|
|
AWS_EC2_METADATA_DISABLED: "true"
|
|
R2_ENDPOINT: ${{ secrets.R2_S3_API_ENDPOINT }}
|
|
run: |
|
|
set -euo pipefail
|
|
if aws s3api head-object \
|
|
--bucket nginx-ui-dev-build \
|
|
--key helm/index.yaml \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
>/dev/null 2>helm-repository/head-object.error; then
|
|
aws s3 cp s3://nginx-ui-dev-build/helm/index.yaml helm-repository/previous-index.yaml \
|
|
--endpoint-url="$R2_ENDPOINT"
|
|
helm repo index helm-repository \
|
|
--url https://cloud.nginxui.com/helm \
|
|
--merge helm-repository/previous-index.yaml
|
|
rm helm-repository/previous-index.yaml
|
|
elif grep -Eq '(404|Not Found|NoSuchKey)' helm-repository/head-object.error; then
|
|
helm repo index helm-repository --url https://cloud.nginxui.com/helm
|
|
else
|
|
cat helm-repository/head-object.error >&2
|
|
exit 1
|
|
fi
|
|
rm helm-repository/head-object.error
|
|
|
|
- name: Upload chart before index
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: us-east-1
|
|
AWS_EC2_METADATA_DISABLED: "true"
|
|
R2_ENDPOINT: ${{ secrets.R2_S3_API_ENDPOINT }}
|
|
run: |
|
|
set -euo pipefail
|
|
chart_file="$(find helm-repository -maxdepth 1 -name 'nginx-ui-*.tgz' -print -quit)"
|
|
test -n "$chart_file"
|
|
aws s3 cp "$chart_file" "s3://nginx-ui-dev-build/helm/$(basename "$chart_file")" \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
--content-type application/gzip \
|
|
--cache-control 'public, max-age=31536000, immutable'
|
|
aws s3 cp helm-repository/index.yaml s3://nginx-ui-dev-build/helm/index.yaml \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
--content-type application/yaml \
|
|
--cache-control 'public, max-age=300'
|
|
|
|
build-openwrt:
|
|
name: OpenWrt ${{ matrix.feed_arch }}
|
|
if: github.event_name == 'release'
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- target: x86
|
|
subtarget: "64"
|
|
feed_arch: x86_64
|
|
binary_artifact: nginx-ui-linux-amd64
|
|
- target: armsr
|
|
subtarget: armv8
|
|
feed_arch: aarch64_generic
|
|
binary_artifact: nginx-ui-linux-arm64
|
|
- target: mediatek
|
|
subtarget: filogic
|
|
feed_arch: aarch64_cortex-a53
|
|
binary_artifact: nginx-ui-linux-arm64
|
|
- target: armsr
|
|
subtarget: armv7
|
|
feed_arch: arm_cortex-a15_neon-vfpv4
|
|
binary_artifact: nginx-ui-linux-armv7
|
|
- target: ipq40xx
|
|
subtarget: generic
|
|
feed_arch: arm_cortex-a7_neon-vfpv4
|
|
binary_artifact: nginx-ui-linux-armv7
|
|
- target: sifiveu
|
|
subtarget: generic
|
|
feed_arch: riscv64_generic
|
|
binary_artifact: nginx-ui-linux-riscv64
|
|
env:
|
|
OPENWRT_VERSION: 25.12.5
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Install SDK dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
build-essential clang flex bison g++ gawk gcc-multilib gettext git \
|
|
libncurses-dev libssl-dev python3-setuptools rsync swig unzip zlib1g-dev \
|
|
file wget zstd
|
|
|
|
- name: Download release binary
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: ${{ matrix.binary_artifact }}
|
|
path: release-binary
|
|
|
|
- name: Download and verify OpenWrt SDK
|
|
env:
|
|
TARGET: ${{ matrix.target }}
|
|
SUBTARGET: ${{ matrix.subtarget }}
|
|
run: |
|
|
set -euo pipefail
|
|
mirror_base_url="https://mirror-03.infra.openwrt.org/releases/${OPENWRT_VERSION}/targets/${TARGET}/${SUBTARGET}"
|
|
downloads_base_url="https://downloads.openwrt.org/releases/${OPENWRT_VERSION}/targets/${TARGET}/${SUBTARGET}"
|
|
archive_base_url="https://archive.openwrt.org/releases/${OPENWRT_VERSION}/targets/${TARGET}/${SUBTARGET}"
|
|
|
|
download_openwrt_file() {
|
|
local output="$1"
|
|
local filename="$2"
|
|
local base_url
|
|
for base_url in "$mirror_base_url" "$downloads_base_url" "$archive_base_url"; do
|
|
if curl --retry 3 --retry-all-errors --retry-delay 2 \
|
|
-fsSLo "$output" "${base_url}/${filename}"; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
download_openwrt_file sha256sums sha256sums
|
|
sdk_filename="$(awk '$2 ~ /^\*?openwrt-sdk-.*\.Linux-x86_64\.tar\.zst$/ {sub(/^\*/, "", $2); print $2; exit}' sha256sums)"
|
|
sdk_sha256="$(awk -v file="$sdk_filename" '$2 == file || $2 == "*" file {print $1; exit}' sha256sums)"
|
|
test -n "$sdk_filename"
|
|
test -n "$sdk_sha256"
|
|
download_openwrt_file "$sdk_filename" "$sdk_filename"
|
|
echo "${sdk_sha256} ${sdk_filename}" | sha256sum --check
|
|
sdk_dir="$(tar --zstd -tf "$sdk_filename" | sed -n '1s#/.*##p')"
|
|
test -n "$sdk_dir"
|
|
tar --zstd -xf "$sdk_filename"
|
|
echo "SDK_DIR=$sdk_dir" >> "$GITHUB_ENV"
|
|
|
|
- name: Prepare package and signing key
|
|
env:
|
|
OPENWRT_APK_PRIVATE_KEY: ${{ secrets.OPENWRT_APK_PRIVATE_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
test -n "$OPENWRT_APK_PRIVATE_KEY"
|
|
package_dir="$SDK_DIR/package/nginx-ui"
|
|
cp -R packaging/openwrt "$package_dir"
|
|
install -m 0755 release-binary/nginx-ui "$package_dir/files/nginx-ui"
|
|
install -m 0755 resources/services/nginx-ui.openwrt "$package_dir/files/nginx-ui.init"
|
|
install -m 0644 resources/keep.openwrt "$package_dir/files/nginx-ui.keep"
|
|
install -m 0644 LICENSE "$package_dir/files/LICENSE"
|
|
|
|
printf '%s\n' "$OPENWRT_APK_PRIVATE_KEY" > "$SDK_DIR/private-key.pem"
|
|
if ! grep -q '^untrusted comment:' "$SDK_DIR/private-key.pem"; then
|
|
sed -i '1i untrusted comment: Nginx UI repository key' "$SDK_DIR/private-key.pem"
|
|
fi
|
|
openssl ec -in "$SDK_DIR/private-key.pem" -pubout > "$SDK_DIR/public-key.pem.tmp"
|
|
{
|
|
echo 'untrusted comment: Nginx UI repository key'
|
|
cat "$SDK_DIR/public-key.pem.tmp"
|
|
} > "$SDK_DIR/public-key.pem"
|
|
rm "$SDK_DIR/public-key.pem.tmp"
|
|
chmod 0600 "$SDK_DIR/private-key.pem"
|
|
|
|
- name: Build signed APK repository
|
|
env:
|
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
EXPECTED_FEED_ARCH: ${{ matrix.feed_arch }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${RELEASE_TAG#v}"
|
|
cd "$SDK_DIR"
|
|
{
|
|
echo 'CONFIG_PACKAGE_nginx-ui=m'
|
|
echo 'CONFIG_SIGNED_PACKAGES=y'
|
|
echo 'CONFIG_USE_APK=y'
|
|
} >> .config
|
|
make defconfig
|
|
make package/nginx-ui/compile V=s NGINX_UI_VERSION="$version"
|
|
make package/index V=s
|
|
|
|
package_files=(bin/packages/*/base/nginx-ui-*.apk)
|
|
test "${#package_files[@]}" -eq 1
|
|
package_file="${package_files[0]}"
|
|
package_repo="$(dirname "$package_file")"
|
|
feed_arch="$(basename "$(dirname "$package_repo")")"
|
|
test "$feed_arch" = "$EXPECTED_FEED_ARCH"
|
|
test -s "$package_repo/packages.adb"
|
|
test -s "$package_repo/index.json"
|
|
|
|
output_dir="$GITHUB_WORKSPACE/openwrt-artifact/$feed_arch"
|
|
mkdir -p "$output_dir"
|
|
cp "$package_file" "$package_repo/packages.adb" "$package_repo/index.json" "$output_dir/"
|
|
cp public-key.pem "$output_dir/public-key.pem"
|
|
|
|
- name: Upload repository artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: openwrt-${{ matrix.feed_arch }}
|
|
path: openwrt-artifact
|
|
if-no-files-found: error
|
|
|
|
publish-openwrt:
|
|
name: Publish OpenWrt repository
|
|
if: github.event_name == 'release'
|
|
runs-on: ubuntu-latest
|
|
needs: build-openwrt
|
|
steps:
|
|
- name: Download repository artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: openwrt-*
|
|
path: openwrt-repository
|
|
merge-multiple: true
|
|
|
|
- name: Verify repository artifacts
|
|
run: |
|
|
set -euo pipefail
|
|
reference_key=""
|
|
for feed_dir in openwrt-repository/*; do
|
|
test -d "$feed_dir"
|
|
test -s "$feed_dir/packages.adb"
|
|
test -s "$feed_dir/index.json"
|
|
package_files=("$feed_dir"/nginx-ui-*.apk)
|
|
test "${#package_files[@]}" -eq 1
|
|
if [[ -z "$reference_key" ]]; then
|
|
reference_key="$feed_dir/public-key.pem"
|
|
else
|
|
cmp "$reference_key" "$feed_dir/public-key.pem"
|
|
fi
|
|
done
|
|
test -n "$reference_key"
|
|
cp "$reference_key" openwrt-repository/public-key.pem
|
|
|
|
- name: Upload packages before indexes
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: us-east-1
|
|
AWS_EC2_METADATA_DISABLED: "true"
|
|
R2_ENDPOINT: ${{ secrets.R2_S3_API_ENDPOINT }}
|
|
run: |
|
|
set -euo pipefail
|
|
aws s3 cp openwrt-repository/public-key.pem s3://nginx-ui-dev-build/openwrt/public-key.pem \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
--content-type application/x-pem-file \
|
|
--cache-control 'public, max-age=3600'
|
|
|
|
for feed_dir in openwrt-repository/*; do
|
|
[[ -d "$feed_dir" ]] || continue
|
|
feed_arch="$(basename "$feed_dir")"
|
|
package_files=("$feed_dir"/nginx-ui-*.apk)
|
|
aws s3 cp "${package_files[0]}" "s3://nginx-ui-dev-build/openwrt/25.12/${feed_arch}/$(basename "${package_files[0]}")" \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
--content-type application/vnd.alpine.apk \
|
|
--cache-control 'public, max-age=31536000, immutable'
|
|
done
|
|
|
|
for feed_dir in openwrt-repository/*; do
|
|
[[ -d "$feed_dir" ]] || continue
|
|
feed_arch="$(basename "$feed_dir")"
|
|
aws s3 cp "$feed_dir/index.json" "s3://nginx-ui-dev-build/openwrt/25.12/${feed_arch}/index.json" \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
--content-type application/json \
|
|
--cache-control 'public, max-age=300'
|
|
aws s3 cp "$feed_dir/packages.adb" "s3://nginx-ui-dev-build/openwrt/25.12/${feed_arch}/packages.adb" \
|
|
--endpoint-url="$R2_ENDPOINT" \
|
|
--content-type application/octet-stream \
|
|
--cache-control 'public, max-age=300'
|
|
done
|
|
|
|
update-homebrew:
|
|
runs-on: ubuntu-latest
|
|
needs: [build, build_macos_native]
|
|
if: github.event_name == 'release'
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Get release info
|
|
id: release
|
|
run: |
|
|
echo "tag_name=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
|
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Download release assets and calculate SHA256 checksums
|
|
id: checksums
|
|
run: |
|
|
VERSION="${{ steps.release.outputs.version }}"
|
|
TAG_NAME="${{ steps.release.outputs.tag_name }}"
|
|
|
|
# Download binary files from releases and calculate SHA256
|
|
mkdir -p downloads
|
|
|
|
# macOS Intel
|
|
wget -O downloads/nginx-ui-macos-64.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-macos-64.tar.gz"
|
|
MACOS_INTEL_SHA256=$(sha256sum downloads/nginx-ui-macos-64.tar.gz | cut -d' ' -f1)
|
|
|
|
# macOS ARM
|
|
wget -O downloads/nginx-ui-macos-arm64-v8a.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-macos-arm64-v8a.tar.gz"
|
|
MACOS_ARM_SHA256=$(sha256sum downloads/nginx-ui-macos-arm64-v8a.tar.gz | cut -d' ' -f1)
|
|
|
|
# Linux Intel
|
|
wget -O downloads/nginx-ui-linux-64.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-linux-64.tar.gz"
|
|
LINUX_INTEL_SHA256=$(sha256sum downloads/nginx-ui-linux-64.tar.gz | cut -d' ' -f1)
|
|
|
|
# Linux ARM
|
|
wget -O downloads/nginx-ui-linux-arm64-v8a.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-linux-arm64-v8a.tar.gz"
|
|
LINUX_ARM_SHA256=$(sha256sum downloads/nginx-ui-linux-arm64-v8a.tar.gz | cut -d' ' -f1)
|
|
|
|
echo "macos_intel_sha256=$MACOS_INTEL_SHA256" >> $GITHUB_OUTPUT
|
|
echo "macos_arm_sha256=$MACOS_ARM_SHA256" >> $GITHUB_OUTPUT
|
|
echo "linux_intel_sha256=$LINUX_INTEL_SHA256" >> $GITHUB_OUTPUT
|
|
echo "linux_arm_sha256=$LINUX_ARM_SHA256" >> $GITHUB_OUTPUT
|
|
|
|
- name: Generate Homebrew Formula
|
|
id: formula
|
|
run: |
|
|
cat > nginx-ui.rb << 'EOF'
|
|
class NginxUi < Formula
|
|
desc "Yet another Nginx Web UI"
|
|
homepage "https://github.com/0xJacky/nginx-ui"
|
|
license "AGPL-3.0"
|
|
|
|
on_macos do
|
|
on_intel do
|
|
url "https://github.com/0xJacky/nginx-ui/releases/download/${{ steps.release.outputs.tag_name }}/nginx-ui-macos-64.tar.gz"
|
|
sha256 "${{ steps.checksums.outputs.macos_intel_sha256 }}"
|
|
end
|
|
on_arm do
|
|
url "https://github.com/0xJacky/nginx-ui/releases/download/${{ steps.release.outputs.tag_name }}/nginx-ui-macos-arm64-v8a.tar.gz"
|
|
sha256 "${{ steps.checksums.outputs.macos_arm_sha256 }}"
|
|
end
|
|
end
|
|
|
|
on_linux do
|
|
on_intel do
|
|
url "https://github.com/0xJacky/nginx-ui/releases/download/${{ steps.release.outputs.tag_name }}/nginx-ui-linux-64.tar.gz"
|
|
sha256 "${{ steps.checksums.outputs.linux_intel_sha256 }}"
|
|
end
|
|
on_arm do
|
|
url "https://github.com/0xJacky/nginx-ui/releases/download/${{ steps.release.outputs.tag_name }}/nginx-ui-linux-arm64-v8a.tar.gz"
|
|
sha256 "${{ steps.checksums.outputs.linux_arm_sha256 }}"
|
|
end
|
|
end
|
|
|
|
def install
|
|
bin.install "nginx-ui"
|
|
|
|
# Create configuration directory
|
|
(etc/"nginx-ui").mkpath
|
|
|
|
# Create default configuration file if it doesn't exist
|
|
config_file = etc/"nginx-ui/app.ini"
|
|
unless config_file.exist?
|
|
config_file.write <<~EOS
|
|
[app]
|
|
PageSize = 10
|
|
|
|
[server]
|
|
Host = 0.0.0.0
|
|
Port = 9000
|
|
RunMode = release
|
|
|
|
[cert]
|
|
HTTPChallengePort = 9180
|
|
|
|
[terminal]
|
|
StartCmd = login
|
|
EOS
|
|
end
|
|
|
|
# Create data directory
|
|
(var/"nginx-ui").mkpath
|
|
end
|
|
|
|
def post_install
|
|
# Ensure correct permissions
|
|
(var/"nginx-ui").chmod 0755
|
|
end
|
|
|
|
service do
|
|
run [opt_bin/"nginx-ui", "serve", "--config", etc/"nginx-ui/app.ini"]
|
|
keep_alive true
|
|
working_dir var/"nginx-ui"
|
|
log_path var/"log/nginx-ui.log"
|
|
error_log_path var/"log/nginx-ui.err.log"
|
|
end
|
|
|
|
test do
|
|
assert_match version.to_s, shell_output("#{bin}/nginx-ui --version")
|
|
end
|
|
end
|
|
EOF
|
|
|
|
echo "Generated Homebrew Formula:"
|
|
cat nginx-ui.rb
|
|
|
|
- name: Checkout homebrew-tools repository
|
|
uses: actions/checkout@v7
|
|
with:
|
|
repository: 0xJacky/homebrew-tools
|
|
path: homebrew-tools
|
|
token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
|
|
|
|
- name: Update Formula file
|
|
run: |
|
|
# Copy the generated formula to the correct location
|
|
mkdir -p homebrew-tools/Formula/
|
|
cp nginx-ui.rb homebrew-tools/Formula/nginx-ui.rb
|
|
|
|
- name: Verify Formula
|
|
run: |
|
|
cd homebrew-tools
|
|
# Basic syntax check
|
|
ruby -c Formula/nginx-ui.rb
|
|
echo "Formula syntax is valid"
|
|
|
|
- name: Create Pull Request to homebrew-tools
|
|
uses: peter-evans/create-pull-request@v8
|
|
with:
|
|
token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
|
|
path: homebrew-tools
|
|
branch: update-nginx-ui-${{ steps.release.outputs.version }}
|
|
delete-branch: true
|
|
title: 'nginx-ui ${{ steps.release.outputs.version }}'
|
|
body: |
|
|
Update nginx-ui to version ${{ steps.release.outputs.version }}
|
|
|
|
**Release Notes:**
|
|
- Version: ${{ steps.release.outputs.version }}
|
|
- Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag_name }}
|
|
|
|
**Checksums (SHA256):**
|
|
- macOS Intel: ${{ steps.checksums.outputs.macos_intel_sha256 }}
|
|
- macOS ARM: ${{ steps.checksums.outputs.macos_arm_sha256 }}
|
|
- Linux Intel: ${{ steps.checksums.outputs.linux_intel_sha256 }}
|
|
- Linux ARM: ${{ steps.checksums.outputs.linux_arm_sha256 }}
|
|
|
|
---
|
|
|
|
This PR was automatically generated by GitHub Actions.
|
|
commit-message: 'nginx-ui ${{ steps.release.outputs.version }}'
|
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
|
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
|
add-paths: |
|
|
Formula/nginx-ui.rb
|
|
|
|
publish-winget:
|
|
runs-on: windows-latest
|
|
needs: [build, build_macos_native]
|
|
if: github.event_name == 'release'
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Publish to WinGet
|
|
uses: vedantmgoyal9/winget-releaser@v2
|
|
with:
|
|
identifier: 0xJacky.nginx-ui
|
|
max-versions-to-keep: 2
|
|
token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
|
|
installers-regex: 'nginx-ui-windows.*\.zip$'
|