fix(diagnostics): expose traceable upload errors #700
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release Docker Image | |
| on: | |
| push: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Docker image tag (leave empty for auto-generated)' | |
| required: false | |
| default: '' | |
| permissions: | |
| contents: write | |
| env: | |
| DOCKER_IMAGE_NAME: tx-5dr | |
| PLATFORMS: linux/amd64,linux/arm64 | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| OSS_ACCESS_KEY_ID: ${{ secrets.ALIYUN_ACCESS_KEY_ID }} | |
| OSS_ACCESS_KEY_SECRET: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }} | |
| OSS_BUCKET: ${{ secrets.OSS_BUCKET }} | |
| OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }} | |
| OSS_BASE_URL: ${{ secrets.OSS_BASE_URL }} | |
| OSS_REGION: ${{ secrets.OSS_REGION }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Generate image tags | |
| id: tags | |
| run: | | |
| # Docker Hub 用户名从 secrets 获取 | |
| DOCKER_USERNAME="${{ secrets.DOCKER_HUB_USERNAME }}" | |
| # 如果用户名未设置,使用默认值(仅用于显示,不会推送) | |
| if [ -z "$DOCKER_USERNAME" ]; then | |
| echo "⚠️ Warning: DOCKER_HUB_USERNAME not set" | |
| DOCKER_USERNAME="your-dockerhub-username" | |
| fi | |
| # 生成标签 | |
| TIMESTAMP=$(date +%Y%m%d-%H%M%S) | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| # 基础标签 | |
| TAGS="${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:latest" | |
| TAGS="${TAGS},${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:${SHORT_SHA}" | |
| # 如果是手动触发且指定了标签 | |
| if [ -n "${{ github.event.inputs.tag }}" ]; then | |
| TAGS="${TAGS},${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}:${{ github.event.inputs.tag }}" | |
| fi | |
| echo "tags=${TAGS}" >> $GITHUB_OUTPUT | |
| echo "docker_username=${DOCKER_USERNAME}" >> $GITHUB_OUTPUT | |
| echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT | |
| echo "📦 Generated tags:" | |
| echo "${TAGS}" | tr ',' '\n' | |
| - name: Generate release info | |
| id: release_info | |
| run: | | |
| echo "timestamp=$(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: linux/amd64,linux/arm64 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| with: | |
| driver-opts: | | |
| image=moby/buildkit:latest | |
| network=host | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| password: ${{ secrets.DOCKER_HUB_TOKEN }} | |
| - name: Build and push Docker image | |
| id: docker_build | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| platforms: ${{ env.PLATFORMS }} | |
| push: true | |
| tags: ${{ steps.tags.outputs.tags }} | |
| cache-from: type=registry,ref=${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:buildcache | |
| cache-to: type=registry,ref=${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:buildcache,mode=max | |
| build-args: | | |
| BUILD_DATE=${{ github.event.head_commit.timestamp }} | |
| VCS_REF=${{ github.sha }} | |
| TX5DR_CLUBLOG_API_KEY=${{ secrets.CLUBLOG_API_KEY }} | |
| labels: | | |
| org.opencontainers.image.title=${{ env.DOCKER_IMAGE_NAME }} | |
| org.opencontainers.image.description=TX-5DR Digital Radio System | |
| org.opencontainers.image.url=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.source=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.version=${{ steps.tags.outputs.short_sha }} | |
| org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| - name: Update Docker Hub description | |
| uses: peter-evans/dockerhub-description@v3 | |
| continue-on-error: true | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| password: ${{ secrets.DOCKER_HUB_TOKEN }} | |
| repository: ${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }} | |
| readme-filepath: ./docker/README.md | |
| - name: Build summary | |
| run: | | |
| echo "## 🎉 Docker 镜像构建成功" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 📦 镜像信息" >> $GITHUB_STEP_SUMMARY | |
| echo "- **仓库**: \`${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **架构**: \`linux/amd64, linux/arm64\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **提交**: \`${{ steps.tags.outputs.short_sha }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 🏷️ 标签" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.tags.outputs.tags }}" | tr ',' '\n' >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 🚀 使用方法" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY | |
| echo "# 拉取最新镜像" >> $GITHUB_STEP_SUMMARY | |
| echo "docker pull ${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "# 运行容器" >> $GITHUB_STEP_SUMMARY | |
| echo "docker run -d -p 8076:80 --name tx-5dr ${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "# 或使用 docker-compose" >> $GITHUB_STEP_SUMMARY | |
| echo "docker-compose up -d" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 🔗 链接" >> $GITHUB_STEP_SUMMARY | |
| echo "- [Docker Hub]( https://hub.docker.com/r/${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }})" >> $GITHUB_STEP_SUMMARY | |
| echo "- [GitHub Repository](https://github.com/${{ github.repository }})" >> $GITHUB_STEP_SUMMARY | |
| - name: Create or Update Nightly Docker Release | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| tag: nightly-docker | |
| name: "Nightly Build — Docker Image" | |
| body: | | |
| ## TX-5DR Nightly Build — Docker Image Docker 镜像 | |
| **Built at** (构建时间): ${{ steps.release_info.outputs.timestamp }} | |
| **Commit**: [${{ steps.tags.outputs.short_sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | |
| **Image Digest**: `${{ steps.docker_build.outputs.digest }}` | |
| --- | |
| ### 🚀 Quick Start 快速启动 | |
| ```bash | |
| # Pull image / 拉取镜像 | |
| docker pull ${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:latest | |
| # Run / 启动 | |
| docker run -d -p 8076:80 --name tx-5dr \ | |
| ${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:latest | |
| # View admin token / 查看管理员令牌 | |
| docker exec tx-5dr cat /app/data/config/.admin-token | |
| ``` | |
| ### 📋 Docker Compose | |
| ```yaml | |
| version: '3.8' | |
| services: | |
| tx5dr: | |
| image: ${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}:latest | |
| container_name: tx5dr | |
| restart: unless-stopped | |
| ports: | |
| - "8076:80" | |
| volumes: | |
| - ./data/config:/app/data/config | |
| - ./data/logs:/app/data/logs | |
| - /dev/snd:/dev/snd:rw # Audio devices / 音频设备 | |
| devices: | |
| - /dev/bus/usb:/dev/bus/usb:rwm # USB devices / USB 设备 | |
| group_add: | |
| - audio | |
| ``` | |
| ### 🔧 Environment Variables 环境变量 | |
| | Variable 变量 | Default 默认值 | Description 说明 | | |
| |---|---|---| | |
| | `PORT` | `4000` | Backend API port (internal) / 后端 API 端口(内部) | | |
| | `TX5DR_CONFIG_DIR` | `/app/data/config` | Config directory / 配置目录 | | |
| | `TX5DR_LOGS_DIR` | `/app/data/logs` | Log directory / 日志目录 | | |
| ### 🏷️ Image Tags 镜像标签 | |
| - `latest` — always points to newest build / 始终指向最新构建 | |
| - `${{ steps.tags.outputs.short_sha }}` — pinned to this commit / 锁定到此提交 | |
| ### 📦 Architectures 支持架构 | |
| - `linux/amd64` (x86_64) | |
| - `linux/arm64` (aarch64) | |
| ### 🔗 Links 链接 | |
| - [Docker Hub](https://hub.docker.com/r/${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}) | |
| - [Desktop App / 桌面应用](https://github.com/${{ github.repository }}/releases/tag/nightly-app) | |
| - [Linux Server / 服务器版](https://github.com/${{ github.repository }}/releases/tag/nightly-server) | |
| allowUpdates: true | |
| artifacts: "" | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| prerelease: true | |
| makeLatest: false | |
| - name: Install ossutil | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| run: bash scripts/install-ossutil.sh | |
| - name: Configure ossutil | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| shell: bash | |
| run: | | |
| oss_region="${OSS_REGION:-}" | |
| oss_endpoint_host="${OSS_ENDPOINT#https://}" | |
| oss_endpoint_host="${oss_endpoint_host#http://}" | |
| oss_endpoint_host="${oss_endpoint_host%%/*}" | |
| if [ -z "${oss_region}" ]; then | |
| if [[ "${oss_endpoint_host}" =~ oss-([a-z0-9-]+)\.aliyuncs\.com$ ]]; then | |
| oss_region="${BASH_REMATCH[1]}" | |
| fi | |
| fi | |
| if [ -z "${oss_region}" ]; then | |
| echo "Failed to determine OSS region from OSS_ENDPOINT=${OSS_ENDPOINT}" >&2 | |
| echo "Please configure the OSS_REGION GitHub secret, for example: cn-hangzhou" >&2 | |
| exit 1 | |
| fi | |
| echo "Using OSS endpoint: ${OSS_ENDPOINT}" | |
| echo "Using OSS region: ${oss_region}" | |
| cat > "${RUNNER_TEMP}/.ossutilconfig" <<EOF | |
| [Credentials] | |
| language = EN | |
| endpoint = ${OSS_ENDPOINT} | |
| region = ${oss_region} | |
| accessKeyID = ${OSS_ACCESS_KEY_ID} | |
| accessKeySecret = ${OSS_ACCESS_KEY_SECRET} | |
| EOF | |
| - name: Generate OSS docker manifest | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| run: | | |
| node scripts/generate-oss-manifest.mjs \ | |
| --product docker \ | |
| --channel nightly \ | |
| --tag "nightly-docker" \ | |
| --version "${{ steps.tags.outputs.short_sha }}" \ | |
| --commit "${{ steps.tags.outputs.short_sha }}" \ | |
| --published-at "${{ steps.release_info.outputs.timestamp }}" \ | |
| --base-url "${OSS_BASE_URL}" \ | |
| --object-prefix "tx-5dr/docker/nightly-docker" \ | |
| --output "oss-metadata/docker/latest.json" \ | |
| --docker-image "${{ steps.tags.outputs.docker_username }}/${{ env.DOCKER_IMAGE_NAME }}" \ | |
| --docker-tags "${{ steps.tags.outputs.tags }}" \ | |
| --docker-digest "${{ steps.docker_build.outputs.digest }}" | |
| - name: Upload docker metadata to OSS | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| run: | | |
| ossutil cp "oss-metadata/docker/latest.json" "oss://${OSS_BUCKET}/tx-5dr/docker/nightly-docker/latest.json" --force -c "${RUNNER_TEMP}/.ossutilconfig" | |
| ossutil cp "oss-metadata/docker/latest.json" "oss://${OSS_BUCKET}/tx-5dr/docker/latest.json" --force -c "${RUNNER_TEMP}/.ossutilconfig" |