콘텐츠로 이동

BDIH Launcher 빌드

이 문서는 BDIH-Launcher 저장소를 기준으로 런처 앱을 빌드하고 패키징하는 방법을 정리합니다. 단순히 pnpm build 명령만 나열하는 것이 아니라, 저장소의 docs/에 정리된 배포 채널, 서명, 업데이트 테스트, 데이터 보존, Bottle 실행 구조까지 함께 설명합니다.

BDIH-Launcher는 macOS arm64를 대상으로 하는 Electron 앱입니다. 빌드 결과는 dist/에 JavaScript bundle로 생성되고, electron-builder가 이를 .app, .dmg, .zip 형태로 패키징합니다.

Main

Electron main process입니다. Bottle, Wine, DXMT, update, process, log, preference 같은 런처 핵심 동작을 관리합니다.

Preload

Renderer와 Main 사이의 IPC bridge를 제공합니다.

Renderer

React 기반 UI입니다. Main에서 제공하는 상태 snapshot과 IPC 결과를 화면에 표시합니다.

Guardian

런처가 비정상 종료될 때 관리 중인 Wine process를 정리하는 native C helper입니다.

주요 기준은 다음과 같습니다.

항목기준
대상 OSmacOS
대상 CPUarm64
패키저electron-builder
UIReact 19
언어TypeScript 6
번들러Webpack 5
업데이트electron-updater

의존성은 pnpm-lock.yaml을 기준으로 설치합니다.

Terminal window
pnpm install --frozen-lockfile

저장소에는 별도 Node.js engine이 고정되어 있지 않으므로, CI 또는 로컬 기준 Node.js 버전을 맞춘 뒤 lockfile을 기준으로 설치하는 것이 좋습니다. macOS 패키징과 native helper 검증에는 macOS 환경이 필요합니다.

가장 기본적인 빌드는 다음 명령입니다.

Terminal window
pnpm build

pnpm build는 내부적으로 다음 순서로 실행됩니다.

pnpm build:guardian
pnpm build:preload
pnpm build:renderer
pnpm build:main

각 명령의 역할은 다음과 같습니다.

명령역할
pnpm build:guardiannative/BDIHGuardian/guardian.c를 빌드하고 app bundle에 넣을 native helper를 준비합니다.
pnpm build:preloadPreload script를 production Webpack 설정으로 빌드합니다.
pnpm build:rendererReact Renderer를 production bundle로 빌드합니다.
pnpm build:mainElectron Main process bundle을 빌드합니다.
pnpm start이미 빌드된 dist/main/main.js를 기준으로 Electron 앱을 실행합니다.
pnpm start:all빌드 후 Electron 앱을 실행합니다.

개발 중 Main과 Preload만 빠르게 다시 묶을 때는 다음 명령을 사용할 수 있습니다.

Terminal window
pnpm build:dev
pnpm build:dev:renderer

unpacked app 디렉터리만 확인하려면 pack을 사용합니다.

Terminal window
pnpm pack

배포용 .dmg와 .zip까지 만들려면 dist를 사용합니다.

Terminal window
pnpm dist

기본 출력 경로는 release/입니다. 패키지에는 dist/**/*, package.json, 앱 아이콘, ko.lproj, locale 파일, native Guardian이 포함됩니다.

release/
mac-arm64/
BDIH Launcher.app
BDIH-Launcher-<version>-arm64.dmg
BDIH-Launcher-<version>-arm64.zip
latest-mac.yml
*.blockmap

electron-builder.config.cjs는 Stable, Beta, Nightly를 하나의 production 설정에서 처리합니다. 주요 환경 변수는 다음과 같습니다.

변수역할
UPDATE_CHANNELlatest, beta, nightly update feed를 선택합니다.
RELEASE_TYPEGitHub Release의 release 또는 prerelease 타입을 선택합니다.
BDIH_RELEASE_CHANNEL런처 내부 채널 marker를 stable, beta, nightly 중 하나로 기록합니다.
BDIH_REQUIRE_CODE_SIGNINGtrue일 때 electron-builder 서명을 필수로 요구합니다.
PUBLISH_REPOSITORYelectron-builder publish 대상 GitHub repository를 지정합니다.

Nightly는 BDIH Launcher Nightly product name과 day.faby.bdih-launcher.nightly bundle identifier를 사용합니다. Stable과 Beta는 일반 BDIH Launcher product name과 day.faby.bdih-launcher bundle identifier를 공유합니다.

Staging은 production 앱을 그대로 덮어쓰는 테스트 앱이 아닙니다. electron-builder.staging.config.cjs는 별도 product name, bundle identifier, app data를 사용합니다.

항목값
Product nameBDIH Launcher Staging
Bundle IDday.faby.bdih-launcher.staging
기본 release repositoryBob-Ddong-Iri-Hoyo/BDIH-Launcher-TestProduction
Stable feedlatest
Beta feedbeta

Staging 빌드는 CI에서 주로 사용하지만, 구조상 다음 환경 값으로 channel과 version을 주입합니다.

BDIH_STAGING_VERSION
BDIH_STAGING_CHANNEL
BDIH_STAGING_SOURCE_COMMIT
BDIH_STAGING_OUTPUT_DIR

Staging artifact 이름은 architecture를 생략합니다. 현재 staging이 arm64-only라는 전제를 명확히 하기 위한 선택입니다.

BDIH-Launcher-Staging-Stable-1.0.0-rc.2.dmg
BDIH-Launcher-Staging-Stable-1.0.0-rc.2.zip
BDIH-Launcher-Staging-Beta-1.0.0-beta.1.staging.2.dmg
BDIH-Launcher-Staging-Beta-1.0.0-beta.1.staging.2.zip

업데이트 UI와 Squirrel.Mac 흐름은 일반 태그 릴리즈와 분리해서 테스트합니다. 로컬 업데이트 테스트 앱은 production 앱과 다른 identity와 저장소를 사용합니다.

항목Stable/Beta 테스트
Product nameBDIH Launcher Update Test
Bundle IDday.faby.bdih-launcher.update-test
앱 위치tests/Release/apps/stable-beta/BDIH Launcher Update Test.app
상태 루트tests/Release/state/stable-beta
로컬 feedhttp://127.0.0.1:45678/

Nightly 업데이트 테스트는 별도 product name과 bundle identifier를 사용합니다.

BDIH Launcher Nightly Update Test
day.faby.bdih-launcher.nightly.update-test

테스트 artifact를 만들 때는 다음 명령을 사용합니다.

Terminal window
pnpm run build:test -- --version 1.0.0 --channel stable
pnpm run build:test -- --version 1.1.0-beta.1 --channel beta
pnpm run build:test:nightly -- --version 1.2.0-nightly.1

여러 버전을 한 번에 만들 때는 range를 사용할 수 있습니다.

Terminal window
pnpm run build:test:stable -- --range 1.0.0~1.0.9
pnpm run build:test:beta -- --range 1.1.0-beta.1~1.1.0-beta.9

로컬 feed를 띄운 뒤, 설치된 테스트 앱을 통해 업데이트를 확인합니다.

Terminal window
pnpm update:test:serve
pnpm install:test
pnpm reveal:test
pnpm start:test

Nightly는 다음 명령을 사용합니다.

Terminal window
pnpm install:test:nightly
pnpm reveal:test:nightly
pnpm start:test:nightly

업데이트 테스트 앱은 production Bottle, Wine, DXMT 경로를 거부하도록 만들어져 있습니다. production 설정 파일을 테스트 상태 디렉터리에 복사하지 않는 것이 원칙입니다.

기본 테스트는 Jest를 사용합니다.

Terminal window
pnpm test

빌드와 함께 자주 확인해야 하는 테스트는 다음과 같습니다.

명령확인 내용
pnpm test:guardianGuardian helper의 clean disarm, EOF, owner exit, signal cleanup을 확인합니다.
pnpm test:guardian:appElectron 앱과 Guardian을 함께 띄워 crash recovery와 single-instance 동작을 확인합니다.
pnpm test:update-build-script업데이트 테스트 artifact 빌드 스크립트의 version/range 해석을 확인합니다.
pnpm test:staging-promotion-policyStaging candidate와 production promotion 정책을 확인합니다.
pnpm test:signing-script.p12 생성, 변환, renewal 흐름을 확인합니다.
pnpm test:hoyo-proxy:buildwine-build에 있는 HoYoPlay proxy helper를 빌드합니다.

Renderer UI는 Storybook으로 확인할 수 있습니다.

Terminal window
pnpm storybook
pnpm build-storybook
pnpm screenshot

pnpm screenshot은 Storybook static build를 만든 뒤 launcher shell story를 캡처합니다. 출력은 다음 경로를 사용합니다.

output/screenshot/launcher-view.png

배포 워크플로는 BDIH Launcher Update Signing identity를 temporary Keychain에 설치하고, electron-builder가 이 identity로 app bundle을 서명하도록 강제합니다. secret이 없거나 잘못된 경우 ad-hoc signing으로 조용히 fallback하지 않고 빌드를 실패시키는 것이 기준입니다.

필요한 GitHub Actions secret은 다음과 같습니다.

Secret역할
MACOS_SIGNING_P12_BASE64signing .p12의 Base64 값입니다.
MACOS_SIGNING_P12_PASSWORD.p12 export password입니다.
STAGING_RELEASE_TOKENTestProduction repository에 release asset을 올리기 위한 fine-grained token입니다.

로컬에서 새 .p12를 만들 때는 다음 스크립트를 사용합니다.

Terminal window
./scripts/createP12.sh --copy-base64

이 signing identity는 Squirrel.Mac 업데이트 연속성을 위한 self-signed update identity입니다. Apple Developer ID 서명이나 notarization 신뢰를 제공하는 것은 아닙니다.

Production Stable과 Beta는 수동으로 production tag를 밀어서 만들지 않습니다. 검증된 TestProduction candidate를 승격하는 방식으로 만듭니다.

GitHub environment는 두 개를 사용합니다.

Environment역할
production-candidate-approvalTestProduction candidate를 확인한 뒤 unpublished Production Draft 생성을 허용합니다.
production-release이미 만들어진 Draft를 다시 검증한 뒤 사용자에게 공개합니다.

Stable 릴리즈의 기본 흐름은 다음과 같습니다.

package.json version 준비
-> TestProduction Candidate 실행
-> rc.N candidate 생성
-> candidate 테스트
-> production-candidate-approval 승인
-> Production Draft 생성
-> production-release 승인
-> v<version> 공개

Beta 릴리즈는 package.json의 release train을 유지한 채 Beta 번호를 workflow 입력으로 주입합니다. Staging suffix는 production version으로 들어가지 않습니다.

package.json: 1.2.3
TestProduction candidate: 1.2.3-beta.2.staging.3
Production release: 1.2.3-beta.2

Stable candidate는 다음 구조를 따릅니다.

package.json: 1.2.3
TestProduction candidate: 1.2.3-rc.2
Production release: 1.2.3

candidate tag와 release는 immutable하게 취급합니다. 같은 대상에 수정이 필요하면 기존 tag를 덮어쓰지 않고 다음 attempt를 만듭니다.

1.0.0-rc.1 -> 1.0.0-rc.2
1.0.0-beta.1.staging.1 -> 1.0.0-beta.1.staging.2

Stable, Beta, Nightly는 업데이트 채널뿐 아니라 app data cleanup 정책에도 영향을 줍니다. 런처는 마지막으로 성공적으로 열린 앱 버전과 channel을 app-data-lifecycle.json에 기록합니다.

retired file cleanup은 같은 channel에서 version이 바뀔 때만 허용합니다.

이전 빌드현재 빌드retired-file cleanup
StableStable허용
BetaBeta허용
NightlyNightly허용
StableBeta보존
BetaStable보존
첫 실행Any보존
같은 버전같은 channel생략

cleanup은 allowlist 기반입니다. Wine registry, prefix, drive, unknown file을 현재 빌드가 읽지 않는다는 이유만으로 삭제하지 않습니다. Stable과 Beta 사이를 이동할 때는 channel transition snapshot을 만들고, 데이터 호환성 확인이 끝난 뒤에만 다음 단계로 진행합니다.

런처 빌드에는 native Guardian과 Main-owned execution state가 함께 포함됩니다. 이 구조는 빌드 결과가 단순 UI 앱이 아니라 Wine process lifecycle을 직접 관리하는 앱이라는 점 때문에 중요합니다.

현재 실행 흐름은 다음처럼 정리할 수 있습니다.

Renderer
-> IPCManager
-> BottleExecutionManager
-> BottleExecutionStateRegistry
-> Wine/DXMT/Jadeite/process helpers
-> Renderer snapshot projection

Main process가 application launch ownership의 최종 권한을 가집니다. Renderer는 중복 클릭 방지나 process lifetime을 직접 판단하지 않고, Main이 제공하는 versioned snapshot을 표시합니다.

동시 실행 요청은 같은 logical target에 대해 하나의 launch promise로 합쳐집니다. 이미 실행 중인 target은 기존 logical process ID를 반환하고, stopping 상태의 target은 retry 가능한 실패로 처리합니다.

새 Wine runtime은 bdih.wine.process.v1 process telemetry를 제공할 수 있습니다. 런처는 runtime metadata가 정확한 capability를 선언할 때만 FIFO 환경 변수를 주입합니다.

WINE_BDIH_PROCESS_TELEMETRY=1
WINE_BDIH_PROCESS_PIPE=/absolute/path/to/process-events.fifo

Wine은 process fact만 보고하고, 어떤 프로세스가 Steam, HoYoPlay, updater, game인지 분류하는 책임은 런처에 있습니다. telemetry가 없는 runtime에서는 기존 wineserver observer와 process discovery가 fallback으로 동작합니다.

실행 구조는 점진적으로 Provider/Strategy 구조로 이동 중입니다. src/Main/Data 아래의 각 application profile과 strategy가 실행 요구사항을 선언하고, BottleExecutionManager는 그 요구사항을 검사한 뒤 실제 side effect를 수행합니다.

현재 등록된 application-owned Strategy 범위는 다음과 같습니다.

  • Generic Wine application launch와 installer execution
  • Steam launcher installation, launcher execution, Steam game execution
  • HoYoPlay installation과 supervised execution
  • ZZZ, Genshin, Star Rail 실행 요구사항

BDIH-Launcher 저장소의 docs/는 다음 역할을 가집니다.

문서역할
docs/build.md현재 내용이 없는 placeholder입니다. 실제 빌드 정보는 package scripts와 packaging config가 기준입니다.
docs/release.mdStable/Beta production promotion, candidate safety rule, version rule을 설명합니다.
docs/publish.mdmacOS signing, GitHub secret, staging candidate, certificate renewal을 설명합니다.
docs/update-testing.mdupdate checker UI, mock feed, signing-certificate transition test 방향을 설명합니다.
docs/test.mdisolated update test app, local feed, Stable/Beta/Nightly update 시나리오를 설명합니다.
docs/data-lifecycle.mdchannel-safe cleanup, app data ownership, snapshot 규칙을 설명합니다.
docs/bottle-execution.mdBottle 실행 구조, Guardian, execution state, Provider/Strategy migration을 설명합니다.
docs/TODO/wine-process-telemetry.mdWine process telemetry prototype과 남은 검증 항목을 정리합니다.
docs/TODO/bottle-app-launch-deduplication.md중복 실행 방지와 logical target ownership 규칙을 정리합니다.

런처 빌드나 배포를 변경할 때는 단순히 pnpm build 성공만 확인하지 말고, 변경 범위에 따라 위 문서의 정책과 테스트도 함께 확인해야 합니다.