d3ro-voice/docs/v2/phase-V2-5-mac-guide.md
윤찬 d397bcbf57 feat(desktop): LLM 기본 모델 qwen3:4b → gemma4:e4b 전면 전환 + think:false 안전장치
qwen3:4b가 reasoning 모델이라 <think>...</think> 블록을 길게 생성 →
stripReasoningBlocks 후 빈 문자열 → 원본 transcript fallback으로 끝나면서
LLM refine이 42초 걸리는 병목 발견. Google Gemma 4 e4b(4.5B effective
params, 2026-04-02 릴리스)로 교체. non-reasoning 기본 + Ollama v0.20+
think: false 파라미터로 2중 방어.

실측 결과: 받아쓰기 한 사이클 51.4s → 5.5s (9.3배 빠름).
  STT 500ms + LLM 3,925ms + insert 1,092ms.
refine 품질 정상 동작 확인: "테스트하는 중입니다" → "테스트하고 있습니다".

- LocalLLMService: 3개 fallback 기본값 변경(generate / streamGenerate /
  chatStream) + Ollama 요청 body에 think: false 명시 추가. non-reasoning
  모델은 무시, reasoning 모델은 thinking 토큰 차단. NO_THINK 주석을
  legacy 설명으로 업데이트 — qwen3/deepseek-r1 수동 선택자를 위한 3중
  방어(/no_think + think:false + stripReasoningBlocks) 명시.
- OnboardingModal / OllamaGuideModal: pull 명령어 갱신
- 테스트 fixture 갱신
- 12개 i18n locale JSON: settings.ollamaHint / ollama.step2.alt 키 업데이트
  (qwen3:4b → gemma4:e4b, qwen3:8b → gemma4:26b)
- 10개 site i18n locale TS + HowItWorks.tsx 파이프라인 시각화 — detail
  문자열 'qwen3 / llama3 / gemma3' → 'gemma4 / llama3.2 / phi4',
  파이프라인 라벨 'qwen3:4b @ localhost' → 'gemma4:e4b @ localhost'
- 설계서 00 LLMConfig 기본값 + CONFIG_DEFAULTS
- 설계서 05: 6개 API 스키마 예시, 2개 OllamaClient 코드 예시, LLM 모델
  추천 표 재정렬(gemma4:e4b 최상위, qwen3는 reasoning 경고와 함께 후순위),
  권장 JSON 설정에 think:false 추가
- phase-14 meeting mode 컨텍스트 윈도우 표 갱신
- V2-5 Mac 부트스트랩 가이드 pull 커맨드 갱신
- project_status.md Part 7 전체 섹션 추가
2026-04-12 09:47:08 +09:00

6.8 KiB

Phase V2-5: macOS 빌드 / 실행 / 배포 가이드

본인 Mac에서 D3RO Voice를 dev 실행하거나 dist(.dmg/.zip) 빌드하기 위한 단계별 가이드. Windows에서 작성된 코드는 V2-5에서 모든 플랫폼 분기를 추가했습니다 — Mac에서는 별도 코드 수정 없이 아래 단계만 따르면 됩니다.


0. 사전 조건

  • macOS 12 (Monterey) 이상 권장
  • Apple Silicon(M1/M2/M3) 또는 Intel 둘 다 지원
  • Xcode Command Line Tools (xcode-select --install)
  • Homebrew 설치 (brew --version으로 확인)

1. 시스템 의존성 설치

# 기본 도구
brew install node@22 python@3.11 git

# 오디오 캡처 (필수)
brew install sox

# Ollama (로컬 LLM, 선택)
brew install ollama
ollama serve &     # 백그라운드 실행
ollama pull gemma4:e4b

2. 프로젝트 클론 + 설치

git clone https://github.com/yunchan8804/d3ro-voice.git
cd d3ro-voice

# npm workspace 설치 (모든 패키지 한 번에)
npm install

# native 모듈 재빌드 (electron 33 ABI에 맞게)
npx @electron/rebuild@3 --version=33.4.11

3. SoX 바이너리 번들 (선택, dev에서는 PATH로 충분)

dev 실행만 할 거라면 brew install sox만으로 충분합니다. paths.tssox 시스템 명령을 자동 fallback으로 사용합니다.

dist 빌드를 할 거라면 resources/sox/에 바이너리를 미리 복사:

cd apps/desktop
bash scripts/install-sox.sh

이 스크립트는:

  1. command -v sox로 시스템 sox 위치 찾기
  2. apps/desktop/resources/sox/에 복사
  3. otool -L로 dylib 의존성 확인 후 함께 복사
  4. install_name_tool로 rpath를 @loader_path로 변경 (앱 번들 내부에서도 동작)

4. Python sidecar 빌드 (faster-whisper)

dev에서도 sidecar는 Python 직접 실행. dist에서는 PyInstaller로 묶어야 함.

cd apps/desktop

# 가상 환경 (선택, 권장)
python3.11 -m venv .venv-sidecar
source .venv-sidecar/bin/activate

pip install pyinstaller
pip install -r sidecar/requirements.txt

# 빌드 (sidecar-dist/sidecar/sidecar 생성)
python scripts/build-sidecar.py

빌드 결과: apps/desktop/sidecar-dist/sidecar/sidecar (실행 파일)

electron-builder.yml이 이 경로를 extraResources로 번들합니다.

5. 아이콘 준비 (dist 빌드 전)

apps/desktop/resources/icons/는 placeholder 디렉토리입니다.

cd apps/desktop/resources/icons

# 1024x1024 PNG 원본을 icon.png로 저장한 뒤:
mkdir -p icon.iconset
sips -z 16 16     icon.png --out icon.iconset/icon_16x16.png
sips -z 32 32     icon.png --out icon.iconset/icon_16x16@2x.png
sips -z 32 32     icon.png --out icon.iconset/icon_32x32.png
sips -z 64 64     icon.png --out icon.iconset/icon_32x32@2x.png
sips -z 128 128   icon.png --out icon.iconset/icon_128x128.png
sips -z 256 256   icon.png --out icon.iconset/icon_128x128@2x.png
sips -z 256 256   icon.png --out icon.iconset/icon_256x256.png
sips -z 512 512   icon.png --out icon.iconset/icon_256x256@2x.png
sips -z 512 512   icon.png --out icon.iconset/icon_512x512.png
sips -z 1024 1024 icon.png --out icon.iconset/icon_512x512@2x.png
iconutil -c icns icon.iconset
rm -r icon.iconset

자세한 내용: apps/desktop/resources/icons/README.md

6. dev 실행

# 루트에서 (npm workspace가 자동으로 apps/desktop 호출)
npm run dev

처음 실행 시 macOS가 다음 권한을 요구합니다:

권한 사용처 거부 시 영향
마이크 음성 인식 (필수) 녹음 불가
Accessibility 전역 핫키 (uiohook-napi), 활성 윈도우 조회 (AppleScript) 핫키/스크린 컨텍스트 동작 안 함
화면 녹화 스크린 컨텍스트 캡처 (선택) OCR/LLM 컨텍스트 비어있음
Apple Events 활성 앱 이름 조회 (osascript) appName이 null

권한은 시스템 설정 → 개인 정보 보호 및 보안에서 수동으로도 조정 가능합니다.

7. dist 빌드 (DMG/ZIP)

cd apps/desktop

# Apple Silicon만
npx electron-builder --mac --arm64

# Intel만
npx electron-builder --mac --x64

# 둘 다
npx electron-builder --mac

결과: apps/desktop/release/<version>/

  • D3RO Voice-1.0.0-arm64.dmg
  • D3RO Voice-1.0.0-arm64.zip
  • D3RO Voice-1.0.0.dmg (intel)
  • D3RO Voice-1.0.0.zip (intel)

8. Code Signing + Notarization (배포용)

8.1 Apple Developer 인증서 준비

  1. https://developer.apple.com → Account → Certificates
  2. +Developer ID Application (배포용) 생성
  3. 다운로드한 .cer을 키체인에 추가
  4. 키체인에서 인증서 + 개인 키를 함께 export → .p12 파일 (암호 설정)

8.2 환경 변수

# Code signing
export CSC_LINK="$(base64 -i path/to/cert.p12)"
export CSC_KEY_PASSWORD="<p12 암호>"

# Notarization (App-specific password 필요)
export APPLE_ID="your-apple-id@example.com"
export APPLE_APP_SPECIFIC_PASSWORD="abcd-efgh-ijkl-mnop"  # appleid.apple.com에서 생성
export APPLE_TEAM_ID="ABCDE12345"

App-specific password 생성: https://appleid.apple.com → Sign-In and Security → App-Specific Passwords

8.3 서명+공증 빌드

cd apps/desktop
npx electron-builder --mac -c.mac.notarize=true

공증은 보통 5~30분 걸립니다. 완료되면 자동으로 staple됩니다.

검증:

spctl --assess --verbose=4 release/<version>/D3RO\ Voice.app
# accepted source=Notarized Developer ID

9. CI 빌드 (GitHub Actions)

.github/workflows/build-mac.yml 워크플로우를 사용하여 자동 빌드:

# 태그 푸시 시 자동 트리거
git tag v1.0.0
git push origin v1.0.0

또는 GitHub UI에서 Actions → Build macOS → Run workflow.

필요한 GitHub Secrets

Secret 설명
APPLE_ID Apple 계정 이메일
APPLE_APP_SPECIFIC_PASSWORD App-specific password
APPLE_TEAM_ID 10자리 팀 ID
MAC_CERT_P12_BASE64 .p12 인증서를 base64 인코딩한 문자열
MAC_CERT_P12_PASSWORD .p12 암호

workflow_dispatch로 수동 실행 시 notarize=true를 선택하면 공증까지 진행.

10. 트러블슈팅

"App is damaged and can't be opened" (서명 없는 dmg)

xattr -cr /Applications/D3RO\ Voice.app

또는 시스템 설정 → 보안 → "확인 없이 열기"

sox: command not found (dev 모드)

brew install sox

ImportError: dlopen failed (sidecar 실행 시)

PyInstaller 빌드가 현재 Mac 아키텍처와 다를 가능성. arm64 Mac에서 arch -x86_64 python 같은 cross 빌드는 권장하지 않음.

Electron 앱이 즉시 종료

# 콘솔 로그 확인
~/Library/Logs/d3ro-voice/main.log
# 또는 dev 모드로 직접 실행
npm run dev

Accessibility 권한 거부 후 재요청

시스템 설정 → 개인정보보호 → 손쉬운 사용 → 좌하단 자물쇠 해제 → D3RO Voice 체크 권한 변경 후 앱 재시작.