visionA/local-agent/Makefile
jim800121chen 3f0175f1a9 feat(local-agent): Phase 0.5 visionA Agent — Wails 桌面 + tunnel client + 配對 UI
從 local-tool 複製出獨立的「visionA Agent」桌面應用(A3 純橋樑:
tunnel client + 配對 UI + 設定,不開 HTTP port、不做本機裝置/推論 UI)。
Bundle ID 與 local-tool 不同(com.innovedus.visiona-agent vs visiona-local),
雙 app 可共存。fork 後不主動 sync,需要時手動 cherry-pick。

Backend / Wails Go(AB1-AB13):
- internal/tunnel:6 狀態機(Idle/Connecting/Connected/Reconnecting/Failed/Stopped)
  + Pair/Unpair/Reconnect/Disconnect binding + ClientHooks event
- internal/auth:encrypted file token store(AES-GCM + scrypt + machineID
  fallback salt + 13 tests)
- internal/config:YAML validation + atomic write + 11 tests
- internal/log:ring buffer + ExportLog 升級 zip
- visionA-backend /api/pairing/exchange:SessionTokenStore + 17 new tests
- 三平台 build 驗證(macOS DMG 160 MB / Windows EXE / Linux AppImage)
- end-to-end 5 milestone 全綠(pairing → tunnel → forward → reuse 防護
  → tunnel drop failover)

Frontend / Next.js(AF1-AF7,沿用 visionA-frontend 基礎):
- AppShell + Header + TabNav(StatusView / PairView / SettingsView 三 tab)
- ConnectionStatusBadge 5 種狀態
- TokenInput regex 驗證 + 7 種錯誤 + 0.5s auto-switch 到狀態頁
- 設定頁 4 區塊(含重新配對 AlertDialog)
- agent-api.ts 封裝 Wails bindings(mock/real 雙實作)+ 90 tests

Phase 0.7 review-driven fix(Round 2):
- A1 Session fixation 防護(RotateSessionID)
- A3 mock pairing 預設改 false(必須明確 opt-in)+ startup log
- A4 Pair 失敗後 state 清理矩陣(exchange/Save/Start fail 各自終態)
- A5 Pair/Unpair/Reconnect lifecycleMu + 50 goroutine race test
- F1 重新配對次按鈕 / F2 PairView Esc cancel / F3 Wails BrowserOpenURL
  / F4 Settings draft 持久 + 未儲存 badge

驗證:agent backend go test -race -count=3 ./... 4 packages 全綠 /
agent frontend pnpm test 119 tests 全綠

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:22:01 +08:00

707 lines
36 KiB
Makefile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# visionA Agent — Makefile骨架M1-1
#
# 這份 Makefile 目前所有 targets 都只是 placeholder實際內容會在後續任務逐步填入
# - M1-2 複製 server core
# - M1-4 複製 frontend
# - M1-8 build-server / build-frontend 實作
# - M1-9 visiona-agent (Wails) shell
# - M1-11 payload-macos
# - M1-12 installer-macos (dmg)
# - M2+ Windows / Linux / CI
#
# 詳見 .autoflow/04-architecture/build-pipeline.md
SHELL := /bin/bash
VERSION ?= v0.1.0-dev
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
OS := $(shell uname -s | tr A-Z a-z)
DIST := dist
PAYLOAD := visiona-agent/payload
.PHONY: help \
vendor-sync vendor-python vendor-wheels vendor-ffmpeg vendor-ffmpeg-macos-build \
vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows \
vendor-python-linux vendor-wheels-linux vendor-ffmpeg-linux \
server build-server \
frontend build-frontend build-embed \
payload payload-macos payload-windows payload-linux \
stage-macos stage-windows \
wails-macos wails-windows wails-linux wails-sync-frontend \
dmg exe exe-only _run-iscc appimage \
dev test lint fmt \
clean clean-all clean-build-exe clean-build-dmg clean-build-appimage
# ── 幫助 ───────────────────────────────────────────────────────────
help: ## 列出所有 make targets
@echo "visionA Agent — available targets (M1-1 skeleton)"
@echo ""
@echo " 依賴同步:"
@echo " vendor-sync 下載 python-build-standalone / wheels / ffmpeg → vendor/"
@echo ""
@echo " Build單元"
@echo " server build Go server binary (→ dist/visiona-agent-server)"
@echo " frontend pnpm build Next.js 靜態產物 (→ frontend/out)"
@echo ""
@echo " Payload 準備:"
@echo " payload-macos stage macOS payload → visiona-agent/payload/"
@echo " payload-windows stage Windows payload"
@echo " payload-linux stage Linux payload"
@echo ""
@echo " Wails 應用 build"
@echo " wails-macos wails build darwin/amd64"
@echo " wails-windows wails build windows/amd64"
@echo " wails-linux wails build linux/amd64"
@echo ""
@echo " 安裝檔打包:"
@echo " dmg macOS .dmgdmgbuild"
@echo " exe Windows .exeInno Setup"
@echo " appimage Linux .AppImage"
@echo ""
@echo " 工具:"
@echo " clean 清除 dist/ payload/"
@echo ""
@echo "Note: 目前所有 target 都是 placeholder只印 TODO尚未實作。"
# ── 依賴同步 ───────────────────────────────────────────────────────
PYTHON_VERSION := 3.12.9
PBS_RELEASE := 20250317
PBS_URL_DARWIN := https://github.com/astral-sh/python-build-standalone/releases/download/$(PBS_RELEASE)/cpython-$(PYTHON_VERSION)+$(PBS_RELEASE)-x86_64-apple-darwin-install_only.tar.gz
# ── ffmpegLGPL v3方案 B 混合)──
# v2 TDD §2.2macOS 自 build decoder-only~20 MBcommit 到 vendor/ffmpeg/macos/
# Windows / Linux 用 BtbN 的 n7.1 LGPL build。
FFMPEG_VERSION := n7.1
FFMPEG_SRC_URL := https://github.com/FFmpeg/FFmpeg/archive/refs/tags/$(FFMPEG_VERSION).tar.gz
# sha256 於第一次 build 時由 `make vendor-ffmpeg-macos-build` 計算後填入 vendor/ffmpeg/macos/BUILD.md
# 之後每次 build 使用此值做 integrity check下方 Makefile 變數亦同步更新)。
FFMPEG_SRC_SHA256 := 7ddad2d992bd250a6c56053c26029f7e728bebf0f37f80cf3f8a0e6ec706431a
vendor-sync: vendor-python vendor-wheels vendor-ffmpeg ## 下載所有第三方依賴到 vendor/(不進 git第三輪決策 Q-D=D2
@echo "==> vendor-sync 完成"
vendor-python: ## 下載 python-build-standalone tarball → vendor/python/darwin/
@mkdir -p vendor/python/darwin
@if [ ! -f vendor/python/darwin/python.tar.gz ]; then \
echo "==> 下載 python-build-standalone $(PYTHON_VERSION)+$(PBS_RELEASE) (macOS x86_64 install_only)..."; \
curl -fL -o vendor/python/darwin/python.tar.gz "$(PBS_URL_DARWIN)"; \
echo "==> tarball 大小:$$(du -sh vendor/python/darwin/python.tar.gz | cut -f1)"; \
else \
echo "==> python tarball 已存在,跳過下載 ($$(du -sh vendor/python/darwin/python.tar.gz | cut -f1))"; \
fi
vendor-wheels: ## 同步 wheels → vendor/wheels/darwin/(內部 wheel 從 visiona-agent/wheels 複製,公開相依用 pip download
@mkdir -p vendor/wheels/darwin
@echo "==> 同步內部 wheelsKneronPLUS 等)..."
@if [ -d visiona-agent/wheels/macos ]; then \
cp visiona-agent/wheels/macos/*.whl vendor/wheels/darwin/ 2>/dev/null || true; \
fi
@echo "==> 從 PyPI 下載公開相依 wheels (cp312, macosx_x86_64)..."
@pip3 download \
--only-binary=:all: \
--platform macosx_10_9_x86_64 \
--platform macosx_11_0_x86_64 \
--platform macosx_12_0_x86_64 \
--python-version 3.12 \
--implementation cp \
--dest vendor/wheels/darwin \
numpy opencv-python-headless pyusb requests || echo "WARN: pip download 部分失敗(詳見上方訊息)"
@echo "==> wheels 總覽:"
@ls -1 vendor/wheels/darwin/*.whl 2>/dev/null | wc -l | xargs -I{} echo " 共 {} 個 wheel"
@du -sh vendor/wheels/darwin
vendor-ffmpeg: ## macOSLGPL v3 ffmpeg + ffprobe 已 commit 到 vendor/ffmpeg/macos/,本 target 只驗證存在 + LGPL 合規
@if [ ! -f vendor/ffmpeg/macos/ffmpeg ]; then \
echo "❌ vendor/ffmpeg/macos/ffmpeg 不存在。"; \
echo " 第一次 build 請執行make vendor-ffmpeg-macos-build"; \
echo " (只需要在升級 ffmpeg 版本時跑一次;平常 clone repo 後 binary 已在 git 內)"; \
exit 1; \
fi
@if [ ! -f vendor/ffmpeg/macos/ffprobe ]; then \
echo "❌ vendor/ffmpeg/macos/ffprobe 不存在。"; \
echo " 請執行make vendor-ffmpeg-macos-build"; \
exit 1; \
fi
@echo "==> vendor/ffmpeg/macos/ffmpeg 存在:$$(du -h vendor/ffmpeg/macos/ffmpeg | cut -f1)"
@echo "==> vendor/ffmpeg/macos/ffprobe 存在:$$(du -h vendor/ffmpeg/macos/ffprobe | cut -f1)"
@if vendor/ffmpeg/macos/ffmpeg -version 2>&1 | grep -qE -- '--enable-gpl|libx264|libx265'; then \
echo "❌ LGPL 驗證失敗binary 含 --enable-gpl / libx264 / libx265"; \
exit 1; \
fi
@echo "==> LGPL 驗證通過(無 --enable-gpl / libx264 / libx265"
# 只有升級 ffmpeg 版本時才跑此 targetbinary 產出後 commit 到 gitv2 TDD R5-6b
# 需要的系統依賴macOS
# brew install pkg-config nasm # 或 yasm
vendor-ffmpeg-macos-build: ## macOS從源碼 build LGPL v3 decoder-only ffmpeg + ffprobe升級時才跑~15 分鐘)
@if [ "$$(uname -s)" != "Darwin" ]; then \
echo "❌ vendor-ffmpeg-macos-build 只能在 macOS 上跑"; exit 1; \
fi
@command -v pkg-config >/dev/null 2>&1 || { echo "❌ 需要 pkg-configbrew install pkg-config"; exit 1; }
@command -v nasm >/dev/null 2>&1 || command -v yasm >/dev/null 2>&1 || { echo "❌ 需要 nasm 或 yasmbrew install nasm"; exit 1; }
@mkdir -p vendor/ffmpeg/macos build/ffmpeg-macos
@echo "==> 下載 ffmpeg source $(FFMPEG_VERSION)..."
curl -fL -o build/ffmpeg-macos/source.tar.gz "$(FFMPEG_SRC_URL)"
@echo "==> 驗證 source tarball sha256..."
@echo "$(FFMPEG_SRC_SHA256) build/ffmpeg-macos/source.tar.gz" | shasum -a 256 -c || { \
echo "❌ sha256 不符,請更新 Makefile 中的 FFMPEG_SRC_SHA256 或檢查來源"; \
echo " 實際 sha256$$(shasum -a 256 build/ffmpeg-macos/source.tar.gz | awk '{print $$1}')"; \
exit 1; }
@rm -rf build/ffmpeg-macos/src build/ffmpeg-macos/install
@mkdir -p build/ffmpeg-macos/src
tar xzf build/ffmpeg-macos/source.tar.gz -C build/ffmpeg-macos/src --strip-components=1
@echo "==> configuredecoder-only LGPL v3..."
cd build/ffmpeg-macos/src && ./configure \
--prefix="$$(pwd)/../install" \
--enable-version3 \
--disable-debug \
--disable-doc \
--disable-ffplay \
--disable-network \
--disable-autodetect \
--disable-shared \
--enable-static \
--disable-everything \
--enable-small \
--enable-protocol=file,pipe \
--enable-demuxer=mov,avi,mpegps,mpegts,matroska,image2 \
--enable-decoder=h264,hevc,mpeg1video,mpeg2video,mpeg4,mjpeg,prores,vp8,vp9,aac,mp2,mp3,pcm_s16le,pcm_s16be \
--enable-parser=h264,hevc,mpeg4video,mpegaudio,aac \
--enable-filter=scale,format,fps,null,anull \
--enable-muxer=image2pipe,image2,null \
--enable-encoder=mjpeg \
--enable-swscale \
--enable-swresample \
--extra-cflags="-arch x86_64 -mmacosx-version-min=10.15" \
--extra-ldflags="-arch x86_64 -mmacosx-version-min=10.15 -Wl,-search_paths_first" \
--arch=x86_64 \
--target-os=darwin \
--cc="clang -arch x86_64"
cd build/ffmpeg-macos/src && make -j$$(sysctl -n hw.ncpu)
cd build/ffmpeg-macos/src && make install
@echo "==> 複製 ffmpeg + ffprobe 到 vendor/ffmpeg/macos/..."
cp build/ffmpeg-macos/install/bin/ffmpeg vendor/ffmpeg/macos/ffmpeg
cp build/ffmpeg-macos/install/bin/ffprobe vendor/ffmpeg/macos/ffprobe
@strip -S -x vendor/ffmpeg/macos/ffmpeg
@strip -S -x vendor/ffmpeg/macos/ffprobe
@chmod +x vendor/ffmpeg/macos/ffmpeg vendor/ffmpeg/macos/ffprobe
@echo "==> ad-hoc 簽章..."
codesign --force --sign - vendor/ffmpeg/macos/ffmpeg
codesign --force --sign - vendor/ffmpeg/macos/ffprobe
@echo "==> 驗證授權configuration line 不含 --enable-gpl / libx264 / libx265..."
@if vendor/ffmpeg/macos/ffmpeg -version 2>&1 | grep -E -- '--enable-gpl|libx264|libx265'; then \
echo "❌ LGPL 驗證失敗build 不該出現 gpl / x264 / x265"; exit 1; \
fi
@echo "==> 複製 COPYING.LGPLv3 到 vendor/ffmpeg/macos/..."
cp build/ffmpeg-macos/src/COPYING.LGPLv3 vendor/ffmpeg/macos/COPYING.LGPLv3
@echo "==> ffmpeg 大小:$$(du -h vendor/ffmpeg/macos/ffmpeg | cut -f1)"
@echo "==> ffprobe 大小:$$(du -h vendor/ffmpeg/macos/ffprobe | cut -f1)"
@echo ""
@echo "✅ macOS LGPL ffmpeg build 完成。接下來請:"
@echo " 1) 更新 vendor/ffmpeg/macos/BUILD.md 的 Binary sha256 區塊"
@echo " 2) git add vendor/ffmpeg/macos/{ffmpeg,ffprobe,COPYING.LGPLv3,BUILD.md}"
# ── Build單元 ──────────────────────────────────────────────────
server: build-server ## alias for build-server
build-server: build-embed ## build Go server binary → dist/visiona-agent-server會先 build frontend + embed
@mkdir -p $(DIST)
cd server && go build -o ../$(DIST)/visiona-agent-server .
@echo "built: $(DIST)/visiona-agent-server"
build-server-windows: build-embed ## 交叉/原生 build Windows server → payload/windows/bin/visiona-agent-server.exe
@mkdir -p payload/windows/bin
cd server && GOOS=windows GOARCH=amd64 go build -o ../payload/windows/bin/visiona-agent-server.exe .
@echo "built: payload/windows/bin/visiona-agent-server.exe"
build-server-linux: build-embed ## 交叉/原生 build Linux server → payload/linux/bin/visiona-agent-server
@mkdir -p payload/linux/bin
cd server && GOOS=linux GOARCH=amd64 go build -o ../payload/linux/bin/visiona-agent-server .
@echo "built: payload/linux/bin/visiona-agent-server"
frontend: build-frontend ## alias for build-frontend
build-frontend: ## pnpm build → frontend/out/
@echo "==> pnpm build frontend..."
cd frontend && pnpm install --frozen-lockfile && pnpm build
@echo "==> frontend/out 完成:"
@du -sh frontend/out
build-embed: build-frontend ## 同步 frontend/out → server/web/out 供 go:embed
@echo "==> 同步 frontend/out → server/web/out..."
rm -rf server/web/out
mkdir -p server/web/out
cp -R frontend/out/. server/web/out/
@du -sh server/web/out
# ── Payload 準備 ───────────────────────────────────────────────────
payload: payload-$(OS) ## 依當前 OS 準備 payload
payload-macos: build-server vendor-python vendor-wheels vendor-ffmpeg ## 準備 macOS payload → payload/darwin/(含 python runtime + wheels + ffmpeg
@echo "==> 建立 macOS payload (binary + models + scripts + python + wheels + ffmpeg + ffprobe)..."
rm -rf payload/darwin
mkdir -p payload/darwin/bin payload/darwin/data payload/darwin/scripts payload/darwin/python payload/darwin/wheels
cp dist/visiona-agent-server payload/darwin/bin/
cp vendor/ffmpeg/macos/ffmpeg payload/darwin/bin/
cp vendor/ffmpeg/macos/ffprobe payload/darwin/bin/
cp vendor/ffmpeg/macos/COPYING.LGPLv3 payload/darwin/bin/ffmpeg-COPYING.LGPLv3
chmod +x payload/darwin/bin/ffmpeg payload/darwin/bin/ffprobe
cp -R server/data/* payload/darwin/data/
cp -R server/scripts/* payload/darwin/scripts/
cp vendor/python/darwin/python.tar.gz payload/darwin/python/
@cp vendor/wheels/darwin/*.whl payload/darwin/wheels/ 2>/dev/null || true
@echo "==> macOS payload 完成:"
@du -sh payload/darwin
@echo ""
@echo "payload/darwin 結構:"
@find payload/darwin -maxdepth 2 -type d | sed 's|^| |'
@echo ""
@echo "payload/darwin/python"
@ls -lh payload/darwin/python
@echo "payload/darwin/wheels"
@ls -1 payload/darwin/wheels | head -20
stage-macos: payload-macos ## 將 payload/darwin/ 放到 Wails build/darwin/Resources/
@echo "==> 放置 payload 到 Wails build/darwin/Resources..."
rm -rf visiona-agent/build/darwin/Resources
mkdir -p visiona-agent/build/darwin/Resources
cp -R payload/darwin/. visiona-agent/build/darwin/Resources/
@echo "==> stage 完成:"
@du -sh visiona-agent/build/darwin/Resources
# ── M4Windows vendor + payload + wails + exe ─────────────────────
# 注意wails-windows 與 exe 必須在 Windows runner 上跑;在 macOS 上會明確 fail。
# payload-windows / vendor-*-windows 是 curl 下載跨平台可跑server.exe 步驟除外)。
PBS_URL_WINDOWS := https://github.com/astral-sh/python-build-standalone/releases/download/$(PBS_RELEASE)/cpython-$(PYTHON_VERSION)+$(PBS_RELEASE)-x86_64-pc-windows-msvc-install_only.tar.gz
# LGPL v3 buildBtbN n7.1 穩定分支,含 LGPL-safe extra libs— v2 TDD §3
FFMPEG_URL_WINDOWS := https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/ffmpeg-n7.1-latest-win64-lgpl-7.1.zip
vendor-python-windows: ## 下載 python-build-standalone Windows x86_64 → vendor/python/windows/
@mkdir -p vendor/python/windows
@if [ ! -f vendor/python/windows/python.tar.gz ]; then \
echo "==> 下載 python-build-standalone $(PYTHON_VERSION)+$(PBS_RELEASE) (Windows x86_64 install_only)..."; \
curl -fL -o vendor/python/windows/python.tar.gz "$(PBS_URL_WINDOWS)"; \
echo "==> tarball 大小:$$(du -sh vendor/python/windows/python.tar.gz | cut -f1)"; \
else \
echo "==> python (Windows) tarball 已存在,跳過 ($$(du -sh vendor/python/windows/python.tar.gz | cut -f1))"; \
fi
vendor-wheels-windows: ## 同步 Windows wheels → vendor/wheels/windows/
@mkdir -p vendor/wheels/windows
@echo "==> 同步內部 wheels (Windows, KneronPLUS 等)..."
@if [ -d visiona-agent/wheels/windows ]; then \
cp visiona-agent/wheels/windows/*.whl vendor/wheels/windows/ 2>/dev/null || true; \
fi
@echo "==> 從 PyPI 下載公開相依 wheels (cp312, win_amd64)..."
@PY=""; \
for candidate in "$$VISIONA_PYTHON" "py -3" python3 python; do \
[ -z "$$candidate" ] && continue; \
resolved=$$(command -v $${candidate%% *} 2>/dev/null || true); \
case "$$resolved" in *WindowsApps*) continue ;; esac; \
if $$candidate --version >/dev/null 2>&1; then PY="$$candidate"; break; fi; \
done; \
if [ -z "$$PY" ]; then \
echo "WARN: 找不到真實 pythonWindowsApps stub 不算),跳過 PyPI 下載(僅使用內部 wheels"; \
else \
echo "==> 使用 $$PY -m pip"; \
$$PY -m pip download \
--only-binary=:all: \
--platform win_amd64 \
--python-version 3.12 \
--implementation cp \
--dest vendor/wheels/windows \
numpy opencv-python-headless pyusb requests || echo "WARN: pip download 部分失敗(詳見上方訊息)"; \
fi
@echo "==> Windows wheels 總覽:"
@ls -1 vendor/wheels/windows/*.whl 2>/dev/null | wc -l | xargs -I{} echo " 共 {} 個 wheel"
@du -sh vendor/wheels/windows 2>/dev/null || true
vendor-ffmpeg-windows: ## 下載 ffmpeg Windows LGPL v3 build (n7.1) → vendor/ffmpeg/windows/
@mkdir -p vendor/ffmpeg/windows
@if [ -f vendor/ffmpeg/windows/ffmpeg.exe ] && [ -f vendor/ffmpeg/windows/ffprobe.exe ]; then \
echo "==> ffmpeg.exe + ffprobe.exe 已存在,跳過"; \
else \
echo "==> 下載 BtbN LGPL ffmpeg (Windows, n7.1)..."; \
curl -fL -o vendor/ffmpeg/windows/ffmpeg-win.zip "$(FFMPEG_URL_WINDOWS)"; \
PY=""; \
for candidate in "$$VISIONA_PYTHON" "py -3" python3 python; do \
[ -z "$$candidate" ] && continue; \
resolved=$$(command -v $${candidate%% *} 2>/dev/null || true); \
case "$$resolved" in *WindowsApps*) continue ;; esac; \
if $$candidate --version >/dev/null 2>&1; then PY="$$candidate"; break; fi; \
done; \
if [ -z "$$PY" ]; then echo "ERROR: 需要真實 python 來解壓 zipWindowsApps stub 無法使用)"; exit 1; fi; \
echo "==> 使用 $$PY 解壓 ffmpeg zip取出 ffmpeg.exe / ffprobe.exe / LICENSE.txt"; \
$$PY -c "import zipfile, os; z=zipfile.ZipFile('vendor/ffmpeg/windows/ffmpeg-win.zip'); \
wanted=['/bin/ffmpeg.exe','/bin/ffprobe.exe','/LICENSE.txt','/COPYING.LGPLv3']; \
members=[n for n in z.namelist() if any(n.endswith(w) for w in wanted)]; \
assert any(n.endswith('/bin/ffmpeg.exe') for n in members), 'ffmpeg.exe not found in zip'; \
assert any(n.endswith('/bin/ffprobe.exe') for n in members), 'ffprobe.exe not found in zip'; \
os.makedirs('vendor/ffmpeg/windows', exist_ok=True); \
[open('vendor/ffmpeg/windows/'+m.rsplit('/',1)[1],'wb').write(z.read(m)) for m in members]; \
print('extracted:', [m.rsplit('/',1)[1] for m in members])" || { echo "ERROR: python 解壓失敗"; exit 1; }; \
rm -f vendor/ffmpeg/windows/ffmpeg-win.zip; \
[ -f vendor/ffmpeg/windows/ffmpeg.exe ] || { echo "ERROR: ffmpeg.exe 沒被寫出"; exit 1; }; \
[ -f vendor/ffmpeg/windows/ffprobe.exe ] || { echo "ERROR: ffprobe.exe 沒被寫出"; exit 1; }; \
echo "==> ffmpeg.exe 大小:$$(du -h vendor/ffmpeg/windows/ffmpeg.exe | cut -f1)"; \
echo "==> ffprobe.exe 大小:$$(du -h vendor/ffmpeg/windows/ffprobe.exe | cut -f1)"; \
fi
payload-windows: build-server-windows vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows ## 準備 Windows payload → payload/windows/
@echo "==> 建立 Windows payload (binary + models + scripts + python + wheels + ffmpeg)..."
@# 注意:不 rm -rf payload/windows因為 build-server-windows 已先把 .exe 放進去
mkdir -p payload/windows/bin payload/windows/data payload/windows/scripts payload/windows/python payload/windows/wheels
@if [ ! -f payload/windows/bin/visiona-agent-server.exe ]; then \
echo "!! ERROR: payload/windows/bin/visiona-agent-server.exe 不存在build-server-windows 可能失敗 !!"; \
exit 1; \
fi
cp vendor/ffmpeg/windows/ffmpeg.exe payload/windows/bin/
cp vendor/ffmpeg/windows/ffprobe.exe payload/windows/bin/
@# LGPL 授權條款BtbN build 自帶 LICENSE.txtCOPYING.LGPLv3 不一定在壓縮檔內,失敗不致命)
@cp vendor/ffmpeg/windows/LICENSE.txt payload/windows/bin/ffmpeg-LICENSE.txt 2>/dev/null || true
@cp vendor/ffmpeg/windows/COPYING.LGPLv3 payload/windows/bin/ffmpeg-COPYING.LGPLv3 2>/dev/null || true
cp -R server/data/. payload/windows/data/
cp -R server/scripts/. payload/windows/scripts/
cp vendor/python/windows/python.tar.gz payload/windows/python/
@cp vendor/wheels/windows/*.whl payload/windows/wheels/ 2>/dev/null || true
@echo "==> Windows payload 完成:"
@du -sh payload/windows
@echo ""
@echo "payload/windows 結構:"
@find payload/windows -maxdepth 2 -type d | sed 's|^| |'
stage-windows: payload-windows ## 將 payload/windows/ 放到 Wails build/windows/Resources/
@echo "==> 放置 payload 到 Wails build/windows/Resources..."
rm -rf visiona-agent/build/windows/Resources
mkdir -p visiona-agent/build/windows/Resources
cp -R payload/windows/. visiona-agent/build/windows/Resources/
@echo "==> stage 完成:"
@du -sh visiona-agent/build/windows/Resources
# ── M5Linux vendor + payload + wails + AppImage ──────────────────
# 注意wails-linux 與 appimage 必須在 Linux runner 上跑;在 macOS 上會明確 fail。
# payload-linux / vendor-*-linux 是 curl 下載跨平台可跑server 步驟除外)。
PBS_URL_LINUX := https://github.com/astral-sh/python-build-standalone/releases/download/$(PBS_RELEASE)/cpython-$(PYTHON_VERSION)+$(PBS_RELEASE)-x86_64-unknown-linux-gnu-install_only.tar.gz
# LGPL v3 buildBtbN n7.1 穩定分支)— v2 TDD §4
FFMPEG_URL_LINUX := https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/ffmpeg-n7.1-latest-linux64-lgpl-7.1.tar.xz
vendor-python-linux: ## 下載 python-build-standalone Linux x86_64 → vendor/python/linux/
@mkdir -p vendor/python/linux
@if [ ! -f vendor/python/linux/python.tar.gz ]; then \
echo "==> 下載 python-build-standalone $(PYTHON_VERSION)+$(PBS_RELEASE) (Linux x86_64 install_only)..."; \
curl -fL -o vendor/python/linux/python.tar.gz "$(PBS_URL_LINUX)"; \
echo "==> tarball 大小:$$(du -sh vendor/python/linux/python.tar.gz | cut -f1)"; \
else \
echo "==> python (Linux) tarball 已存在,跳過 ($$(du -sh vendor/python/linux/python.tar.gz | cut -f1))"; \
fi
vendor-wheels-linux: ## 同步 Linux wheels → vendor/wheels/linux/
@mkdir -p vendor/wheels/linux
@echo "==> 同步內部 wheels (Linux, KneronPLUS 等)..."
@if [ -d visiona-agent/wheels/linux ]; then \
cp visiona-agent/wheels/linux/*.whl vendor/wheels/linux/ 2>/dev/null || true; \
fi
@echo "==> 從 PyPI 下載公開相依 wheels (cp312, manylinux2014_x86_64)..."
@pip3 download \
--only-binary=:all: \
--platform manylinux2014_x86_64 \
--python-version 3.12 \
--implementation cp \
--dest vendor/wheels/linux \
numpy opencv-python-headless pyusb requests || echo "WARN: pip download 部分失敗(詳見上方訊息)"
@echo "==> Linux wheels 總覽:"
@ls -1 vendor/wheels/linux/*.whl 2>/dev/null | wc -l | xargs -I{} echo " 共 {} 個 wheel"
@du -sh vendor/wheels/linux 2>/dev/null || true
vendor-ffmpeg-linux: ## 下載 ffmpeg Linux LGPL v3 build (n7.1) → vendor/ffmpeg/linux/
@mkdir -p vendor/ffmpeg/linux
@if [ -f vendor/ffmpeg/linux/ffmpeg ] && [ -f vendor/ffmpeg/linux/ffprobe ]; then \
echo "==> ffmpeg + ffprobe (Linux) 已存在,跳過"; \
else \
echo "==> 下載 BtbN LGPL ffmpeg (Linux, n7.1)..."; \
curl -fL -o /tmp/ffmpeg-linux.tar.xz "$(FFMPEG_URL_LINUX)"; \
rm -rf /tmp/ffmpeg-linux-extract; \
mkdir -p /tmp/ffmpeg-linux-extract; \
tar xf /tmp/ffmpeg-linux.tar.xz -C /tmp/ffmpeg-linux-extract --strip-components=1; \
cp /tmp/ffmpeg-linux-extract/bin/ffmpeg vendor/ffmpeg/linux/; \
cp /tmp/ffmpeg-linux-extract/bin/ffprobe vendor/ffmpeg/linux/; \
cp /tmp/ffmpeg-linux-extract/LICENSE.txt vendor/ffmpeg/linux/LICENSE.txt 2>/dev/null || true; \
chmod +x vendor/ffmpeg/linux/ffmpeg vendor/ffmpeg/linux/ffprobe; \
rm -rf /tmp/ffmpeg-linux* ; \
echo "==> ffmpeg (Linux) 大小:$$(du -h vendor/ffmpeg/linux/ffmpeg | cut -f1)"; \
echo "==> ffprobe (Linux) 大小:$$(du -h vendor/ffmpeg/linux/ffprobe | cut -f1)"; \
fi
payload-linux: build-server-linux vendor-python-linux vendor-wheels-linux vendor-ffmpeg-linux ## 準備 Linux payload → payload/linux/
@echo "==> 建立 Linux payload (binary + models + scripts + python + wheels + ffmpeg)..."
mkdir -p payload/linux/bin payload/linux/data payload/linux/scripts payload/linux/python payload/linux/wheels
@if [ ! -f payload/linux/bin/visiona-agent-server ]; then \
echo "!! ERROR: payload/linux/bin/visiona-agent-server 不存在build-server-linux 可能失敗 !!"; \
exit 1; \
fi
@chmod +x payload/linux/bin/visiona-agent-server
@cp vendor/ffmpeg/linux/ffmpeg payload/linux/bin/ 2>/dev/null && chmod +x payload/linux/bin/ffmpeg || echo "!! WARN: ffmpeg 缺失"
@cp vendor/ffmpeg/linux/ffprobe payload/linux/bin/ 2>/dev/null && chmod +x payload/linux/bin/ffprobe || echo "!! WARN: ffprobe 缺失"
@cp vendor/ffmpeg/linux/LICENSE.txt payload/linux/bin/ffmpeg-LICENSE.txt 2>/dev/null || true
@if [ -d server/data ]; then cp -R server/data/. payload/linux/data/; fi
@if [ -d server/scripts ]; then cp -R server/scripts/. payload/linux/scripts/; fi
@if [ ! -f vendor/python/linux/python.tar.gz ]; then \
echo "!! ERROR: vendor/python/linux/python.tar.gz 不存在vendor-python-linux 應該已先跑過 !!"; \
exit 1; \
fi
@cp vendor/python/linux/python.tar.gz payload/linux/python/
@cp vendor/wheels/linux/*.whl payload/linux/wheels/ 2>/dev/null || true
@wheel_count=$$(ls -1 payload/linux/wheels/*.whl 2>/dev/null | wc -l); \
if [ "$$wheel_count" -lt 4 ]; then \
echo "!! ERROR: payload/linux/wheels 只找到 $$wheel_count 個 wheel預期至少 4 個numpy / opencv / pyusb / kp"; \
echo " 請檢查 vendor/wheels/linux/ 並重跑 make vendor-wheels-linux"; \
exit 1; \
fi
@echo "==> Linux payload 完成:包含 $$(ls payload/linux/wheels/*.whl 2>/dev/null | wc -l) 個 wheel + python tarball"
@du -sh payload/linux 2>/dev/null || true
@echo ""
@echo "payload/linux 結構:"
@find payload/linux -maxdepth 2 -type d 2>/dev/null | sed 's|^| |'
# ── Wails build ────────────────────────────────────────────────────
#
# AF6Wails 使用 visiona-agent/main.go 的 `//go:embed all:frontend` 嵌入前端產物。
# 新的 Next.js 靜態匯出在 `frontend/out/`,需要在 wails build 前同步到 `visiona-agent/frontend/`
# 否則打包後的 app 會仍嵌入舊的 local-tool 遺留控制台 JS。
#
# 選擇「Makefile 同步」而非改 Go embed 路徑的理由:
# 1. `//go:embed` 不允許走出 module rootvisiona-agent/);相對路徑 ../frontend/out 不合法
# 2. 保持 Go 原始碼穩定,避免為了 build pipeline 去碰語意性的 embed 宣告
# 3. 同步腳本單純rm + cp失敗時容易 debug
wails-sync-frontend: build-frontend ## 同步 frontend/out/ → visiona-agent/frontend/AF6取代舊的 local-tool JS 控制台)
@echo "==> 同步 frontend/out → visiona-agent/frontend (Wails go:embed 嵌入來源) ..."
@# 保留 wailsjs/ 目錄Wails CLI 產出的 TS bindings不應被 frontend/out 覆蓋)
@find visiona-agent/frontend -mindepth 1 -maxdepth 1 ! -name 'wailsjs' -exec rm -rf {} +
cp -R frontend/out/. visiona-agent/frontend/
@du -sh visiona-agent/frontend
wails-macos: stage-macos wails-sync-frontend ## wails build darwin/amd64 → visiona-agent/build/bin/visiona-agent.app
@# -s跳過 Wails 內建 frontend install/build我們已在 wails-sync-frontend 處理好,
@# visiona-agent/frontend 只放靜態產物,沒有 package.jsonWails 若自己跑 pnpm 會失敗)
cd visiona-agent && wails build -platform darwin/amd64 -clean -s
cp -R visiona-agent/build/darwin/Resources/bin \
visiona-agent/build/darwin/Resources/data \
visiona-agent/build/darwin/Resources/scripts \
visiona-agent/build/darwin/Resources/python \
visiona-agent/build/darwin/Resources/wheels \
visiona-agent/build/bin/visiona-agent.app/Contents/Resources/
codesign --force --deep --sign - visiona-agent/build/bin/visiona-agent.app
codesign --verify --verbose visiona-agent/build/bin/visiona-agent.app
@du -sh visiona-agent/build/bin/visiona-agent.app
wails-windows: stage-windows wails-sync-frontend ## ⚠️ 必須在 Windows runner 上執行wails build -platform windows/amd64
@case "$$(uname -s 2>/dev/null)" in \
MINGW*|CYGWIN*|MSYS*) : ;; \
*) \
echo ""; \
echo "❌ wails-windows 只能在 Windows runner 上 build偵測到 $$(uname -s)"; \
echo " 請在 Windows 機器上執行此 target或用 CI 的 Windows runner"; \
echo " 若只是要檢查前置步驟,可單獨跑 make stage-windows"; \
echo ""; \
exit 1 ;; \
esac
@# -s跳過 Wails 內建 frontend install/build見 wails-macos 的說明)
cd visiona-agent && wails build -platform windows/amd64 -clean -s
@du -sh visiona-agent/build/bin/visiona-agent.exe
wails-linux: payload-linux wails-sync-frontend ## ⚠️ 必須在 Linux runner 上執行wails build -platform linux/amd64
@if [ "$$(uname -s)" != "Linux" ]; then \
echo ""; \
echo "❌ wails-linux 只能在 Linux 上 build偵測到 $$(uname -s)"; \
echo " 請在 Linux x86_64 runnerGitHub Actions ubuntu-latest 即可)執行"; \
echo " 若只是要檢查前置步驟,可單獨跑 make payload-linux"; \
echo ""; \
exit 1; \
fi
# webkit2gtk-4.0 從 Ubuntu 22.10+ / Debian 12+ 起被 webkit2gtk-4.1 取代。
# 偵測 pkg-config 哪個存在:優先用 4.1(加 -tags webkit2_414.0 則
# 不加 tagWails 預設)。
@# -s跳過 Wails 內建 frontend install/build見 wails-macos 的說明)
@if pkg-config --exists webkit2gtk-4.1 2>/dev/null; then \
echo "==> webkit2gtk-4.1 detected → wails build with -tags webkit2_41"; \
cd visiona-agent && wails build -platform linux/amd64 -clean -s -tags webkit2_41; \
elif pkg-config --exists webkit2gtk-4.0 2>/dev/null; then \
echo "==> webkit2gtk-4.0 detected → wails build (default)"; \
cd visiona-agent && wails build -platform linux/amd64 -clean -s; \
else \
echo ""; \
echo "❌ 找不到 webkit2gtk-4.0 或 webkit2gtk-4.1 dev header"; \
echo " 請執行sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev"; \
echo " (舊版 Ubuntu 20.04 可改用 libwebkit2gtk-4.0-dev"; \
echo ""; \
exit 1; \
fi
@du -sh visiona-agent/build/bin/visiona-agent
# ── 安裝檔打包 ─────────────────────────────────────────────────────
dmg: wails-macos ## 美化 DMGcreate-dmg 有裝)或 plain DMGfallback→ dist/visiona-agent.dmg
@mkdir -p $(DIST)
@rm -f $(DIST)/visiona-agent.dmg
@if command -v create-dmg > /dev/null 2>&1; then \
$(MAKE) --no-print-directory dmg-fancy; \
else \
echo "⚠️ create-dmg 未安裝,使用 plain DMGhdiutil UDZO"; \
echo " 想要美化版本請執行brew install create-dmg"; \
$(MAKE) --no-print-directory dmg-plain; \
fi
@du -sh $(DIST)/visiona-agent.dmg
@file $(DIST)/visiona-agent.dmg
dmg-plain: ## hdiutil UDZO → dist/visiona-agent.dmg無背景圖CI / fallback 用)
@mkdir -p $(DIST)
rm -f $(DIST)/visiona-agent.dmg
hdiutil create -volname "visionA Agent" \
-srcfolder visiona-agent/build/bin/visiona-agent.app \
-ov -format UDZO \
$(DIST)/visiona-agent.dmg
dmg-fancy: ## create-dmg 美化版 → dist/visiona-agent.dmg需 brew install create-dmg
@if [ ! -d visiona-agent/build/bin/visiona-agent.app ]; then \
echo "❌ visiona-agent/build/bin/visiona-agent.app 不存在,請先跑 make wails-macos"; exit 1; \
fi
@if ! command -v create-dmg > /dev/null 2>&1; then \
echo "❌ create-dmg 未安裝請執行brew install create-dmg"; exit 1; \
fi
@mkdir -p $(DIST)
rm -f $(DIST)/visiona-agent.dmg
create-dmg \
--volname "visionA Agent" \
--background installer/macos/background.png \
--window-pos 200 120 \
--window-size 640 400 \
--icon-size 128 \
--icon "visiona-agent.app" 180 200 \
--app-drop-link 460 200 \
--hide-extension "visiona-agent.app" \
--no-internet-enable \
$(DIST)/visiona-agent.dmg \
visiona-agent/build/bin/visiona-agent.app
exe-only: ## 只跑 iscc 打包 installer前置產物必須已存在不重 build wails/payload
@if [ ! -f visiona-agent/build/bin/visiona-agent.exe ]; then \
echo "❌ visiona-agent/build/bin/visiona-agent.exe 不存在,請先跑 make wails-windows"; exit 1; \
fi
@if [ ! -f payload/windows/bin/visiona-agent-server.exe ]; then \
echo "❌ payload/windows/bin/visiona-agent-server.exe 不存在,請先跑 make payload-windows"; exit 1; \
fi
@$(MAKE) --no-print-directory _run-iscc
exe: wails-windows _run-iscc ## ⚠️ 必須在 Windows 上跑Inno Setup → dist/visiona-agent-*-windows-x64.exe
_run-iscc:
@echo "DEBUG _run-iscc: ISCC='$$ISCC'"
@echo "DEBUG _run-iscc: PATH first entries: $$(echo $$PATH | tr ':' '\n' | head -5)"
@ISCC_BIN="$$ISCC"; \
if [ -z "$$ISCC_BIN" ]; then \
if command -v iscc > /dev/null 2>&1; then ISCC_BIN=iscc; \
elif command -v iscc.exe > /dev/null 2>&1; then ISCC_BIN=iscc.exe; \
else \
for p in "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" \
"/c/Program Files/Inno Setup 6/ISCC.exe" \
"$$LOCALAPPDATA/Programs/Inno Setup 6/ISCC.exe" \
"$$USERPROFILE/AppData/Local/Programs/Inno Setup 6/ISCC.exe" \
"/c/Program Files (x86)/Inno Setup 5/ISCC.exe"; do \
if [ -f "$$p" ]; then ISCC_BIN="$$p"; break; fi; \
done; \
fi; \
fi; \
ISCC_OK=0; \
if [ -n "$$ISCC_BIN" ]; then \
if [ -e "$$ISCC_BIN" ] || command -v "$$ISCC_BIN" > /dev/null 2>&1; then ISCC_OK=1; fi; \
fi; \
if [ "$$ISCC_OK" = "0" ]; then \
echo ""; \
echo "❌ Inno Setup Compiler (iscc) 未安裝 / 找不到"; \
echo " 已嘗試偵測的路徑:"; \
echo " - \$$ISCC 環境變數"; \
echo " - PATH 上的 iscc / iscc.exe"; \
echo " - /c/Program Files (x86)/Inno Setup 6/ISCC.exe"; \
echo " - /c/Program Files/Inno Setup 6/ISCC.exe"; \
echo " 請從 https://jrsoftware.org/isdl.php 下載並安裝 Inno Setup 6"; \
echo " 或設定 ISCC 環境變數指向 ISCC.exe 絕對路徑"; \
echo ""; \
exit 1; \
fi; \
echo "==> 使用 ISCC: $$ISCC_BIN"; \
mkdir -p $(DIST); \
echo "==> 執行 iscccwd: $$(pwd)..."; \
"$$ISCC_BIN" installer/windows/visiona-agent.iss; \
ISCC_RC=$$?; \
echo "==> iscc exit code: $$ISCC_RC"; \
if [ $$ISCC_RC -ne 0 ]; then exit $$ISCC_RC; fi
@echo "==> 產出:"
@ls -lh $(DIST)/ 2>/dev/null || echo "dist 目錄不存在"
@ls -lh $(DIST)/visiona-agent-*-windows-x64.exe 2>/dev/null || echo "未找到 .exe 產出檔"
appimage: wails-linux ## ⚠️ 必須在 Linux 上跑build-appimage.sh → dist/visiona-agent-*-linux-x64.AppImage
@if [ "$$(uname -s)" != "Linux" ]; then \
echo ""; \
echo "❌ appimage 只能在 Linux 上 build偵測到 $$(uname -s)"; \
echo " 需要 appimagetool請在 Linux x86_64 runner 執行"; \
echo ""; \
exit 1; \
fi
@mkdir -p $(DIST)
VERSION=$(VERSION) bash installer/linux/build-appimage.sh
@echo "==> 產出:"
@ls -lh $(DIST)/visiona-agent-*-linux-x64.AppImage 2>/dev/null || echo "未找到產出檔"
# ── 開發 ───────────────────────────────────────────────────────────
dev-agent: ## AB6build server binary + 以 go run 啟動 visiona-agentskip Wails GUI方便 CLI 迭代)
@echo "==> building server binary → $(DIST)/visiona-agent-server ..."
cd server && go build -o ../$(DIST)/visiona-agent-server .
@echo "==> starting visiona-agent (ctrl-c 停止)..."
@echo " tunnel 會等 VISIONA_RELAY_URL 設定後才啟動;"
@echo " 設 VISIONA_SESSION_TOKEN 自動跳過 Pair。"
cd visiona-agent && go run .
dev: dev-agent ## alias for dev-agent
test: ## go test -racefull agent module + server module
cd visiona-agent && go test -race -count=1 ./...
cd server && go test -race -count=1 ./...
test-tunnel: ## 只跑 tunnel 整合測試AB6
cd visiona-agent && go test -race -count=1 -v ./internal/tunnel/
lint: ## go vet + (optional) pnpm lint
cd visiona-agent && go vet ./...
cd server && go vet ./...
@echo "TODO: pnpm lint"
fmt: ## go fmt
cd visiona-agent && go fmt ./...
cd server && go fmt ./...
# ── 清理 ───────────────────────────────────────────────────────────
clean: ## 清除 dist/ 與 payload/ 產物
@echo "Cleaning dist/ and payload artifacts..."
rm -rf $(DIST)
rm -rf $(PAYLOAD)
@mkdir -p $(DIST) $(PAYLOAD)
@touch $(DIST)/.gitkeep $(PAYLOAD)/.gitkeep
@echo "Done."
clean-all: clean ## 完整清除dist/ payload/ wails build/ frontend build/ server embed
@echo "==> 清除 Wails build 產物..."
rm -rf visiona-agent/build/bin
rm -rf visiona-agent/build/darwin/Resources
rm -rf visiona-agent/build/windows/Resources
@echo "==> 清除 frontend build 產物..."
rm -rf frontend/out
rm -rf frontend/.next
@echo "==> 清除 server 內嵌前端..."
rm -rf server/web/out
@echo "==> clean-all 完成"
clean-build-exe: clean-all exe ## Windows完整 clean + 從頭 build installer .exe最乾淨的 build
clean-build-dmg: clean-all dmg ## macOS完整 clean + 從頭 build installer .dmg
clean-build-appimage: clean-all appimage ## Linux完整 clean + 從頭 build installer .AppImage