fix(dev): use current Node runtime in worktrees #906
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 Linux Server | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release version (e.g., v1.0.0, leave empty for nightly)' | |
| required: false | |
| default: '' | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-server-linux: | |
| name: Build Server Package (Linux ${{ matrix.arch }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-22.04 | |
| arch: amd64 | |
| artifact_name: tx5dr-server-linux-amd64 | |
| - os: ubuntu-22.04-arm | |
| arch: arm64 | |
| artifact_name: tx5dr-server-linux-arm64 | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 20 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.15.1' | |
| - name: Enable Corepack and setup Yarn | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@4.9.1 --activate | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libasound2-dev libpulse-dev \ | |
| libhamlib-dev libhamlib4 \ | |
| libopus-dev \ | |
| build-essential python3-dev pkg-config \ | |
| ruby-dev rpm | |
| - name: Install fpm | |
| run: sudo gem install fpm --no-document | |
| - name: Install dependencies | |
| run: yarn install | |
| - name: Prepare server build metadata | |
| env: | |
| RELEASE_VERSION: ${{ github.event.inputs.version }} | |
| run: | | |
| BASE_VERSION=$(node -p "require('./package.json').version.split('-')[0].split('+')[0]") | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| COMMIT_STAMP=$(git show -s --date=format-local:%Y%m%d%H%M --format=%cd "${{ github.sha }}") | |
| if [ -z "$RELEASE_VERSION" ]; then | |
| node scripts/prepare-server-build-info.mjs \ | |
| --channel nightly \ | |
| --version "${BASE_VERSION}-nightly.${COMMIT_STAMP}+${SHORT_SHA}" \ | |
| --commit "${{ github.sha }}" \ | |
| --build-stamp "$COMMIT_STAMP" \ | |
| --distribution linux-service | |
| else | |
| node scripts/prepare-server-build-info.mjs \ | |
| --channel release \ | |
| --version "${RELEASE_VERSION#v}" \ | |
| --commit "${{ github.sha }}" \ | |
| --build-stamp "$COMMIT_STAMP" \ | |
| --distribution linux-service | |
| fi | |
| - name: Build application | |
| run: yarn build | |
| env: | |
| NODE_ENV: production | |
| TX5DR_CLUBLOG_API_KEY: ${{ secrets.CLUBLOG_API_KEY }} | |
| - name: Build server packages (deb + rpm) | |
| run: scripts/package-linux.sh both --no-build | |
| env: | |
| TX5DR_DOWNLOAD_BASE_URL: ${{ secrets.OSS_BASE_URL }} | |
| - name: Verify packaged Image Radio runtime | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DEB_FILE=$(find dist -name '*.deb' -type f | head -1) | |
| if [ -z "$DEB_FILE" ]; then | |
| echo "Server DEB package not found" >&2 | |
| exit 1 | |
| fi | |
| EXTRACT_DIR=$(mktemp -d) | |
| trap 'rm -rf "$EXTRACT_DIR"' EXIT | |
| dpkg-deb -x "$DEB_FILE" "$EXTRACT_DIR" | |
| APP_ROOT="$EXTRACT_DIR/usr/share/tx5dr" | |
| export NODE_PATH="$APP_ROOT/node_modules" | |
| (cd "$APP_ROOT/packages/server/dist/scripts" && node -e ' | |
| const { PNG } = require("pngjs"); | |
| if (typeof PNG.sync.read !== "function") throw new Error("pngjs unavailable"); | |
| const rasterwave = require("rasterwave-node"); | |
| if (rasterwave.sstvModes().length !== 31) throw new Error("rasterwave mode catalog incomplete"); | |
| if (typeof rasterwave.correctFaxPaper !== "function") throw new Error("radiofax correction unavailable"); | |
| const mode = rasterwave.sstvModes().find((item) => item.mode === "robot8Bw"); | |
| const pixels = new Uint8Array(mode.width * mode.height * 3); | |
| const encoders = [ | |
| { enhancedPreamble: true, stationId: { kind: "fsk", callsign: "N0CALL" }, postImageGapMs: 500, endGuardMs: 300 }, | |
| { stationId: { kind: "cw", callsign: "N0CALL", wpm: 20, toneHz: 800 }, postImageGapMs: 500, endGuardMs: 300 }, | |
| { stationId: { kind: "none" }, endGuardMs: 300 }, | |
| ].map((options) => new rasterwave.SstvEncoder(pixels, mode.mode, 12000, options)); | |
| if (encoders.some((encoder) => encoder.progress.rasterEndSample <= 0)) throw new Error("SSTV envelope progress unavailable"); | |
| Promise.all([ | |
| ...encoders.map((encoder) => encoder.dispose()), | |
| new rasterwave.SstvDecoder(12000, { outputMode: "continuousPaper", fallbackMode: "robot36", queueCapacitySamples: 24000 }, () => {}).dispose(), | |
| new rasterwave.FaxDecoder(12000, { outputMode: "continuousPaper", continuousAuto: true, queueCapacitySamples: 24000 }, () => {}).dispose(), | |
| rasterwave.correctFaxPaper(new Uint8Array(8), 4, 2, 0, []), | |
| ]).then(() => console.log("Packaged Image Radio runtime ok")).catch((error) => { console.error(error); process.exit(1); }); | |
| ') | |
| - name: Prepare artifacts | |
| run: | | |
| VERSION="${{ github.event.inputs.version }}" | |
| ARCH="${{ matrix.arch }}" | |
| mkdir -p release-artifacts | |
| if [ -z "$VERSION" ]; then | |
| PREFIX="TX-5DR-nightly" | |
| else | |
| PREFIX="TX-5DR-${VERSION}" | |
| fi | |
| find dist/ -name "*.deb" -type f | while read -r f; do | |
| cp "$f" "release-artifacts/${PREFIX}-server-linux-${ARCH}.deb" | |
| echo "✅ deb: ${PREFIX}-server-linux-${ARCH}.deb" | |
| done | |
| find dist/ -name "*.rpm" -type f | while read -r f; do | |
| cp "$f" "release-artifacts/${PREFIX}-server-linux-${ARCH}.rpm" | |
| echo "✅ rpm: ${PREFIX}-server-linux-${ARCH}.rpm" | |
| done | |
| ls -lh release-artifacts/ | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: release-artifacts/* | |
| retention-days: 30 | |
| create-release: | |
| needs: [build-server-linux] | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' | |
| 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 | |
| with: | |
| fetch-depth: 20 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist/ | |
| - name: Build install-online.sh | |
| env: | |
| TX5DR_DOWNLOAD_BASE_URL: ${{ secrets.OSS_BASE_URL }} | |
| run: | | |
| bash scripts/build-install-script.sh | |
| cp dist/install-online.sh release-assets/ 2>/dev/null || true | |
| - name: Prepare release assets | |
| run: | | |
| mkdir -p publish-assets | |
| mkdir -p release-assets | |
| find dist/ -type f \( -name "*.deb" -o -name "*.rpm" \) -exec cp {} publish-assets/ \; | |
| # install-online.sh was already copied above | |
| cp dist/install-online.sh publish-assets/ 2>/dev/null || true | |
| find publish-assets/ -type f -exec cp {} release-assets/ \; | |
| ls -lh release-assets/ | |
| - name: Generate release info | |
| id: release_info | |
| env: | |
| TZ: UTC | |
| run: | | |
| echo "timestamp=$(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT | |
| echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT | |
| echo "commit_title=$(git show -s --format=%s "${{ github.sha }}")" >> $GITHUB_OUTPUT | |
| echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT | |
| BASE_VERSION=$(node -p "require('./package.json').version.split('-')[0].split('+')[0]") | |
| COMMIT_STAMP=$(git show -s --date=format-local:%Y%m%d%H%M --format=%cd "${{ github.sha }}") | |
| if [ -z "${{ github.event.inputs.version }}" ]; then | |
| echo "tag=nightly-server" >> $GITHUB_OUTPUT | |
| echo "version=${BASE_VERSION}-nightly.${COMMIT_STAMP}+$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT | |
| echo "channel=nightly" >> $GITHUB_OUTPUT | |
| echo "channel_path=nightly" >> $GITHUB_OUTPUT | |
| else | |
| VERSION_NO_V="${{ github.event.inputs.version }}" | |
| echo "tag=${{ github.event.inputs.version }}-server" >> $GITHUB_OUTPUT | |
| echo "version=${VERSION_NO_V#v}" >> $GITHUB_OUTPUT | |
| echo "channel=release" >> $GITHUB_OUTPUT | |
| echo "channel_path=release" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Prepare server release metadata | |
| run: | | |
| mkdir -p release-metadata | |
| cat > release-metadata/server-release-notes.md <<EOF | |
| Channel: ${{ steps.release_info.outputs.channel }} | |
| Version: ${{ steps.release_info.outputs.version }} | |
| Commit: ${{ steps.release_info.outputs.short_sha }} | |
| Built at: ${{ steps.release_info.outputs.timestamp }} | |
| EOF | |
| node - <<'EOF' > release-metadata/recent-commits.json | |
| const { execFileSync } = require('node:child_process'); | |
| const lines = execFileSync('git', [ | |
| 'log', | |
| '-n', | |
| '10', | |
| '--date=iso-strict', | |
| '--pretty=format:%H%x09%h%x09%s%x09%cI', | |
| process.env.GITHUB_SHA, | |
| ], { encoding: 'utf8' }).trim().split('\n').filter(Boolean); | |
| const commits = lines.map((line) => { | |
| const [id, shortId, title, publishedAt] = line.split('\t'); | |
| return { id, short_id: shortId, title, published_at: publishedAt }; | |
| }); | |
| process.stdout.write(JSON.stringify(commits)); | |
| EOF | |
| node scripts/generate-oss-manifest.mjs \ | |
| --product server \ | |
| --channel "${{ steps.release_info.outputs.channel }}" \ | |
| --tag "${{ steps.release_info.outputs.tag }}" \ | |
| --version "${{ steps.release_info.outputs.version }}" \ | |
| --commit "${{ steps.release_info.outputs.short_sha }}" \ | |
| --commit-title "${{ steps.release_info.outputs.commit_title }}" \ | |
| --published-at "${{ steps.release_info.outputs.timestamp }}" \ | |
| --base-url "${{ secrets.OSS_BASE_URL }}" \ | |
| --object-prefix "tx-5dr/server/${{ steps.release_info.outputs.tag }}" \ | |
| --oss-base-url "${{ secrets.OSS_BASE_URL }}" \ | |
| --oss-object-prefix "tx-5dr/server/${{ steps.release_info.outputs.tag }}" \ | |
| --github-base-url "https://github.com/${{ github.repository }}/releases/download" \ | |
| --github-object-prefix "${{ steps.release_info.outputs.tag }}" \ | |
| --assets-dir "publish-assets" \ | |
| --release-notes-file "release-metadata/server-release-notes.md" \ | |
| --recent-commits-json "$(cat release-metadata/recent-commits.json)" \ | |
| --output "release-assets/latest.json" | |
| - name: Validate server manifest | |
| shell: bash | |
| run: | | |
| node - <<'EOF' | |
| const fs = require('node:fs'); | |
| const manifest = JSON.parse(fs.readFileSync('release-assets/latest.json', 'utf8')); | |
| if (!manifest.version) throw new Error('server manifest missing version'); | |
| if (!manifest.commit_title) throw new Error('server manifest missing commit_title'); | |
| if (!Array.isArray(manifest.recent_commits) || manifest.recent_commits.length === 0 || manifest.recent_commits.length > 10) { | |
| throw new Error('server manifest missing recent_commits'); | |
| } | |
| if (manifest.recent_commits[0]?.short_id !== manifest.commit) { | |
| throw new Error('server manifest recent_commits[0] does not match top-level commit'); | |
| } | |
| if (manifest.recent_commits[0]?.title !== manifest.commit_title) { | |
| throw new Error('server manifest recent_commits[0] does not match top-level commit_title'); | |
| } | |
| if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) { | |
| throw new Error('server manifest missing assets'); | |
| } | |
| const hasDeb = manifest.assets.some((asset) => asset.platform === 'linux' && asset.package_type === 'deb' && asset.url_cn && asset.url_global); | |
| const hasRpm = manifest.assets.some((asset) => asset.platform === 'linux' && asset.package_type === 'rpm' && asset.url_cn && asset.url_global); | |
| if (!hasDeb || !hasRpm) throw new Error('server manifest missing linux packages with dual urls'); | |
| EOF | |
| # Nightly Release | |
| - name: Create or Update Nightly Release | |
| if: github.event.inputs.version == '' | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| tag: nightly-server | |
| name: "Nightly Build — Linux Server" | |
| body: | | |
| ## TX-5DR Nightly Build — Linux Server 服务器版 | |
| **Built at** (构建时间): ${{ steps.release_info.outputs.timestamp }} | |
| **Commit**: [${{ steps.release_info.outputs.short_sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | |
| **Branch**: ${{ github.ref_name }} | |
| ### 📦 Downloads 下载 | |
| #### Linux Server (x64) | |
| - **DEB Package** (Ubuntu/Debian): `TX-5DR-nightly-server-linux-amd64.deb` | |
| - **RPM Package** (Fedora/RHEL): `TX-5DR-nightly-server-linux-amd64.rpm` | |
| #### Linux Server (ARM64) | |
| - **DEB Package** (Ubuntu/Debian): `TX-5DR-nightly-server-linux-arm64.deb` | |
| - **RPM Package** (Fedora/RHEL): `TX-5DR-nightly-server-linux-arm64.rpm` | |
| --- | |
| ### 🚀 Quick Install 一键安装 | |
| ```bash | |
| curl -fsSL https://github.com/${{ github.repository }}/releases/download/nightly-server/install-online.sh | sudo bash | |
| ``` | |
| ### 📖 Manual Install 手动安装 | |
| ```bash | |
| curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash - | |
| sudo apt install -y nodejs nginx | |
| curl -fSL -O https://github.com/${{ github.repository }}/releases/download/nightly-server/TX-5DR-nightly-server-linux-amd64.deb | |
| sudo dpkg -i --force-depends TX-5DR-nightly-server-linux-amd64.deb | |
| sudo bash /usr/share/tx5dr/install.sh | |
| tx5dr start | |
| ``` | |
| ### 🛠 Linux Server Commands 指令与用法 | |
| | Command 命令 | Usage 用法 | Description 说明 | | |
| |---|---|---| | |
| | `tx5dr start` | `tx5dr start` | Start server and verify startup; prints Web UI URL / 启动服务并校验启动结果,同时输出访问地址 | | |
| | `tx5dr stop` | `tx5dr stop` | Stop TX-5DR server / 停止服务 | | |
| | `tx5dr restart` | `tx5dr restart` | Restart TX-5DR server / 重启服务 | | |
| | `tx5dr status` | `tx5dr status` | Show dashboard for server, nginx, rtc-data-audio UDP, token, and config paths / 显示服务、nginx、rtc-data-audio UDP、令牌和配置路径 | | |
| | `tx5dr logs` | `tx5dr logs` | Follow TX-5DR logs / 跟踪 TX-5DR 日志 | | |
| | `tx5dr logs --nginx` | `tx5dr logs --nginx` | Follow nginx error log / 跟踪 nginx 错误日志 | | |
| | `tx5dr logs --all` | `tx5dr logs --all` | Follow TX-5DR + nginx logs / 同时跟踪 TX-5DR 和 nginx | | |
| | `tx5dr token` | `tx5dr token` | Show admin token and login URL / 显示管理员令牌和登录地址 | | |
| | `tx5dr token --reset` | `tx5dr token --reset` | Regenerate admin token / 重建管理员令牌 | | |
| | `tx5dr doctor` | `tx5dr doctor` | Run full environment diagnostics / 执行完整环境诊断 | | |
| | `tx5dr update` | `tx5dr update` | Download and install latest nightly server build / 下载并安装最新 nightly 服务器版 | | |
| | `tx5dr enable` | `tx5dr enable` | Enable auto-start on boot / 开机自动启动 | | |
| | `tx5dr disable` | `tx5dr disable` | Disable auto-start on boot / 关闭开机自启 | | |
| | `tx5dr version` | `tx5dr version` | Show installed version / 显示当前安装版本 | | |
| ### 🧩 Service Layout 服务关系 | |
| Linux Server is not a single standalone process. The installer sets up `tx5dr` (backend with embedded rtc-data-audio on UDP 50110) and `nginx` (public reverse proxy / HTTPS). | |
| Linux 服务器版并不是单个独立进程。安装脚本会自动安装并编排 `tx5dr`(后端,内置 UDP 50110 上的 rtc-data-audio)以及 `nginx`(对外反向代理 / HTTPS)。 | |
| ### 🔒 SSL Notice SSL 提示 | |
| Voice features (browser microphone) require HTTPS. Configure SSL in `/etc/nginx/conf.d/tx5dr.conf`. | |
| 语音通联功能需要 HTTPS。请在 `/etc/nginx/conf.d/tx5dr.conf` 中配置 SSL。 | |
| ### ⚠️ Notice 注意事项 | |
| - This is a development build. (开发版本,可能包含未经充分测试的功能) | |
| - Automatically updated on every push to `main` / `develop`. (每次推送自动更新) | |
| ### 🔧 System Requirements 系统要求 | |
| - Debian 12+ / Ubuntu 22.04+ (Debian 12 recommended / 推荐 Debian 12) | |
| - Node.js 22+ (auto-installed via `install.sh` / 通过安装脚本自动安装) | |
| - nginx (auto-installed / 自动安装) | |
| > Looking for the **Desktop App** (Windows/macOS/Linux with GUI)? See [nightly-app](https://github.com/${{ github.repository }}/releases/tag/nightly-app). | |
| > 寻找**桌面应用**(Windows/macOS/Linux 图形界面)?请查看 [nightly-app](https://github.com/${{ github.repository }}/releases/tag/nightly-app)。 | |
| allowUpdates: true | |
| removeArtifacts: true | |
| artifacts: "release-assets/*" | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| prerelease: true | |
| makeLatest: false | |
| # Official Release | |
| - name: Create Official Release | |
| if: github.event.inputs.version != '' | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| tag: ${{ github.event.inputs.version }}-server | |
| name: "TX-5DR Server ${{ github.event.inputs.version }}" | |
| body: | | |
| ## TX-5DR Server ${{ github.event.inputs.version }} | |
| **Release Date** (发布日期): ${{ steps.release_info.outputs.date }} | |
| **Commit**: [${{ steps.release_info.outputs.short_sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | |
| ### 📦 Downloads 下载 | |
| #### Linux Server (x64) | |
| - **DEB Package**: `TX-5DR-${{ github.event.inputs.version }}-server-linux-amd64.deb` | |
| - **RPM Package**: `TX-5DR-${{ github.event.inputs.version }}-server-linux-amd64.rpm` | |
| #### Linux Server (ARM64) | |
| - **DEB Package**: `TX-5DR-${{ github.event.inputs.version }}-server-linux-arm64.deb` | |
| - **RPM Package**: `TX-5DR-${{ github.event.inputs.version }}-server-linux-arm64.rpm` | |
| ### 🚀 Quick Install 一键安装 | |
| ```bash | |
| curl -fsSL https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.version }}-server/install-online.sh | sudo bash | |
| ``` | |
| ### 📖 Manual Install 手动安装 | |
| ```bash | |
| curl -fSL -O https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.version }}-server/TX-5DR-${{ github.event.inputs.version }}-server-linux-amd64.deb | |
| sudo dpkg -i --force-depends TX-5DR-${{ github.event.inputs.version }}-server-linux-amd64.deb | |
| sudo bash /usr/share/tx5dr/install.sh | |
| tx5dr start | |
| ``` | |
| ### 🛠 Linux Server Commands 指令与用法 | |
| | Command 命令 | Usage 用法 | Description 说明 | | |
| |---|---|---| | |
| | `tx5dr start` | `tx5dr start` | Start server and verify startup; prints Web UI URL / 启动服务并校验启动结果,同时输出访问地址 | | |
| | `tx5dr stop` | `tx5dr stop` | Stop TX-5DR server / 停止服务 | | |
| | `tx5dr restart` | `tx5dr restart` | Restart TX-5DR server / 重启服务 | | |
| | `tx5dr status` | `tx5dr status` | Show dashboard for server, nginx, rtc-data-audio UDP, token, and config paths / 显示服务、nginx、rtc-data-audio UDP、令牌和配置路径 | | |
| | `tx5dr logs` | `tx5dr logs` | Follow TX-5DR logs / 跟踪 TX-5DR 日志 | | |
| | `tx5dr logs --nginx` | `tx5dr logs --nginx` | Follow nginx error log / 跟踪 nginx 错误日志 | | |
| | `tx5dr logs --all` | `tx5dr logs --all` | Follow TX-5DR + nginx logs / 同时跟踪 TX-5DR 和 nginx | | |
| | `tx5dr token` | `tx5dr token` | Show admin token and login URL / 显示管理员令牌和登录地址 | | |
| | `tx5dr token --reset` | `tx5dr token --reset` | Regenerate admin token / 重建管理员令牌 | | |
| | `tx5dr doctor` | `tx5dr doctor` | Run full environment diagnostics / 执行完整环境诊断 | | |
| | `tx5dr update` | `tx5dr update` | Download and install latest nightly server build / 下载并安装最新 nightly 服务器版 | | |
| | `tx5dr enable` | `tx5dr enable` | Enable auto-start on boot / 开机自动启动 | | |
| | `tx5dr disable` | `tx5dr disable` | Disable auto-start on boot / 关闭开机自启 | | |
| | `tx5dr version` | `tx5dr version` | Show installed version / 显示当前安装版本 | | |
| ### 🧩 Service Layout 服务关系 | |
| Linux Server is not a single standalone process. The installer sets up `tx5dr` (backend with embedded rtc-data-audio on UDP 50110) and `nginx` (public reverse proxy / HTTPS). | |
| Linux 服务器版并不是单个独立进程。安装脚本会自动安装并编排 `tx5dr`(后端,内置 UDP 50110 上的 rtc-data-audio)以及 `nginx`(对外反向代理 / HTTPS)。 | |
| ### 🔧 System Requirements 系统要求 | |
| - Debian 12+ / Ubuntu 22.04+, Node.js 22+, nginx | |
| --- | |
| Built at (构建于): ${{ steps.release_info.outputs.timestamp }} | |
| artifacts: "release-assets/*" | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| draft: false | |
| prerelease: false | |
| 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 server manifest | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| run: | | |
| mkdir -p oss-metadata/server | |
| node scripts/generate-oss-manifest.mjs \ | |
| --product server \ | |
| --channel "${{ steps.release_info.outputs.channel }}" \ | |
| --tag "${{ steps.release_info.outputs.tag }}" \ | |
| --version "${{ steps.release_info.outputs.version }}" \ | |
| --commit "${{ steps.release_info.outputs.short_sha }}" \ | |
| --commit-title "${{ steps.release_info.outputs.commit_title }}" \ | |
| --published-at "${{ steps.release_info.outputs.timestamp }}" \ | |
| --base-url "${OSS_BASE_URL}" \ | |
| --object-prefix "tx-5dr/server/${{ steps.release_info.outputs.tag }}" \ | |
| --oss-base-url "${OSS_BASE_URL}" \ | |
| --oss-object-prefix "tx-5dr/server/${{ steps.release_info.outputs.tag }}" \ | |
| --github-base-url "https://github.com/${{ github.repository }}/releases/download" \ | |
| --github-object-prefix "${{ steps.release_info.outputs.tag }}" \ | |
| --assets-dir "publish-assets" \ | |
| --release-notes-file "release-metadata/server-release-notes.md" \ | |
| --recent-commits-json "$(cat release-metadata/recent-commits.json)" \ | |
| --output "oss-metadata/server/latest.json" | |
| - name: Validate OSS server manifest | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| shell: bash | |
| run: | | |
| node - <<'EOF' | |
| const fs = require('node:fs'); | |
| const manifest = JSON.parse(fs.readFileSync('oss-metadata/server/latest.json', 'utf8')); | |
| if (!Array.isArray(manifest.recent_commits) || manifest.recent_commits.length === 0 || manifest.recent_commits.length > 10) { | |
| throw new Error('oss server manifest missing recent_commits'); | |
| } | |
| if (manifest.recent_commits[0]?.short_id !== manifest.commit) { | |
| throw new Error('oss server manifest recent_commits[0] does not match top-level commit'); | |
| } | |
| if (manifest.recent_commits[0]?.title !== manifest.commit_title) { | |
| throw new Error('oss server manifest recent_commits[0] does not match top-level commit_title'); | |
| } | |
| EOF | |
| - name: Upload server assets to OSS | |
| if: env.OSS_ACCESS_KEY_ID != '' && env.OSS_ACCESS_KEY_SECRET != '' && env.OSS_BUCKET != '' && env.OSS_ENDPOINT != '' && env.OSS_BASE_URL != '' | |
| run: | | |
| for file in publish-assets/*; do | |
| ossutil cp "$file" "oss://${OSS_BUCKET}/tx-5dr/server/${{ steps.release_info.outputs.tag }}/$(basename "$file")" --force -c "${RUNNER_TEMP}/.ossutilconfig" | |
| done | |
| ossutil cp "oss-metadata/server/latest.json" "oss://${OSS_BUCKET}/tx-5dr/server/${{ steps.release_info.outputs.tag }}/latest.json" --force -c "${RUNNER_TEMP}/.ossutilconfig" | |
| ossutil cp "oss-metadata/server/latest.json" "oss://${OSS_BUCKET}/tx-5dr/server/${{ steps.release_info.outputs.channel_path }}/latest.json" --force -c "${RUNNER_TEMP}/.ossutilconfig" | |
| ossutil cp "oss-metadata/server/latest.json" "oss://${OSS_BUCKET}/tx-5dr/server/latest.json" --force -c "${RUNNER_TEMP}/.ossutilconfig" | |
| if [ -f "publish-assets/install-online.sh" ]; then | |
| ossutil cp "publish-assets/install-online.sh" "oss://${OSS_BUCKET}/tx-5dr/server/latest/install-online.sh" --force -c "${RUNNER_TEMP}/.ossutilconfig" | |
| fi |