Skip to content

Build Octop FPK

Build Octop FPK #1

Workflow file for this run

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 }}