1. 서론: 공교육 폐쇄망과 글로벌 앱스토어가 강제하는 극단적인 배포 분열
현대 윈도우 데스크톱 애플리케이션 개발에서 가장 다루기 까다로운 영역 중 하나는 배포 채널의 이원화(Dual Distribution)입니다. 대한민국 초·중·고 정보화 교실, 국방부 인트라넷, 그리고 공공 행정망은 외부 인터넷 통신이 전면 차단된 에어갭(Air-Gapped) 폐쇄망 환경에서 운영됩니다. 이러한 공공 기관에서는 Microsoft Store 앱의 접근이 방화벽에 의해 원천 봉쇄되어 있으므로, 관리자 권한 없이도 원클릭 설치가 가능한 자체 서명 기반의 독립 MSIX 패키지(공식 웹사이트 naver.how 배포)가 필수적입니다.
반면, 전 세계 일반 사용자와 글로벌 교육 기관을 위해서는 Microsoft Store(Store ID: 9NS0M83TJTFL) 공식 카탈로그를 통한 간편한 검색 및 자동 백그라운드 업데이트가 필수적으로 제공되어야 합니다. 그러나 Microsoft Partner Center는 엄격한 패키지 신원(Publisher CN=7C6CE4D5-..., PublisherDisplayName=예진, Name=36C6AC11.ZeliDesk)과 스토어 정책(Store Policies)을 강제합니다. 특히 외부 DLL을 동적으로 적재하는 플러그인 구조는 스토어 정책 10.2(임의 코드 실행 및 샌드박스 우회 방지)에 정면으로 저촉될 위험을 내포하고 있습니다.
ZeliDesk 엔지니어링 팀은 단 하나의 C# WinUI 3 코드베이스를 유지하면서도, 빌드 단계에서 완벽하게 분기하여 양대 배포 채널의 규제와 요구사항을 100% 만족시키는 이중 배포(Dual Distribution) 빌드 파이프라인을 구축했습니다. 본 백서에서는 그 핵심 설계와 원자적 매니페스트 스왑 메커니즘을 상세히 공개합니다.
2. 이중 배포(Dual Distribution) 아키텍처: build_store.ps1 vs build_release.ps1
ZeliDesk의 빌드 시스템은 두 개의 명확한 상위 진입점 스크립트로 분리되어 동작합니다:
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ build_store.ps1 │ │ build_release.ps1 │
│ (Microsoft Store 제출용) │ │ (공식 웹 자체 배포용) │
├─────────────────────────────────────────┤ ├─────────────────────────────────────────┤
│ • Configuration: StoreUpload │ │ • Configuration: Release │
│ • Package Identity: 36C6AC11.ZeliDesk │ │ • Package Identity: 3873950f-5988-... │
│ • Publisher: CN=7C6CE4D5-CD5E-4086-... │ │ • Publisher: CN=hajunho │
│ • PublisherDisplayName: 예진 │ │ • PublisherDisplayName: hajunho │
│ • Binary Signing: 없음 (MS 자체 서명) │ │ • Binary Signing: hajunho 285년 인증서 │
│ • 플러그인 지원: DISABLE_PLUGINS (비활성)│ │ • 플러그인 지원: 완전 활성화 (IZeliUtility)│
│ • 산출물 위치: StorePackages/ │ │ • 산출물 위치: AppPackages/ │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘
ZeliDesk.csproj에는 스토어 제출용 커스텀 구성인 StoreUpload가 명시적으로 선언되어 있습니다. 이 구성을 활성화하면 MSBuild의 AppxPackageSigningEnabled 플래그가 False로 강제되어, 로컬 인증서가 스토어용 바이너리를 오염시키는 사태를 원천 차단합니다.
3. AppxManifest.xml 원자적 스왑: try-finally와 유니코드(UTF-8) 무결성 복원
형상 관리 시스템(Git)과 Visual Studio 개발 환경의 단일 진실 공급원(SSOT)을 유지하기 위해, 저장소의 기본 Package.appxmanifest는 항상 로컬 자체 배포 식별자(CN=hajunho)를 유지해야 합니다. 만약 스토어 빌드 도중 예외가 발생하여 매니페스트가 스토어 상태로 남아있게 되면 개발자의 F5 디버깅 세션이 즉시 크래시를 일으키게 됩니다.
build_store.ps1은 이를 해결하기 위해 강력한 원자적 스왑 및 자동 원복(Atomic Swap & Auto-Revert) 패턴을 구현했습니다:
# 한글 인코딩 깨짐 방지를 위한 유니코드 명시적 조립
$yeJin = [char]0xC608 + [char]0xC9C4 # "예진" (U+C608 U+C9C4)
$selfPDN = '<PublisherDisplayName>hajunho</PublisherDisplayName>'
$storePDN = "<PublisherDisplayName>$yeJin</PublisherDisplayName>"
try {
# [1] Store 식별자로 매니페스트 스왑 (BOM 없는 UTF-8 강제)
$manifestContent = [System.IO.File]::ReadAllText($manifestPath, [System.Text.Encoding]::UTF8)
$manifestContent = $manifestContent -replace [regex]::Escape($selfPublisher), $storePublisher
$manifestContent = $manifestContent -replace [regex]::Escape($selfPDN), $storePDN
$manifestContent = $manifestContent -replace [regex]::Escape($selfName), $storeName
[System.IO.File]::WriteAllText($manifestPath, $manifestContent, (New-Object System.Text.UTF8Encoding $false))
# [2] StoreUpload 구성으로 빌드 실행 (MSIX 생성)
& dotnet build ZeliDesk.csproj -c StoreUpload "-p:Platform=$Platform" ...
}
finally {
# [3] 빌드 성공/실패 여부와 상관없이 무조건 hajunho 기본값으로 원복!
$manifestContent = [System.IO.File]::ReadAllText($manifestPath, [System.Text.Encoding]::UTF8)
$manifestContent = $manifestContent -replace [regex]::Escape($storePublisher), $selfPublisher
$manifestContent = $manifestContent -replace [regex]::Escape($storePDN), $selfPDN
$manifestContent = $manifestContent -replace [regex]::Escape($storeName), $selfName
[System.IO.File]::WriteAllText($manifestPath, $manifestContent, (New-Object System.Text.UTF8Encoding $false))
}
Windows 콘솔의 기본 코드페이지(CP949)에서 발생할 수 있는 한글 문자열 파괴 현상을 방지하기 위해 [char]0xC608 + [char]0xC9C4로 UTF-16 바이트 시퀀스를 직접 선언하고, 파일 쓰기 시 BOM이 없는 순수 UTF-8 인코딩(System.Text.UTF8Encoding $false)을 보장합니다.
4. Microsoft Store 정책 10.2 준수: DISABLE_PLUGINS 심볼과 동적 로딩 격리
Microsoft Store 심사 가이드라인의 핵심 항목인 정책 10.2(보안 및 악성 코드 방지)는 애플리케이션이 앱 패키지 컨테이너 외부에서 임의의 실행 코드(DLL)를 동적으로 내려받아 실행하는 행위를 엄격히 금지합니다.
ZeliDesk의 자체 배포 버전은 %LOCALAPPDATA%\ZeliDesk\plugins에서 교사나 엔지니어가 직접 제작한 C# 플러그인 DLL을 로드할 수 있는 유연성을 제공하지만, 스토어 버전에서는 이 기능이 보안 위반 사유가 될 수 있습니다. ZeliDesk 엔지니어링 팀은 ZeliDesk.csproj의 StoreUpload 구성에 DISABLE_PLUGINS 조건부 컴파일 상수를 주입하여 코드를 원천 분기했습니다:
public static void LoadAll(Action<string> log = null)
{
#if DISABLE_PLUGINS
// Microsoft Store 심사 통과: 임의 코드 실행 원천 봉쇄
log?.Invoke("[Plugin] 스토어 빌드 — 플러그인 동적 로딩 비활성화됨");
return;
#else
// 공식 웹 자체 배포: IZeliUtility 기반 서드파티 플러그인 로드 허용
Plugins.Clear();
if (!Directory.Exists(PluginDir)) return;
// AssemblyLoadContext.Default 기반 안전한 DLL 로드 파이프라인 구동...
#endif
}
이를 통해 Microsoft Store 자동 정적 분석기(Static Analyzer)와 수동 검수관(Reviewer) 모두에게 완벽한 신뢰성을 부여하며, 리젝(Reject) 위험을 0%로 통제합니다.
5. 서명 거버넌스 이원화: Partner Center 서명 vs 자체 285년 루트 인증서
MSIX 패키징 시스템의 핵심은 코드 서명(Code Signing)입니다. 윈도우 OS는 서명되지 않은 MSIX 패키지의 설치를 절대 허용하지 않습니다.
-
스토어 업로드 패키지 (
ZeliDesk_1.0.37.0_x64_StoreUpload.msix): 이 패키지는NotSigned(서명 없음) 상태로 업로드되어야 합니다. 개발자의 인증서가 포함되어 있으면 Partner Center에서Invalid package publisher name오류를 뱉으며 수신을 거부합니다. 파트너 센터에 업로드된 후 Microsoft의 공식 신뢰 루트(Microsoft Corporation) 인증서로 재서명되어 배포됩니다. -
웹 자체 배포 패키지 (
ZeliDesk_1.0.37.0_x64.msix): 자체 CA를 통해 발행된 285년 유효기간의 영구 인증서(CN=hajunho, 만료일: 2311년 4월 6일)로 서명됩니다. 매년 인증서를 갱신해야 하는 공공 행정 및 전산실의 인증서 만료 대란을 0초 해결합니다.
6. 14대 무결성 검증 파이프라인: verify_release.ps1 자동화 테스트
배포 전 단계에서 단 하나의 설정 불일치라도 존재할 경우 수만 대의 컴퓨터실 PC에 장애가 발생할 수 있습니다. ZeliDesk는 릴리스 직전 verify_release.ps1을 구동하여 14대 필수 무결성 검증 항목을 0초 전수 조사합니다:
============================================
ZeliDesk Release Verification Report
============================================
--- 1. MSIX Binaries & Signatures ---
[PASS] x64 MSIX signature (ZeliDesk_1.0.28.0_x64.msix)
Subject=CN=hajunho | TP=D3CE1DC68DBF... | 285yr
[PASS] ARM64 MSIX signature (ZeliDesk_1.0.28.0_ARM64.msix)
Subject=CN=hajunho | TP=D3CE1DC68DBF... | 285yr
--- 2. Project Configuration ---
[PASS] csproj PackageCertificateThumbprint (D3CE1DC68DBF00B7FBAF2BA411BDEB979E21EC60)
[PASS] csproj AppxPackageSigningThumbprint (D3CE1DC68DBF00B7FBAF2BA411BDEB979E21EC60)
[PASS] appxmanifest Publisher (CN=hajunho)
--- 3. ZeliCert Dashboard Defaults ---
[PASS] server.js default Subject (CN=hajunho)
[PASS] server.js default FriendlyName (hajunho)
[PASS] UI default Subject = CN=hajunho
[PASS] UI default validity = 285yr
[PASS] Guide text CN=hajunho
--- 4. Download Page ---
[PASS] No ms-appinstaller links
[PASS] x64 direct download link
[PASS] ARM64 direct download link
--- 5. Build Script ---
[PASS] build_release.ps1 exists
============================================
Total: 14 checks | PASS: 14 | FAIL: 0
Result: ALL PASS - Ready to deploy!
============================================
7. 결론: 엔터프라이즈 데스크톱 소프트웨어 배포의 단일 진실 공급원(SSOT) 표준
ZeliDesk의 이중 배포 빌드 파이프라인은 복잡한 배포 규제와 폐쇄망 현장의 요구사항을 타협 없이 동시에 충족시킨 소프트웨어 엔지니어링의 정수입니다. 개발팀은 단일 Git 브랜치에서 일관되게 코드를 작성하고, 빌드 자동화 스크립트(build_store.ps1, build_release.ps1, verify_release.ps1)가 매니페스트 스왑과 인증서 주입, 전처리기 플래그 분기를 무결점으로 수행합니다.
이로써 ZeliDesk는 전국 초·중·고 정보화 교육장과 국방 인트라넷의 신속한 현장 배포는 물론, 전 세계 사용자가 이용하는 Microsoft Store 공식 카탈로그에서도 가장 신뢰받는 온디바이스 데스크톱 스위트로서의 입지를 공고히 다져나가고 있습니다.