local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
Wails IPC raise endpoint, stale process cleanup
.autoflow/: full PRD / Design Spec / Architecture / Testing docs
(4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
310 lines
9.7 KiB
Markdown
310 lines
9.7 KiB
Markdown
# Packaging — visionA-local
|
||
|
||
> macOS .dmg、Windows Inno Setup .exe、Linux AppImage 的具體打包流程。
|
||
> **無任何程式碼簽章**(使用者決策 Q2 = C)。
|
||
|
||
---
|
||
|
||
## 1. 總覽:三平台產出物
|
||
|
||
| 平台 | 格式 | 簽章 | 大小(預估) |
|
||
|------|------|------|------------|
|
||
| macOS 14/15 x86_64 | `visiona-local-v{ver}-macos-x64.dmg` 內含 `.app` | ad-hoc(`codesign -s -`) | ~195MB |
|
||
| Windows 10/11 x64 | `visiona-local-v{ver}-windows-x64.exe`(Inno Setup installer) | **無** | ~205MB |
|
||
| Ubuntu 22.04/24.04 x64 | `visiona-local-v{ver}-linux-x64.AppImage` | 無 | ~210MB |
|
||
|
||
## 2. macOS:.app + .dmg
|
||
|
||
### 2.1 建置流程
|
||
|
||
```bash
|
||
# 1. 準備 payload
|
||
make payload-macos # 把 python + wheels + ffmpeg + models 塞到 visiona-local/payload/
|
||
|
||
# 2. Wails 編譯
|
||
cd visiona-local && wails build -platform darwin/amd64 -clean
|
||
|
||
# 3. ad-hoc sign
|
||
codesign --force --deep --sign - build/bin/visiona-local.app
|
||
|
||
# 4. 驗證簽章
|
||
codesign -dv --verbose=4 build/bin/visiona-local.app
|
||
|
||
# 5. 打包成 dmg
|
||
dmgbuild -s dmg-config.py "visionA-local" dist/visiona-local-v${VERSION}-macos-x64.dmg # 顯示標題沿用產品名 visionA-local
|
||
```
|
||
|
||
### 2.2 `wails.json` 關鍵設定
|
||
|
||
```json
|
||
{
|
||
"name": "visiona-local",
|
||
"outputfilename": "visiona-local",
|
||
"frontend:install": "echo skip",
|
||
"frontend:build": "echo skip",
|
||
"info": {
|
||
"companyName": "Innovedus",
|
||
"productName": "visionA-local",
|
||
"productVersion": "1.0.0",
|
||
"copyright": "© 2026 Innovedus",
|
||
"comments": "Kneron KL520/KL720 本地開發工具"
|
||
},
|
||
"nsisType": "multiple",
|
||
"appid": "com.innovedus.visiona-local"
|
||
}
|
||
```
|
||
|
||
### 2.3 Info.plist 關鍵項
|
||
|
||
- `CFBundleIdentifier = com.innovedus.visiona-local`
|
||
- `CFBundleExecutable = visiona-local`(binary 全小寫;`CFBundleName` / `CFBundleDisplayName` 仍為 `visionA-local` 作為顯示名)
|
||
- `NSHighResolutionCapable = true`
|
||
- `LSMinimumSystemVersion = 14.0`
|
||
- `NSCameraUsageDescription = visionA-local 需要存取攝影機以執行 AI 推論展示`
|
||
- `NSAppleEventsUsageDescription = visionA-local 需要使用 AppleScript 開啟瀏覽器`
|
||
- **不需要** `NSAppTransportSecurity` 因為只用 localhost
|
||
|
||
### 2.4 dmgbuild 設定檔(`dmg-config.py`)
|
||
|
||
```python
|
||
# dmg-config.py
|
||
format = 'UDBZ' # bzip2 壓縮,壓縮率好
|
||
size = '400M'
|
||
files = ['build/bin/visiona-local.app']
|
||
symlinks = {'Applications': '/Applications'}
|
||
badge_icon = 'build/icon.icns'
|
||
icon_locations = {
|
||
'visiona-local.app': (150, 200),
|
||
'Applications': (450, 200),
|
||
}
|
||
window_rect = ((200, 200), (600, 400))
|
||
background = 'build/dmg-background.png'
|
||
```
|
||
|
||
### 2.5 首次啟動:Gatekeeper workaround
|
||
|
||
**因為沒有 notarization,使用者第一次打開 .app 會跳警告:**
|
||
> "visionA-local" can't be opened because it is from an unidentified developer.
|
||
|
||
**解法(必須寫進 README 與首次啟動說明頁):**
|
||
1. 在 Finder 中對 `visiona-local.app` **按右鍵 → 開啟**
|
||
2. 在警告對話框按「開啟」
|
||
3. 之後可以正常雙擊開啟
|
||
|
||
或命令列:`xattr -d com.apple.quarantine /Applications/visiona-local.app`
|
||
|
||
## 3. Windows:Inno Setup .exe
|
||
|
||
### 3.1 為何選 Inno Setup 而不是 NSIS / MSI
|
||
|
||
- **UI 現代化**:Inno Setup 6 的預設樣式比 NSIS 好看
|
||
- **腳本簡單**:Pascal-like DSL,比 NSIS 的 MakeNSIS 好維護
|
||
- **安裝 driver 方便**:內建 `DriverInstall` 流程支援 pnputil
|
||
- **不用 MSI**:MSI 需要 WiX 工具鏈與 signing 才順,我們不簽章,Inno Setup 更簡單
|
||
|
||
### 3.2 建置流程
|
||
|
||
```bash
|
||
# 1. 準備 payload
|
||
make payload-windows
|
||
|
||
# 2. Wails 編譯
|
||
cd visiona-local && wails build -platform windows/amd64 -clean
|
||
|
||
# 3. 執行 Inno Setup Compiler
|
||
iscc visiona-local-installer.iss
|
||
|
||
# 產物:dist/visiona-local-v{ver}-windows-x64.exe
|
||
```
|
||
|
||
### 3.3 `visiona-local-installer.iss` 骨架
|
||
|
||
```pascal
|
||
[Setup]
|
||
AppId={{A7F3E891-4B2C-4D5E-9F1A-8B3C2D1E0F9A}
|
||
AppName=visionA-local
|
||
AppVersion=1.0.0
|
||
AppPublisher=Innovedus
|
||
AppPublisherURL=https://innovedus.com
|
||
DefaultDirName={autopf}\visiona-local
|
||
DefaultGroupName=visiona-local
|
||
OutputDir=..\dist
|
||
OutputBaseFilename=visiona-local-v1.0.0-windows-x64
|
||
SetupIconFile=assets\icon.ico
|
||
Compression=lzma2/ultra64
|
||
SolidCompression=yes
|
||
WizardStyle=modern
|
||
PrivilegesRequired=admin
|
||
ArchitecturesInstallIn64BitMode=x64
|
||
MinVersion=10.0.17763
|
||
|
||
[Files]
|
||
Source: "visiona-local\build\bin\visiona-local.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||
Source: "visiona-local\payload\*"; DestDir: "{app}\payload"; Flags: ignoreversion recursesubdirs
|
||
|
||
[Icons]
|
||
Name: "{group}\visionA-local"; Filename: "{app}\visiona-local.exe"
|
||
Name: "{autodesktop}\visionA-local"; Filename: "{app}\visiona-local.exe"; Tasks: desktopicon
|
||
|
||
[Tasks]
|
||
Name: "desktopicon"; Description: "建立桌面捷徑"; GroupDescription: "附加選項:"
|
||
|
||
[Run]
|
||
; 安裝 WinUSB driver
|
||
Filename: "{sys}\pnputil.exe"; \
|
||
Parameters: "/add-driver ""{app}\payload\drivers\kneron_winusb.inf"" /install"; \
|
||
StatusMsg: "正在安裝 Kneron USB driver..."; \
|
||
Flags: runhidden waituntilterminated
|
||
|
||
; 啟動 app
|
||
Filename: "{app}\visiona-local.exe"; \
|
||
Description: "啟動 visionA-local"; \
|
||
Flags: postinstall nowait skipifsilent
|
||
|
||
[UninstallDelete]
|
||
Type: filesandordirs; Name: "{localappdata}\visiona-local"
|
||
```
|
||
|
||
### 3.4 SmartScreen 警告處理
|
||
|
||
**沒有 Authenticode 簽章,Windows SmartScreen 會擋:**
|
||
> Windows protected your PC — Microsoft Defender SmartScreen prevented an unrecognized app from starting.
|
||
|
||
**解法(寫進安裝說明):**
|
||
1. 點「更多資訊」
|
||
2. 點「仍要執行」
|
||
|
||
使用者第一次下載後會遇到這個警告,執行過一次之後 Windows 會記住。這是可接受的摩擦成本(使用者決策)。
|
||
|
||
## 4. Linux:AppImage
|
||
|
||
### 4.1 為何選 AppImage 而不是 .deb / snap / flatpak
|
||
|
||
- **單檔可攜**:一個 `.AppImage` 檔案,雙擊即跑,不需安裝
|
||
- **跨發行版**:只要 glibc >= 2.28(Ubuntu 18.04+)就能跑
|
||
- **不需 sudo**(正常情境下)
|
||
- **符合「像一般 app」的體驗**
|
||
|
||
`.deb` 需要 apt install + sudo;snap / flatpak 需要發到 store 或有 runtime 依賴,不適合內部工具分發。
|
||
|
||
### 4.2 建置流程
|
||
|
||
```bash
|
||
# 1. 準備 payload
|
||
make payload-linux
|
||
|
||
# 2. Wails 編譯
|
||
cd visiona-local && wails build -platform linux/amd64 -clean
|
||
|
||
# 3. 建立 AppDir
|
||
rm -rf AppDir && mkdir -p AppDir/usr/bin AppDir/usr/lib
|
||
cp build/bin/visiona-local AppDir/usr/bin/
|
||
cp -r payload AppDir/usr/bin/payload
|
||
|
||
# 4. 複製 libusb 到 AppDir(避免依賴系統 libusb)
|
||
cp /usr/lib/x86_64-linux-gnu/libusb-1.0.so.0 AppDir/usr/lib/
|
||
|
||
# 5. 寫入 .desktop 與 AppRun
|
||
cat > AppDir/visiona-local.desktop <<EOF
|
||
[Desktop Entry]
|
||
Type=Application
|
||
Name=visionA-local
|
||
Exec=visiona-local
|
||
Icon=visiona-local
|
||
Categories=Development;
|
||
EOF
|
||
|
||
cat > AppDir/AppRun <<'EOF'
|
||
#!/bin/bash
|
||
HERE="$(dirname "$(readlink -f "$0")")"
|
||
export LD_LIBRARY_PATH="$HERE/usr/lib:${LD_LIBRARY_PATH}"
|
||
exec "$HERE/usr/bin/visiona-local" "$@"
|
||
EOF
|
||
chmod +x AppDir/AppRun
|
||
|
||
cp assets/icon.png AppDir/visiona-local.png
|
||
|
||
# 6. 用 appimagetool 打包
|
||
ARCH=x86_64 appimagetool AppDir dist/visiona-local-v${VERSION}-linux-x64.AppImage
|
||
```
|
||
|
||
### 4.3 首次執行需要的權限
|
||
|
||
因為要寫入 `/etc/udev/rules.d/99-kneron.rules` 讓非 root 使用者存取 USB:
|
||
|
||
**首次執行時跳 pkexec 提權對話框(GNOME 預設可用),執行:**
|
||
```bash
|
||
pkexec cp ~/.local/share/visiona-local/scripts/99-kneron.rules /etc/udev/rules.d/
|
||
pkexec udevadm control --reload-rules
|
||
pkexec udevadm trigger
|
||
```
|
||
|
||
使用者拒絕或系統沒有 pkexec(例如 minimal server 安裝):顯示錯誤訊息提示手動執行 `sudo ./install-udev.sh`,內附的 script 由我們提供。
|
||
|
||
### 4.4 AppImage 的已知限制
|
||
|
||
- **沒有 .desktop integration**:使用者要自己放到 `~/Applications/` 或用 `appimaged`
|
||
- **第一次解壓慢**:AppImage 是 squashfs,第一次啟動會把內容 mount 到 `/tmp/.mount_*/`
|
||
|
||
## 5. 圖示與品牌資產
|
||
|
||
### 5.1 檔案清單
|
||
|
||
| 平台 | 檔案 | 尺寸 |
|
||
|------|------|------|
|
||
| macOS | `visiona-local.icns` | 512×512 @1x + @2x |
|
||
| Windows | `visiona-local.ico` | 16, 32, 48, 64, 128, 256 |
|
||
| Linux | `visiona-local.png` | 256×256 |
|
||
|
||
> Tray 圖示已移除(第三輪使用者決策 Q-A=A3:砍 tray)。
|
||
|
||
### 5.2 來源
|
||
|
||
使用者決策 Q14:**先沿用 edge-ai-platform 既有視覺(字母 E)**,品牌換名但圖示暫時不動。
|
||
|
||
從 `edge-ai-platform/installer/frontend/src-tauri/icons/` 直接複製(`server/tray/assets/` 不再使用)。
|
||
|
||
## 6. 首次啟動警告匯總(使用者要看到的文件)
|
||
|
||
必須在以下位置寫清楚:
|
||
1. `README.md`(GitHub / Gitea 發布頁)
|
||
2. 下載頁(Gitea Release 描述)
|
||
3. 首次啟動歡迎頁(如果有的話,可跳過)
|
||
|
||
**內容模板:**
|
||
|
||
```markdown
|
||
## 首次啟動提示
|
||
|
||
visionA-local 是內部工具,**沒有 Apple / Microsoft 的程式碼簽章**。
|
||
首次執行時作業系統可能會顯示警告,這是正常的。
|
||
|
||
### macOS
|
||
在 Finder 中找到 `visiona-local.app`,**按右鍵 → 開啟**,在彈出對話框中選「開啟」。
|
||
之後可以正常雙擊開啟。
|
||
|
||
### Windows
|
||
執行安裝檔時若看到 "Windows 已保護您的電腦" 警告:
|
||
點「更多資訊」→「仍要執行」。
|
||
|
||
### Linux
|
||
AppImage 預設沒有執行權限:
|
||
```
|
||
chmod +x visiona-local-v1.0.0-linux-x64.AppImage
|
||
./visiona-local-v1.0.0-linux-x64.AppImage
|
||
```
|
||
```
|
||
|
||
## 7. 發布流程
|
||
|
||
| 步驟 | 負責 | 工具 |
|
||
|------|------|------|
|
||
| 1. Tag release | 開發者 | `git tag v1.0.0 && git push --tags` |
|
||
| 2. CI build macOS | GitHub Actions / local | `make installer-macos` |
|
||
| 3. CI build Windows | GitHub Actions / local | `make installer-windows` |
|
||
| 4. CI build Linux | GitHub Actions / Docker | `make installer-linux` |
|
||
| 5. 上傳到 Gitea Release | 開發者 | `gh release create` 或手動 |
|
||
| 6. 更新 `latest.json`(如果未來做 auto-update) | - | 目前不做 |
|
||
|
||
**不建置 universal / multi-arch**,只有 x64。
|