Build Octop FPK #1
Workflow file for this run
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 Octop FPK | |
| # FnOS (飞牛 NAS) 安装包流水线。 | |
| # | |
| # 触发: | |
| # - Release 工作流在 GitHub Release(v*)创建成功后自动 dispatch(推荐) | |
| # - 亦可手动 workflow_dispatch(需对应版本镜像已在 GHCR,且 v* Release 已存在) | |
| # | |
| # 制品复用: | |
| # - Docker 镜像:不再重建,等待 docker-publish 推送的 | |
| # ghcr.io/xyaistudio/freeos:{version|latest} | |
| # - Native wheel:优先从同版本 GitHub Release(v*)下载; | |
| # 缺失时再回退到本仓源码构建 | |
| # - 产物挂到同一个 v* GitHub Release(与 wheel / 桌面包并列),不再单独打 fnos-* tag | |
| # | |
| # Jobs: | |
| # version → ensure-image → fpk(Docker 版 .fpk,仅打包 compose) | |
| # ↘ native(本地版 .fpk)→ release(把 .fpk 挂到已有的 v* GitHub Release) | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| packages: read | |
| concurrency: | |
| group: build-octop-fnos | |
| cancel-in-progress: false | |
| jobs: | |
| version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.ver.outputs.version }} | |
| tag: ${{ steps.ver.outputs.tag }} | |
| pkg_ver: ${{ steps.ver.outputs.pkg_ver }} | |
| image: ${{ steps.ver.outputs.image }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Read version and image name | |
| id: ver | |
| run: | | |
| VER=$(grep -m1 '^version' pyproject.toml | sed -E 's/.*"([0-9][0-9.]*[0-9])".*/\1/') | |
| IMAGE="ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" | |
| echo "version=$VER" >> "$GITHUB_OUTPUT" | |
| echo "image=$IMAGE" >> "$GITHUB_OUTPUT" | |
| echo "tag=v${VER}" >> "$GITHUB_OUTPUT" | |
| echo "pkg_ver=${VER}" >> "$GITHUB_OUTPUT" | |
| echo "[version] VER=$VER, TAG=v${VER}" | |
| # 复用 docker-publish 已推送的镜像,不在此重新 build/push。 | |
| ensure-image: | |
| needs: version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Wait for GHCR image from docker-publish | |
| env: | |
| IMAGE: ${{ needs.version.outputs.image }} | |
| VER: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${IMAGE}:${VER}" | |
| echo "Waiting for ${TAG} (published by docker-publish)..." | |
| for i in $(seq 1 90); do | |
| if docker buildx imagetools inspect "$TAG" >/dev/null 2>&1; then | |
| echo "Image ready: $TAG" | |
| docker buildx imagetools inspect "$TAG" | head -20 | |
| exit 0 | |
| fi | |
| echo "[$i/90] not ready yet..." | |
| sleep 20 | |
| done | |
| echo "::error::Timed out waiting for ${TAG}. Ensure Docker Publish finished for v${VER}." | |
| exit 1 | |
| fpk: | |
| needs: [ensure-image, version] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Build Docker FPK | |
| env: | |
| FPK_NAME_PREFIX: Octop-fnos | |
| run: bash scripts/build-fpk.sh docker | |
| - name: Upload docker fpk artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: docker-fpk | |
| path: dist/Octop-fnos-docker-*.fpk | |
| if-no-files-found: error | |
| native: | |
| # 即使原生版构建失败也不阻塞整体发布:Docker 版(fpk)始终会发布。 | |
| continue-on-error: true | |
| needs: version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| native_ok: ${{ steps.build_native.outcome == 'success' && steps.upload_native.outcome == 'success' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Try download wheel from GitHub Release | |
| id: wheel | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VER: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| REL_TAG="v${VER}" | |
| if gh release download "$REL_TAG" --repo "${{ github.repository }}" -p 'octop-*.whl' -D dist 2>/tmp/wheel-dl.err; then | |
| ls -lh dist/octop-*.whl | |
| echo "source=release" >> "$GITHUB_OUTPUT" | |
| echo "Reusing wheel from GitHub Release ${REL_TAG}" | |
| else | |
| echo "Release wheel not available yet:" | |
| cat /tmp/wheel-dl.err || true | |
| echo "source=build" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Python 3.12 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Set up Node.js 20 (frontend build) | |
| if: steps.wheel.outputs.source == 'build' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Build Octop wheel from current source | |
| if: steps.wheel.outputs.source == 'build' | |
| run: | | |
| set -e | |
| echo "=== build frontend ===" | |
| cd dashboard | |
| npm ci --prefer-offline --no-audit | |
| NODE_ENV=production NODE_OPTIONS="--max-old-space-size=4096" npm run build | |
| cd .. | |
| echo "=== frontend output ===" | |
| ls -la src/octop/dashboard | head -20 | |
| echo "=== build wheel ===" | |
| python3 -m pip install --quiet build | |
| python3 -m build --wheel | |
| echo "=== built wheel ===" | |
| ls -lh dist/octop-*.whl | |
| - name: Install Octop core into app/site-packages (no Chromium, no preinstalled skills) | |
| id: install_core | |
| timeout-minutes: 15 | |
| env: | |
| UV_PYTHON_INSTALL_DIR: ${{ github.workspace }}/.uv-python | |
| run: | | |
| set -x | |
| LOG="${{ github.workspace }}/native-install.log" | |
| : > "$LOG" | |
| mkdir -p "${{ github.workspace }}/fnos/native/app/site-packages" | |
| SP="${{ github.workspace }}/fnos/native/app/site-packages" | |
| { | |
| python3 -m pip install --quiet uv | |
| python3 -m uv pip install --python python3.12 \ | |
| --target "$SP" --no-cache "$(ls dist/octop-*.whl | head -1)" | |
| rm -rf "$SP/mcp" "$SP"/mcp-*.dist-info \ | |
| "$SP/langchain_mcp_adapters" "$SP"/langchain_mcp_adapters-*.dist-info | |
| mkdir -p /tmp/octop-pinned | |
| python3.12 -m pip download --no-deps --no-cache-dir -d /tmp/octop-pinned \ | |
| "mcp==1.28.1" "langchain-mcp-adapters==0.3.0" | |
| for whl in /tmp/octop-pinned/*.whl; do | |
| echo "Extracting pinned wheel: $whl" | |
| python3.12 -m zipfile -e "$whl" "$SP" | |
| done | |
| cp dist/octop-*.whl "$SP/octop.whl" | |
| PYTHONPATH="$SP" python3.12 \ | |
| -c "import octop, mcp, langchain_mcp_adapters; from octop.launch import run_foreground_blocking; print('octop', octop.__version__, '| mcp', mcp.__version__, '| lcma', langchain_mcp_adapters.__version__)" | |
| } 2>&1 | tee -a "$LOG" | |
| - name: Build native FPK | |
| id: build_native | |
| env: | |
| FPK_NAME_PREFIX: Octop-fnos | |
| run: bash scripts/build-fpk.sh native | |
| - name: Show native fpk size | |
| run: ls -lh dist/Octop-fnos-native-*.fpk | |
| - name: Upload native fpk artifact | |
| id: upload_native | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: native-fpk | |
| path: dist/Octop-fnos-native-*.fpk | |
| if-no-files-found: error | |
| - name: Upload native install log (for debugging) | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: native-install-log | |
| path: native-install.log | |
| if-no-files-found: ignore | |
| release: | |
| # 即使原生版失败(continue-on-error)也要发布 Docker 版。 | |
| if: always() | |
| needs: [fpk, native, version] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Download docker fpk artifact (required) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: docker-fpk | |
| path: artifacts/docker-fpk | |
| if-no-files-found: error | |
| - name: Download native fpk artifact (best-effort) | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: native-fpk | |
| path: artifacts/native-fpk | |
| if-no-files-found: ignore | |
| - name: Stage dist | |
| run: | | |
| mkdir -p dist | |
| cp artifacts/docker-fpk/*.fpk dist/ || true | |
| cp artifacts/native-fpk/*.fpk dist/ || true | |
| ls -lh dist | |
| if ! ls dist/*.fpk >/dev/null 2>&1; then | |
| echo "::error::No .fpk files to attach." | |
| exit 1 | |
| fi | |
| - name: Require existing v* GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| if ! gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null; then | |
| echo "::error::GitHub Release ${TAG} does not exist yet. Run this after Release.yml creates it." | |
| exit 1 | |
| fi | |
| - name: Attach fpk to GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.version.outputs.tag }} | |
| files: dist/*.fpk | |
| fail_on_unmatched_files: true | |
| generate_release_notes: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |