326 KiB
변경 이력
English | 한국어
이 프로젝트의 주요 변경 사항은 이 문서에 기록됩니다.
형식은 Keep a Changelog를 기반으로 하며, 버전 정책은 Semantic Versioning을 지향합니다.
[Unreleased]
Fixed
- non-purge
uninstall.sh가 Windows VM 디스크를 삭제하지 않도록 수정 (#716, @munir-abbasi 데이터 손실 리포트 감사). 기본 VM 저장소가 앱 데이터 디렉토리 하위에 있는데(~/.local/share/winpodx/storage/data.img), "앱 정의 제거" 단계가rm -rf ~/.local/share/winpodx를 그냥 실행해서 — VM 데이터를 보존한다고 문서화된 non-purge 언인스톨이, 특히 무인 실행되는 패키지 매니저postrm재진입 경로에서storage/아래 Windows VM을 통째로 지웠습니다. 이제 두 경로를 정규화해서, 저장소 디렉토리가DATA_DIR자신이거나 그 하위이고--purge가 없으면 저장소 서브트리를 보존하고 나머지 앱 데이터 항목만 삭제합니다. 앱 데이터 디렉토리 밖의 커스텀WINPODX_STORAGE_PATH는 기존대로 손대지 않으며,--purge는 여전히 전부 삭제합니다. 언인스톨 스모크 하네스로 회귀 테스트(중첩 저장소·외부 저장소·purge 케이스).
Added
- Windows의 "Linux Apps" 바로가기에서 Linux 앱을 파일 없이 직접 실행 가능 (#616, @notnotno 기여 감사). Windows 시작 메뉴/바탕화면 "Linux Apps" 폴더의 reverse-open shim은 주로 "연결 프로그램" 선택 항목인데, 파일 없이 직접 클릭하면 게스트 shim이 파일 인자를 요구해서 조용히 종료돼 아무 일도 안 일어났습니다. 이제 launch-only 요청(
origin: "launch", 빈 경로)을 보내 호스트가 파일 없이 Linux 앱을 실행하므로(%f/%uplaceholder를 채우지 않고 제거), 그 바로가기가 일반 앱 런처로도 동작합니다. 갱신된 게스트 shim 필요(다음winpodx guest sync/apply-fixes때 재프로비저닝).
Fixed
- 최소 데스크톱에서 GUI의
libxcb-cursor0의존성을 자동 설치 (#712, @numericOverflow 기여 감사). Qt 6.5+ (PySide6)는libxcb-cursor.so.0없이는xcb플랫폼 플러그인을 시작하지 못하는데("could not load the Qt platform plugin 'xcb'"), 새 최소 설치(예: Linux Mint 22)에선 전이 의존성으로 딸려오지 않습니다. 이제install.sh가 GUI가 켜져 있고 런타임 lib가 없을 때 이를 설치합니다(배포판 매핑: Debian/Ubuntu/openSUSE는libxcb-cursor0, Fedora/Arch는xcb-util-cursor). 존재 여부는ldconfig -p가 아니라 표준 lib 디렉토리의 실제.so파일로 감지합니다 — openSUSE에선 라이브러리가/usr/lib64에 있지만 ld.so 캐시엔 없어서,ldconfig프로브가 패키지 설치 후에도 매번 다시 프롬프트를 띄웠습니다.
[0.9.0] - 2026-07-11
Added
- Windows 앱을 Linux에서 URL-scheme 핸들러로 등록 가능 (#421, #694 — 호스트측 기반). 앱이 URL 스킴(
mailto,https,slack,vnc, …)을 가지면.desktop에x-scheme-handler/<scheme>항목이 붙고Exec가 URL을 받아, 호스트에서 그런 링크를 클릭하면 Windows 앱에서 열립니다 — URL은$HOME파일 경로로 매핑하지 않고 앱에 그대로 전달됩니다. 공유 정책 모듈(denylist + 엄격한 스킴 정규식 +/app-cmd sanitizer)이 위험 스킴(file:,javascript:,data:, …)을 차단하고 등록·실행 양쪽에서 커맨드라인 인젝션을 무력화합니다. discovery가 이제 게스트에서 각 앱의 스킴을 자동 수확하므로(스킴별UrlAssociations기본값, 앱별Capabilities\URLAssociations, UWP 매니페스트의windows.protocol),mailto/https등을 처리하는 앱이 수동app.toml편집 없이 자동 등록됩니다.
Changed
- 선택적 Windows 디블로트가 더 많은 광고/텔레메트리를 끕니다 (#669, #684, @GameSoul7Eugene 기여 감사). 광고·제안 옵트아웃을 넓히고(광고 ID, 잠금화면 "팁", 시작/설정/검색 제안, 소비자 앱 무음 자동설치), SQM/CEIP 텔레메트리 예약 작업 비활성화를 추가했으며, RDP 친화적 시각 효과 조정을 적용했습니다 — 모두 보수적·되돌릴 수 있는 범위로 한정.
Fixed
- discovery 로 발견된 Windows 앱이 호스트의
http/https기본 핸들러를 조용히 탈취할 수 없게 함 (보안 강화). URL 스킴 핸들러 등록이 선언된 모든 스킴에 대해xdg-mime default를 실행해서, (반신뢰) 게스트에서 발견된 앱이app install --mime나doctor --fix를 통해http/https의 시스템 기본이 되어 사용자가 클릭하는 모든 웹 링크(세션 토큰 포함)를 받을 수 있었습니다. 이제 이 두 범용 웹 스킴은 자동 기본 획득에서 제외됩니다: Windows 브라우저는 여전히 URL 대상(app run <browser> https://…)으로 동작하고 "Open with" 목록에도 남지만, 웹 기본값으로 삼는 것은 명시적 옵트인이 됐습니다.mailto와 벤더 스킴은 영향 없음. - 세션 창-reaper 가 방금 재실행된 앱을 오살하거나, 일시적 스캔 실패에 전 세션을 몰살하지 않도록 수정 (#680 후속). 문서를 닫고 바로 다시 열면 새 세션을 SIGTERM 할 수 있었고(리퍼가 앱을 이름으로 추적하는데 재실행이 프로세스 마커를 덮어씀), 이제 각 reap 을 무장 시점의 정확한 PID 에 묶습니다. 또
wmctrl비정상 종료(윈도우 매니저 일시 사용중)를 "창 없음"으로 읽어 두 번 연속 실패 시 실행 중인 앱을 전부 죽였는데, 실패한 스캔을 이제 창 0개가 아니라 실패로 취급합니다. - 설치 원라이너가
git이 없으면 중단되지 않고 자동 설치하도록 수정 (#705, @numericOverflow 기여 감사).git이 없는 새 시스템(예: Linux Mint 22)에서curl … | bash설치가 clone 단계에서 "git is required for remote install" 로 실패했습니다 — 다른 의존성은 다 자동 설치하면서요. 이제 나머지와 같은 패키지 매니저 경로로git을 설치하고, 패키지 매니저가 제공하지 못할 때만 (수동 설치 안내와 함께) 오류를 냅니다. - kio-fuse 가 실제로 설치돼 있는데도 "미설치"로 표시되던 문제 수정 (#697, @twkirk161 기여 감사). Info/헬스 체크가
kio-fuse바이너리를 고정된 경로 몇 개에서만 찾아서, 다른 곳에 두는 배포판 — Fedora Kinoite / KF6 버전 libexec, 또는 Debian multiarch/usr/lib/<triplet>/libexec/kio-fuse(3단계 깊이라 기존 2단계 glob 이 놓침) — 에선 reverse-open 게스트 디스크 마운트(#616)가 잘 되는데도 미설치로 읽혔습니다. 이제 multiarch 경로와, 마운트가 실제로 호출하는 authoritative 신호인org.kde.KIOFuseD-Bus 활성화 서비스까지 확인합니다. - reverse-open listener 가 이제 트레이와 함께 자동 시작되고, 트레이 Quit 이 명시적으로 중지함 (#691, @notnotno 기여 감사). listener 는
pod start와 앱 실행 경로에서만 (재)시작돼서,winpodx gui만 켜거나 로그인 autostart 로 트레이만 뜨면 기능을 켜놔도 Windows→Linux "Open with" 가 조용히 죽어 있었습니다. 그리고 Quit 은 우연히만 listener 를 죽였습니다: listener 는 double-fork 데몬이라 부모의 커맨드라인을 상속하는데, GUI 설정 패널에서 시작한 경우에만 Quit 의pkill …gui패턴에 걸렸습니다. 이제 트레이가 시작 시 idempotent listener self-heal 을 (UI 스레드 밖에서) 실행하고, Quit 은stop_listener()를 명시적으로 호출합니다. - 빠른 절전/복구 감지가 이제 실제로 systemd-logind 를 구독함 (#690, @notnotno 기여 감사). 트레이의
PrepareForSleepD-Bus 구독이QDBusConnection.connect에 맨 파이썬 함수를 넘겼는데 PySide6가 런타임에 거부(called with wrong argument types) — 그래서 복구-후 빠른 pod 새로고침(#225)이 한 번도 작동하지 않고 모든 설치가 조용히 30초 폴링으로 폴백하며winpodx gui/트레이 시작마다 경고를 남겼습니다. 이제 QObject 리시버 +SLOT("onPrepareForSleep(bool)")시그니처로 구독하며(가비지 컬렉션에 연결이 끊기지 않게 트레이 아이콘에 보관),org.freedesktop.login1대상 라이브 검증 완료. - 창을 닫아도 남아있는 Windows 앱이 더 이상 영원히 "RUNNING"으로 표시되지 않음 (#680, @mdshahalam3 기여 감사). FreeRDP RemoteApp 클라이언트는 게스트가 연결을 끊어야 종료되는데, Office는 문서 창을 닫아도 프로세스(와 RemoteApp 세션)를 남겨둬서 세션이 끝나지 않고 앱이 영원히 RUNNING으로 뜨며 다음 열기를 깨뜨리는 반쯤-멈춘
\\tsclient\home리다이렉트를 남겼습니다. 이제 시스템 트레이 헬퍼가 모든 세션의 RAIL 창을 감시해, 앱의 창이 전부 닫히면(짧은 디바운스 후) 멈춘 세션을 종료해 running 목록에서 사라지게 합니다. 즉시 종료되는winpodx app run프로세스가 아니라 장수하는 트레이에서 돌기 때문에 메뉴/CLI 실행에서도 작동합니다. best-effort(wmctrl있는 X11/XWayland); 창이 실제로 보인 세션만 reap하므로 실행 중이거나 창 없는 앱을 조기에 죽이지 않습니다. - RemoteApp 실행마다 뜨던 FreeRDP의 무해한
[get_next_comma]: Invalid quoted argument경고 침묵 (#680, @mdshahalam3 기여 감사). 공백 포함 파일 경로가 쪼개지지 않게 유지하는cmd:"<UNC>"따옴표 형식에서 나온 경고입니다; 이제 FreeRDP 서브프로세스가WLOG_FILTER=com.winpr.commandline:FATAL로 실행돼 전달 커맨드라인은 그대로 둔 채 그 WLog 태그만 음소거합니다(/log-filters플래그는 파서보다 늦게 파싱돼 자기 경고를 못 막음). - Office 2016(MSI) 앱들이 더 이상 "Microsoft Office 2016 component" 하나의 깨진 엔트리로 뭉치지 않음 (#680 regression, @mdshahalam3 기여 감사). 0.8.0의 Start-Menu-only discovery가 각 바로가기의 raw
.TargetPath를 읽었는데, MSI 설치는 advertised shortcut(Windows Installer "Darwin descriptor")를 써서 그 타깃이C:\WINDOWS\Installer\{ProductCode}\아래 제품별 아이콘 스텁(예:xlicons.exe)으로 해석됩니다. 그 스텁들은 파일 설명 "Microsoft Office 2016 component"를 공유해 Word/Excel/PowerPoint/Outlook이 전부 동일해 보였고 dedup으로 실행 불가 엔트리 하나로 뭉쳐winpodx app list에서 사라졌습니다. 이제 advertised shortcut은 타깃 기록 전에MsiGetShortcutTarget+MsiGetComponentPath로 실제 설치 실행파일로 해석됩니다; 일반(Click-to-Run/직접) 바로가기는 영향 없음. - 이미 실행 중인 Windows 앱에 "연결 프로그램으로 열기"가 이제 새 창으로 안정적으로 파일을 엶 (#675 @ajeshchrist, #680 @mdshahalam3 기여 감사). 예전엔 조용히 아무것도 안 하거나(실행-중 경로가 모든 에러를 삼킴), 게스트 agent로 전달한 경우 "Access denied", 또는 ~30초 멈춘 뒤 crash처럼 재실행됐습니다. 원인: 게스트가 멀티세션 RDP라 이미 보이는 앱에 파일을 넘기려는 시도가 다른 세션에 착지합니다(agent는 아예 autologon 콘솔 세션에서 돌아 세션별
\\tsclient\home리다이렉트가 없음). 그래서 winpodx는 더 이상 "warm" 전달을 시도하지 않고 — 실행 중 앱에 파일 열기는 파일을 실은 새 RemoteApp 창으로 곧장 가며(자격증명 접속이 세션 잠금도 해제/재로그온). 공유$HOME/미디어 밖의 파일은 사라지지 않고 눈에 보이는 에러를 냅니다. 조용한-실패 표면도 강화:.desktop의Exec가%f,notify-send·file://URI를 절대경로로 해석해 stripped-PATH 실행에서도 살아남습니다. - 게스트 잠금화면 체크가 세션-스코프로 바뀌고, cold 실행이 느린 게스트를 더 오래 대기 (#680/#675, @mdshahalam3·@ajeshchrist 기여 감사). winpodx는 RemoteApp 창을 만들기 전 게스트 데스크톱이 interactive인지 기다리는데, 체크가 머신전역
Get-Process LogonUI라 다른 세션(콘솔이나 멈춘 disconnected RAIL 세션)의 잠금화면이 앱 세션을 잠긴 걸로 오탐해 full timeout까지 대기시켰습니다. 이제 LogonUI/explorer를 세션 ID로 매칭합니다. cold 실행도 느린 게스트의 자동로그온 완료를 위해 최대 45초(기존 20초) 대기해 앱 위에 stale 로그온 화면을 그리지 않습니다.
Contributors
이번 릴리스를 이끈 리포트와 기여에 감사드립니다 — @notnotno (#694, #691, #690), @twkirk161 (#697), @mdshahalam3 (#680), @ajeshchrist (#675), @numericOverflow (#705), @GameSoul7Eugene (#669, #684).
[0.8.0] - 2026-06-30
Added
- Windows 앱이 Linux 메뉴에서 시작 메뉴 폴더별로 그룹화됩니다 (#581, @Milliw 기여 감사). 각 앱이 속한 시작 메뉴 하위 폴더(예:
Microsoft Office\Tools)를 "winpodx" 메뉴 폴더 아래 중첩 하위 그룹으로 미러 — Windows에서 보이는 그대로. KDE Plasma·XFCE·Cinnamon·MATE·LXQt에서 렌더(freedesktop.menu메커니즘); GNOME은 앱이 보이긴 하나 그룹화 안 됨(오버뷰가 평면 그리드). 최상위 앱·폴더 없는 앱은 "winpodx" 바로 아래에 위치. [pod] keyboard설정이 이제 FreeRDP 세션 키보드 레이아웃에 반영됨 (#660, @lsvab 기여 감사). Windows 설치용으로 고른 로케일(예:keyboard = "hu-HU")이 대응하는 Windows 레이아웃으로 매핑되어 FreeRDP에/kbd:layout:0x…로 전달됩니다. 비-US 키보드가rdp.extra_flags를 손으로 안 써도 RemoteApp 창에서 동작합니다. 기본값en-US는 그대로 둬서(FreeRDP가 호스트 XKB 레이아웃 자동감지 유지 — 설정을 안 건드린 유저가 US로 강제되지 않음),rdp.extra_flags에 명시한/kbd가 항상 우선하고, 매핑에 없는 로케일은 자동감지로 폴백합니다. (rdp.extra_flags로/kbd를 직접 넘기는 건 0.7.4에서 이미 허용됨.)
Changed
- 앱 검출이 기본적으로 Windows 시작 메뉴에 실제로 뜨는 앱만 노출합니다 (#581, @Milliw 기여 감사). 기존에는 등록된 모든 실행 파일(레지스트리 App Paths, Chocolatey/Scoop shim, 모든 UWP 패키지)을 긁어 Linux 메뉴에 쏟아부어 언인스톨러·헬퍼·백그라운드 프로세스로 범람했습니다. 이제 기본값은 시작 메뉴 전용입니다: 시작 메뉴 바로가기 + 시작 메뉴에 보이는 UWP 앱(
Get-StartApps와 교차) + OS 필수앱(파일 탐색기/계산기/설정). 옛 동작은winpodx config set desktop.full_app_scan true또는 설정 → "설치된 모든 앱 검출(시작 메뉴 외 포함)" 체크박스로 되돌립니다 — 시작 메뉴 항목이 없는 포터블 앱에 유용. 다음winpodx app refresh부터 적용. - 번들 rdprrap 0.1.3 → 0.3.0. rdprrap(각 RemoteApp 창에 독립 세션을 주는 멀티세션 RDP wrapper)이
termsrv.dll패치 지점을 동적으로 도출합니다 — 하드코딩된 struct 오프셋/레지스터/바이트 템플릿 대신 런타임에 각 타겟 함수를 디스어셈블해 패치 바이트를 인코딩. Windows 빌드별termsrv.dll구조 변화에도 멀티세션이 유지됩니다. OEM 버전 27 → 28이라 기존 설치는 다음winpodx guest sync/apply-fixes때 반영.
Fixed
- 앱 이름이 길거나 CJK일 때, 또는 앱 목록이 비었을 때 GUI가 레이아웃 재귀로 죽던(SIGSEGV) 문제 수정 (#553, @hermitguo 기여 감사). 리사이즈되는 앱 목록 스크롤 영역 안의 word-wrap
QLabel(빈 상태 패널, 런처 타일)이 wrap 폭을 viewport에 맞춰 따라가서, 높이가 폭으로 되먹임돼 Qt 6.11에서QBoxLayout::setGeometry→heightForWidth를 무한 재진입했습니다 — 시작 시(빈 목록) 또는 CJK 이름 앱 추가 직후 하드 세그폴트. 해당 라벨들에 고정 wrap 폭을 줘서 되먹임 루프를 끊었습니다. - Debloat / 유지보수 작업 창이 작업 완료 시 스스로 닫힘 (#550, @ismikes 기여 감사). 진행
BusyDialog를 워커 내부에서QTimer.singleShot(0, dlg.finish)로 닫았는데, 맨threading.Thread엔 Qt 이벤트 루프가 없어 그 타이머가 영영 안 떠서 창이 손으로 닫을 때까지 떠 있었습니다(v0.7.2 변경은 워커 시작 시점만 고쳤고 닫기는 아님).BusyDialog.finish()가 이제 시그널 기반이라 스레드 간 닫기가accept()를 GUI 스레드에 큐잉해서 완료 시 창이 닫힙니다(Debloat, Grow Disk, Sync Guest, Apply Fixes). - GUI "Refresh Apps"가 완료 시점에 죽던(SIGSEGV) 문제 수정. 디스커버리 워커(parent 없는 파이썬 소유
QObject)에 삭제 경로가 2개였습니다 — 워커 스레드의deleteLater와 메인 스레드의 파이썬 ref-drop. Qt6는 워커의deleteLaterflush 전에QThread.finished를 emit하므로, cleanup 슬롯이 그 삭제가 진행 중인데 마지막 파이썬 ref를 떨궈 워커를 스레드 간 double-free(QObject::~QObject세그폴트)했습니다. 이제 삭제 경로가 정확히 하나(ref-drop,thread.wait()로 워커 스레드 종료 확인 후)이고, 시작 가드가 살아있는 스레드 ref에도 bail하며(빠른 재클릭이 끝나가는 워커를 떨구지 못하게), 윈도우 close 시 실행 중 워커 스레드를 join합니다. - 백그라운드 워커가 디스플레이 배율을 읽을 때 GUI가 죽던(SIGABRT) 문제 수정.
QGuiApplication.screens()는 GUI 메인 스레드 전용인데, 워커 스레드(Info 패널의gather_info등)에서 닿을 수 있어QObject::setParent: ... different thread경고를 쏟아내고 워커 스레드 종료 시__cxa_pure_virtual로 앱 전체를 abort시켰습니다 — refresh 시 GUI가 죽는 증상으로 나타남. Qt 배율 프로브가 이제 메인 스레드가 아니면None으로 단락되어 서브프로세스/환경변수 감지로 폴백합니다. winpodx app refresh가 이제 더 이상 검출 안 되는 앱을 추가만 하지 않고 제거도 함 (#581). 기존 refresh는 항상 추가만 해서, 사라진 검출 앱(Windows에서 언인스톨된 앱, 또는 새 시작메뉴-전용 기본값으로 빠진 모든 앱)이 수동 삭제 전까지 Linux 메뉴에 남았습니다. 이제 사라진 discovered 프로필(과 런처)을 prune해서 메뉴가 실제로 현재 집합으로 마이그레이션됩니다. 수동 추가 앱(~/.local/share/winpodx/apps/)은 절대 안 건드리고, 실패/빈 스캔은 메뉴를 비우지 않습니다.--win-iso가 이제 다운로드 대신 실제로 그 ISO에서 설치함 (#647, @ismikes 기여 감사). 로컬 ISO가winpodx setup이 이미compose up을 돌린 뒤에<storage>/custom.iso로 스테이징돼서, 컨테이너가 이미 부팅되고 dockur가 Microsoft 다운로드를 시작한 다음에야 파일이 생겼습니다(dockur는 부팅 순간custom.iso를 찾음). 이제 스테이징이winpodx setup내부에서 storage 경로 확정 후 컨테이너 (재)생성 전에 일어나, dockur가 ISO를 찾아 설치합니다.winpodx setup --win-iso <path>로도 노출.- reverse-open 리스너가 다음
pod start까지 죽어있지 않고 앱 실행 시 자가복구됨.winpodx pod stop/ 트레이 Quit이 리스너를 멈추는데(stop_listener()), pod는 계속 돌고 있으면 watcher를 다시 띄우는 게 없어 Windows의 "연결 프로그램 → Linux 앱"이 조용히 무반응이었습니다. 이제ensure_ready(모든winpodx app run/ GUI 실행)가reverse_open활성 시 리스너를 idempotent하게 보장합니다. v0.7.4 스모크 중 발견.
Contributors
이번 릴리즈에서 고친 이슈를 제보해 주신 분들께 감사드립니다: @Milliw (#581), @lsvab (#660), @ismikes (#647, #550), @hermitguo (#553), @liveifsh (#659).
[0.7.4] - 2026-06-23
Added
install.sh --storage-dir <path>(및winpodx setup --storage-path)로 Windows VM 위치 선택 (#646, @realahmed7777 기여 감사). VM 디스크 + ISO를~/.local/share/winpodx/storage대신 더 넉넉한 파티션에 둡니다 — 디렉터리는 기본값과 동일한 처리로 생성(btrfs면chattr +C로 raw 디스크 단편화 방지, 대상이 비회전 디스크면 SSD 에뮬레이션). 신규 설치 전용; 기존 설치 이전은winpodx setup --migrate-storage --migrate-storage-target유지.- 24GB+ 호스트의 신규 설치는 VM RAM 기본값이 6 대신 8GB (#630, @ismikes 기여 감사). Windows 11은 8GB에서 눈에 띄게 부드럽고, ≥24GB 호스트는 이를 감당할 수 있습니다 —
winpodx setup이pod.ram_gb를 미리 채우는 auto-tier가 해당 호스트에서 mid 티어를 6→8GB로 올립니다(CPU 사이징 불변; ≥32GB/≥12스레드는 여전히 12GB high 티어). 기존 설치는 설정값 유지 — Settings 또는winpodx config set pod.ram_gb 8로 언제든 변경. install.sh --win-iso <path>로 다운로드 대신 로컬 Windows ISO에서 설치 (#647, @ismikes 기여 감사). 이미 가진 Windows ISO 경로를 넘기면 storage 디렉터리에 dockur의custom.iso로 스테이징돼 ~5-8 GB Microsoft 다운로드를 건너뜁니다 — 반복 purge/reinstall 사이클에 유용. 파일시스템이 지원하면(btrfs/xfs) reflink 복사라 추가 디스크 비용 없음. (직접 storage에custom.iso를 둘 수도 있었지만, 이걸 플래그로 연결하고--help에 문서화함.)
Fixed
- Windows 비밀번호가 더 이상 로그에 평문으로 기록되지 않음. "Launching RDP" / "Relaunching RDP" 로그 줄이
/p:<password>를 포함한 전체xfreerdpargv를 출력했습니다. 이제 비밀번호 토큰(/p:,/gp:)을 로깅 전/p:***로 마스킹합니다. (로컬 로그 한정; 기존 문제 — 0.7.4 보안 검토 중 강화.) - Logs 탭의 Status / Pod logs / Inspect 버튼이 항상
podman을 실행하는 대신 선택된 컨테이너 백엔드를 사용함 (#658). Docker 백엔드 설치에서 Terminal 탭 진단 버튼이pod.backend와 무관하게podman ps/podman logs/podman inspect를 실행해, podman이 활성 백엔드가 아닐 때 실패하거나 엉뚱한 런타임을 조회했습니다. 이제cfg.pod.backend를 따릅니다 — Docker 선택 시docker(manual은podman으로 폴백하며, 거기선 컨테이너 명령이 무의미함). 화면의 pod 로그 tail$ …에코도 하드코딩된podman대신 실제 백엔드를 출력하도록 수정. - 이미 실행 중인 Windows 앱에서 두 번째 문서를 여는 게 이제 동작함(조용히 무시되지 않음) (#657). 앱이 이미 열려 있을 때(예: Word) 두 번째 파일을 그 앱으로 실행하면 — 두 번째
gio launch, 또는 우클릭 → 연결 프로그램 → 해당 앱 — 파일이 버려졌습니다: 라이브 세션은 재사용했지만 경로를 폐기했습니다. 이제 파일이 실행 중인 세션으로 전달됩니다 —\\tsclientUNC 경로로 매핑돼Start-Process로 열리며, 게스트 에이전트로 보내되 에이전트가 닿지 않으면 FreeRDP RemoteApp으로 폴백합니다. Best-effort: 실패는 로그만 남기고 실행을 막지 않습니다. - 우클릭 → 연결 프로그램으로 Windows 앱 실행이 PATH가 잘리는 데스크탑(예: Deepin)에서도 동작 (#657). 생성된
.desktop파일이 bareExec=winpodx …를 써서, 데스크탑 환경이 런처를~/.local/bin을 PATH에서 떨어뜨리는 systemd transient 유닛으로 실행하면(Deepin의dde-application-manager)exec: winpodx: not found로 실패했습니다. 터미널에서gio launch는 셸 PATH를 상속받아 동작했기에 버그가 가려졌습니다. 이제 엔트리가 설치 시점에shutil.which()로 해석한 winpodx 절대 경로를 박아 넣습니다(해석 실패 시 bare 이름으로 폴백). - RDP 비밀번호 누락 시 답할 수 없는 프롬프트 대신 명확한 에러로 실패 (#569, @biskasarchaniotakis 기여 감사).
rdp.password와rdp.askpass가 둘 다 미설정이면 xfreerdp가 GUI 실행에서 답할 수 없는 대화형 비밀번호 프롬프트로 빠져, 실행이 불투명한Inappropriate ioctl for device/ERRCONNECT_CONNECT_CANCELLED로만 실패했습니다. 이제build_rdp_command가 누락된 설정을 짚어 사전에 예외를 던집니다. - 비밀번호 로테이션이 거부된
net user를 성공으로 보고하지 않음 (#569). 게스트측 PowerShell이net user종료 코드와 무관하게password set를 출력해서, 거부된 변경이 저장된 비밀번호와 실제 Windows 비밀번호를 어긋나게 둔 채 rc=0으로 보고됐습니다 — 이는 이후 RDP 인증 실패 / 앱이 안 열림으로 드러납니다. 이제$LASTEXITCODE를 확인하고 실패 시 non-zero로 종료합니다. /kbd가 허용된 FreeRDP extra 플래그가 됨 (#657).rdp.extra_flags로 키보드 레이아웃 오버라이드(예:/kbd:layout:0x409)를 넘겨 일부 레이아웃의 FreeRDP keycode 스캐닝 경고를 우회할 수 있습니다.- Debian/Ubuntu
.deb가 이제podman-compose를 끌어오고, compose provider 누락 시 cryptic 대신 명확히 실패 (#644, @paolodongilli 기여 감사). Debian 13에서.deb가 winpodx + podman은 깔아도podman-compose는 안 깔아winpodx setup이 컨테이너를 못 만들고 나중에no such container "winpodx-windows"로 죽었습니다. 이제podman-compose가 패키지Recommends(apt 기본 설치)이고, setup 시점에 compose provider가 없으면 배포판별 설치 패키지명을 알려주는 실행가능 에러를 출력합니다(조용히 건너뛰지 않음). (curl … install.sh경로는 이미 설치했음 —.deb빈틈을 메움.) - 유지보수 작업 다이얼로그(Debloat, Grow Disk, Sync Guest 등)가 더 이상 비좁게 열리지 않음 (#550, @ismikes 기여 감사).
BusyDialog진행 창이 최소 너비 380px에 높이는 내용에 맞춰져 약 392×139로 떠 읽기에 너무 작았습니다(특히 Debloat Speed 실행). 480×168 하한 부여. (picker 창 크기와 빠른 작업 자동 닫힘은 0.7.2에서 이미 처리됨.)
Contributors
코드 기여 @cxgreat2014 (PR #657, #658), 그리고 이번 릴리스를 이끈 리포트·제안에 감사드립니다 — @ismikes (#630, #550), @realahmed7777 (#646), @paolodongilli (#644), @biskasarchaniotakis (#569).
[0.7.3] - 2026-06-20
Added
+multitouch가 허용된 FreeRDP 플래그가 됨 — 터치스크린 / 스타일러스 / 펜 패스스루 (#623, @Scratch2xs 기여 감사). extra FreeRDP 플래그(winpodx.toml의rdp.extra_flags, 또는 Settings의 extra-args 필드)에+multitouch를 추가하면 터치스크린·드로잉 태블릿·펜이 Windows 앱 안에서 동작합니다 — Linux 빌드가 없는 드로잉 소프트웨어에 유용. 입력 전용 토글이라 보안 트레이드오프 없이 FreeRDP 플래그 허용목록에 들어갑니다.- VM RAM 해제를 위한 idle 자동 정지 옵션 (#622, @hermitguo 기여 감사). Settings → Idle Action에서 Idle Timeout 경과 시 동작 선택: Pause(기본 — VM 동결, CPU 해제, RAM 유지, 즉시 재개) 또는 Stop(VM의 RAM 해제; 다음 실행은 풀 부팅). 기본 off — Idle Timeout 자체가 기본 off이고, Stop을 직접 켜지 않으면 Pause 유지. (인터페이스 다국어도 이 요청의 일부인데 이미 지원 — Settings → UI Language에서 中文/日本語/한국어 포함 8개 선택.)
- Reverse-open이 공유 Home뿐 아니라 Windows VM 자체의 파일도 엽니다 (#616, @notnotno 기여 감사). Windows 파일의 연결 프로그램 메뉴에서 Linux 앱을 고르는 기능이 기존엔 공유 Home(
\\tsclient\home) 아래 파일만 됐고, Windows 데스크탑이나C:어디든 있는 파일은 "파일 없음" 오류로 실패했습니다. 이제 게스트C:를 호스트에 SMB(루프백 전용)로 공유·온디맨드 마운트해서, 호스트 앱이 실제 게스트 파일을 열고 편집은 원본으로 바로 저장됩니다. 전부 런타임 전달(에이전트로 게스트 공유 + 호스트 마운트)이라install.bat은 안 건드립니다. 호스트에 kio-fuse(KDE) 필요 — 없으면winpodx doctor가 경고합니다. - Debloat가 더 많은 텔레메트리·광고 항목을 비활성화 (#590, @GameSoul7Eugene 기여 감사). Ads & suggestions 항목이 추가
ContentDeliveryManager추천 키와 회전 잠금화면 광고를 제거하고, 불필요한 예약 작업 항목이 순수 텔레메트리 작업(AitAgent,ProgramInventoryUpdater, CEIPBthSQM,Feedback\Siuf,WindowsAICopilot/Insights 데이터 수집, Office 텔레메트리 에이전트)을 추가로 비활성화합니다. 보안·시스템 핵심 작업(Windows Defender, 라이선스/활성화, 인증서 서비스, Windows Update 복구, 언어 팩, Windows Hello)은 활성화·업데이트·IME가 깨지지 않도록 의도적으로 건드리지 않습니다.
Fixed
- 대시보드 RAM + Disk C: 게이지가 "n/a"에 멈추지 않음 (#634, @ismikes 기여 감사). 둘 다 게스트에서 에이전트로 읽는데(CPU는 호스트측이라 멀쩡했음), 그 조용한 poll의 타임아웃이 빡센 4초여서 — 느리거나 방금 재실행된 게스트에선 그 안에 못 끝내고 둘 다 "n/a"로 비었습니다. 넉넉한 12초로 상향(poll은 GUI 스레드 밖 + 재진입 가드로 돌아서, 긴 budget이 대시보드를 멈추거나 중첩시키지 않습니다). #619와 같은 "타임아웃 너무 빡셈" 부류.
winpodx app refresh가 느린 게스트에서 30초 만에 타임아웃되지 않음 (#619, @KyleSanderson 기여 감사). 디스커버리는 게스트의 시작 메뉴 + AppX 패키지를 열거하는데, 콜드/저사양 게스트에선 1분을 넘는 게 정상입니다 — 그런데 CLI가 30초로 캡(/exec timed out after 29.0s)한 반면 새 설치는 라이브러리 자체 기본 180초로 잘 됐습니다. 이제 모든 경로(provision,app refresh, GUI Refresh)가 단일 넉넉한 기본값(DEFAULT_DISCOVERY_TIMEOUT= 300초)을 씁니다. 타임아웃은 정상 완료를 자르려는 게 아니라 진짜 멈춘(wedged) 게스트만 한정하기 위한 것이며, 극단적으로 느린 게스트는winpodx app refresh --timeout으로 올릴 수 있습니다.install.sh --ref <branch>재실행 시 이제 브랜치 최신 커밋을 설치 (#616). 원격을 fetch만 하고 stale한 로컬 브랜치 ref를 체크아웃했고(reset --hard는main만), 그래서 피처 브랜치로 업데이트를 재실행하면 처음 clone된 커밋이 다시 깔리는 조용한 no-op이었습니다. 이제origin/<ref>를 체크아웃합니다.- 업데이트가 compose 변경을 적용하고 멈춘 pod를 기동 (#616).
install.sh는 업그레이드마다compose.yaml을 재생성하지만 컨테이너 recreate는 pod가 떠 있을 때만 했고, 멈춰 있으면 Windows측 적용을 통째로 건너뛰어, 업데이트가 가져온 게스트측 변경이 다음 수동 실행 전까지 조용히 적용 안 됐습니다. 이제 compose가 바뀌면 멈춰 있어도 recreate하고, 멈춘 pod를 기동해 적용합니다(기존 설치 — ISO 재다운로드 없음). - 설치를 계속 불안정하게 만들던 USB 드라이브 문자 자동매핑 기능 제거 (#613, #638, @zephir2008·@ismikes 기여 감사).
media_monitor.ps1은 각 USB 볼륨을 드라이브 문자(E:, F:…)로 띄우려 했지만, RemoteApp(RAIL) 세션에서 안정적으로 띄우지 못했고, OEM 번들에 포함되자 간헐적 Windows Defender/rdprrap first-boot 설치 데드락을 재발시켰습니다(새 설치가 그 파일 복사 직후 멈춤). 제거했습니다. USB 미디어는 여전히 모든 세션에서\\tsclient\media리다이렉션과 바탕화면 USB 바로가기로 접근 가능하며, 진짜 드라이브 문자/raw 블록 장치가 필요하면 USB passthrough를 쓰면 됩니다. 기존 pod는 다음apply-fixes때 잔존WinpodxMedia자동시작 항목이 정리됩니다. usbredirect not found안내가 배포판별로 올바른 패키지를 안내 (#593, @techabsol 기여 감사). Debian/Ubuntu는usbredirect, Fedora는usbredir-tools(Atomic Fedora는rpm-ostree형태 추가), openSUSE는usbredir유지 — 기존 메시지는 바이너리가 들어있지 않은 패키지를 안내했습니다.install.bat가 에이전트 URL 예약 시 더 이상 구문 오류를 내지 않음 (#614, @zephir2008 기여 감사).netsh http add urlacl … sddl=D:(A;;GX;;;WD)줄에서 SDDL 값을 따옴표로 감싸지 않아cmd.exe가(,;,)를 메타문자로 해석해 예약이 실패할 수 있었습니다. 이제 SDDL 값을 따옴표로 감쌉니다(sddl="D:(A;;GX;;;WD)").- 게스트 에이전트가 드리프트된 bearer 토큰에서 복구됨 — 더 이상 영구 401에 머무르지 않음 (#615, @zephir2008 기여 감사). 에이전트는 부팅 시
C:\OEM\agent_token.txt의 bake본에서 토큰을 한 번만 읽는데, 이게 호스트 토큰과 어긋나면guest_exec/guest_summary(및 모든 authed/exec)가 HTTP 401을 반환하고 에이전트가 스스로 고칠 방법이 없습니다. 새winpodx guest resync-token이 현재 토큰을 FreeRDP 채널(Windows 암호로 인증하므로 에이전트가 401이어도 동작)로 다시 밀어넣고 에이전트를 재시작해 재독하게 합니다.winpodx doctor는guest_exec프로브가 401을 만나면 이를 자동 실행한 뒤 재확인합니다.
Contributors
이번 릴리스를 이끈 리포트와 기여에 감사드립니다 — @notnotno (#616), @hermitguo (#622), @Scratch2xs (#623), @KyleSanderson (#619), @ismikes (#634, #638), @GameSoul7Eugene (#590), @techabsol (#593), @zephir2008 (#613, #614, #615).
[0.7.2] - 2026-06-15
Fixed
- "Refresh Apps" 클릭 시 GUI 크래시 (KDE/Plasma Wayland) 수정 (#567). 앱 목록이
widgetResizable스크롤 영역이라 as-needed 수직 스크롤바가updateScrollBars→word-wrap 라벨heightForWidth를 무한 재귀시켜 스택 오버플로 SIGSEGV. 스크롤바를 고정해 뷰포트 폭이 진동하지 않게 함. - 트레이 "Terminate Session" · "USB Devices" 서브메뉴가 다시 열림 (KDE/Plasma) (#573). 메뉴 항목을 헬퍼 안에서 부모 없는
QAction으로 만들어 헬퍼 반환 후 PySide6 GC가 수거 → 서브메뉴가 children 0개로 export(화살표만 뜨고 안 열림). 각 액션에 서브메뉴를 부모로 지정해 생존시킴. - 중국어/일본어/한국어 앱 이름이 디스커버리에서 누락되지 않음 (#553). 순수 비ASCII 이름이 빈 슬러그가 되어 앱이 사라졌음; 이제 원본 문자를 표시 이름으로 유지하고 안정적 내부 id를 부여해 다른 앱처럼 표시됨.
winpodx pod stop이 컨테이너를 보존 (제거하지 않음).compose down(컨테이너 삭제)을 써서 정지 상태로 업데이트하면 매번 컨테이너를 재생성("Container 'winpodx-windows' is missing — creating it …")하고 불필요한 Windows 재부팅 발생. 이제compose stop사용 —start가 보존된 컨테이너를 재시작. 디스크는 영향 없음.- Windows 사용자명이 비면 정체불명의 FreeRDP
Inappropriate ioctl for device대신 명확한 에러로 빠르게 실패 (#569). 자격증명이 없으면 xfreerdp가 대화형 프롬프트로 빠지는데 GUI 실행에선 동작 불가 — 이제winpodx setup실행을 안내. - 추가 RDP 플래그가 현재 FreeRDP 3 철자를 수용 (#380): 캐시 토글은
/cache:bitmap:on|off등 — xfreerdp 3가 거부하는 구 FreeRDP-2+/-bitmap-cache형식을 allow-list에서 제거. -gfx로 GFX 파이프라인 비활성화 가능 (#393) — 일부 XWayland/Plasma에서 RAIL 렌더 깨짐(파란/뭉개진 창) 워크어라운드; bare+gfx/-gfx토글이 이전엔 allow-list에 막혀 있었음.- Debloat 피커가 적절한 크기로 열리고, "debloat 실행 중" 창이 작업 완료 시 자동으로 닫힘(빠른 프리셋에서 안 닫히던 문제) (#550).
Contributors
이번 릴리즈를 이끈 버그 제보를 해주신 @ismikes (#393, #550, #573), @notnotno (#380), @hermitguo (#553), @bangetto (#567), @biskasarchaniotakis (#569) 님께 감사드립니다.
[0.7.1] - 2026-06-13
Added
- 빠른 앱 런처 (
winpodx launch). Windows 앱을 위한 시작 메뉴 스타일 선택기 — 검색, 카테고리 칩, reveal-highlight 타일, compact 모드 — 메인 GUI 와 동일한 앱 레지스트리에서 가져오며winpodx app run으로 실행. 데스크톱 환경 커스텀 단축키(KDE/GNOME)에 바인딩하면 시스템 전역 핫키로 사용 가능; 추가 의존성 없음, Wayland 안전 (#561). - 자동 파일 연결 — 탐지된 Windows 앱이 파일 관리자의 "다른 프로그램으로 열기" 메뉴에 표시 (#545). 기본 활성화;
winpodx config set desktop.mime_associations false또는 GUI 설정 토글로 비활성화. 앱은 "열기" / 추천 목록에 추가 만 됩니다 — winpodx 는 Windows 앱을 특정 형식의 기본 핸들러로 설정하지 않습니다. 연결은 Windows 에서 실시간으로 읽으며 앱이 열 수 있다고 선언한 모든 위치를 합칩니다 (확장자별UserChoice, 앱별Capabilities\FileAssociations,Applications\<exe>\SupportedTypes, UWP AppxManifestwindows.fileTypeAssociation) — 예:.png는 Paint/Photos/Snipping Tool,.pdf는 Edge,.txt는 Notepad. 확장자→MIME 매핑은 시스템shared-mime-infoDB 사용 (하드코딩 테이블 없음), 다음install.sh업데이트 시 재탐지로 자동 적용 — 수동app refresh불필요. - GUI 앱 관리 — 재설정, 커스텀 아이콘, 다중 선택 (#530). 앱별 탐지값으로 재설정 이 자동 탐지된 이름/아이콘 복원; 커스텀 아이콘 선택기 로 아이콘 덮어쓰기; 다중 선택 으로 선택 숨기기 / 선택 삭제 일괄 처리; 삭제된 앱은 복원 목록 에 들어가 삭제 취소 가능 (전체 재탐지 버튼 포함). 체크박스는 이제 보이는 대비 색상 사용.
winpodx doctor가 RemoteApp 창이 깨지는 구버전 FreeRDP 를 경고 (#546). 네이티브xfreerdp3.x 가 3.6.0 미만 (예: Ubuntu/Budgie 24.04 apt 기본인 3.5.1) 이면 RAIL 창 매핑 버그로 RemoteApp 이 연결돼도 창이 안 뜸. doctor 가 이를 경고(실패 아님)로 표시하고 업그레이드 또는 winpodx AppImage 를 안내.- 체크섬 게이트 주기적 아이콘 갱신. 탐지 앱 아이콘은 소스 실행 파일의 체크섬이 바뀔 때만 게스트에서 재동기화 — 일상 갱신이 변경되지 않은 아이콘을 다시 쓰지 않음 (#539).
Fixed
winpodx gui가 더 이상 터미널을 막지 않음 (#549). 셸에서 실행하면 자체 세션으로 분리되어 트레이 + 대시보드를 띄우고 즉시 프롬프트를 반환..desktop자동시작과 빠른 런처는 이미 분리 실행 중이었음;winpodx gui --foreground로 디버깅 시 연결 유지 가능.install.sh --main(및--ref/--source/--image-tar) 이 Atomic Fedora 에서 존중됨 (#548). rpm-ostree 시스템 (Silverblue/Kinoite/Bazzite/...) 이 OBS RPM 으로 기본 동작하며 명시적 소스 오버라이드를 무시했음; RPM 은 태그 릴리스만 담아--main이 도달 불가였음. 이제 rpm-ostree 경로가 해당 플래그가 모두 비었을 때만 동작 — 커스텀 이미지 빌더가 소스에서 레이어링 가능. 기본 Atomic 설치는 불변.install.sh가/dev/kvm이 없지만 CPU 가 가상화를 지원할 때kvm모듈을 자동 로드 (#541).- 베어메탈 위장이 더 이상 컨테이너를 크래시 루프시키지 않음 (#246 후속). winpodx 가 합성 SMBIOS blob 을 재작성한 뒤 컨테이너가 다시 읽을 수 있도록 파일 레이블을 재지정 — 이전엔 오래된 레이블이 다음 부팅을 깨뜨림.
winpodx doctor가 의도적으로 숨긴 앱을 누락된 desktop entry 로 표시하지 않음 (#535).uninstall.sh가 winpodx 가 만든 것을 모두 제거 — 통합 "WinPodX" 메뉴 폴더 (.directory/.menu조각, 안 지우면 빈 서브메뉴가 남음), 오래된mimeapps.list핸들러 항목, 그리고 (--purge시)/etc/modules-load.d/winpodx-kvm.conf드롭인.- 탐지된 앱 이름이 더 이상
.desktop런처에 추가 키를 주입할 수 없음. 게스트가 준 이름의 제어 문자를Name=필드에서 제거 — 기존Comment=처리와 일치 (하드닝; 악의적 게스트가 있어야 발동).
Changed
winpodx gui가 인터랙티브 실행 시 기본 분리,--foreground로 opt-out (#549).
Contributors
앱 런처 (#561) 기여 및 #530 · #535 · #545 제보해주신 @MirzaAyBaig12, #541 · #546 제보해주신 @hermitguo, #548 제보해주신 @notnotno, #549 제보해주신 @ismikes 님께 감사드립니다.
[0.7.0] - 2026-06-11
Added
- 베어메탈 위장 — VM 탐지 소프트웨어에게 Windows 게스트를 물리 머신처럼 보이게 (#246). Opt-in, 기본 꺼짐. 하이퍼바이저 감지 시 실행을 거부하는 소프트웨어 — Nvidia GPU 패스스루 "code 43", launch-gate VM 체크, VM 비적합 설치 프로그램 — 가 QEMU/KVM 대신 실물 베어메탈 머신으로 인식. 베어메탈 레벨 로 동작 계층을 선택합니다 (
winpodx config set pod.disguise_level off | balanced | max, 또는 GUI Settings 셀렉터):- 재빌드 불필요, VM 별 적용 (
balanced+): CPUID 하이퍼바이저 present 비트와 KVM 시그니처 제거 (-cpu -hypervisor,kvm=off,-kvm-pv-*), 호스트의 실제 SMBIOS/DMI (시스템 / 보드 / BIOS 벤더 + 제품, CPU 벤더 + 모델) 를 게스트에 미러링, 합성 SMBIOS 센서/디스크립터 blob (전압 / 온도 프로브, 냉각 장치, 캐시, 메모리 어레이 + DIMM, 40+ 구조체) 을 주입하여Win32_*/CIM_*WMI 센서 클래스가 실제 하드웨어처럼 보고하도록. - 패치된 QEMU 이미지 (
max/ "Hardened"):winpodx disguise build-image가 QEMU 를 로컬에서 컴파일 (~20–40 분; 바이너리는 절대 shipped 안 됨) 하되, 커맨드 라인으로 건드릴 수 없는 VM 식별 문자열 을 호스트 실제 값으로 재작성 — ACPI OEM ID (BOCHS/BXPC→ 호스트), FADT 하이퍼바이저 벤더 + PM-profile 바이트, 디바이스_HID,WAET테이블 시그니처, 디스크 / 광학 모델 및 INQUIRY 벤더 — 에 더해 thermal-zone SSDT 와 WSMT 테이블 주입.max에서 winpodx 는 SATA 시스템 디스크, e1000 NIC, std VGA,nec-usb-xhciUSB-3 컨트롤러 (USB3 유지하면서 Red HatVEN_1B36특징 제거) 를 사용하고, virtio-rng 디바이스 (VEN_1AF4) 를 제거하며, 게스트에서 미사용 virtio 드라이버 서비스 키를 정리. GUI 에서 Hardened 선택 시 이미지 빌드 + 연결이 자동으로 이루어짐. - 개인정보: 호스트에서 파생된 문자열은 비식별 (벤더 / 모델 코드 등 디스크 모델) 이며 Docker build-arg 를 거쳐 로컬 이미지 레이어에만 — git 에 절대 커밋되지 않고 push 도 없음. 시리얼 / UUID / 자산 태그는 읽지 않습니다. 소스는 제네릭 폴백 (
ALASKA/Samsung SSD/ATA) 을 ship. - 실제 Windows 에서 al-khaser 0.82 검증 완료: 디스크, 센서, SMBIOS, ACPI, CPUID, virtio 서비스, 사용자명 탐지 계열 clean. 남은 특징들은 컨테이너-QEMU 구조적 바닥 (RDTSC 타이밍, 실제 하드웨어에서도 비어있는 레거시
Win32_MemoryDevice클래스, RDP 디스플레이 드라이버를 공유하는 Windows 내재 Hyper-V 통합 객체).
- 재빌드 불필요, VM 별 적용 (
- 기본 Windows 게스트 사용자명이 이제
WPX-User(기존User) — al-khaser 의 정확한 일치 샌드박스 사용자명 목록을 회피합니다. 첫 설치 전에winpodx config set rdp.user <name>으로 변경하세요.
Fixed
$HOME의 심볼릭 링크 서브디렉토리 하위 파일 열기 — 예:~/Documents가/mnt/store/Documents로 심링크된 경우 — 가 "Path is outside shared locations" 오류 없이 동작 (#547). 호스트→게스트 경로가 resolve 대신 lexical 정규화되어, 심링크 서브디렉토리를 폴더처럼 traverse (FreeRDP 가 따라가도록);..는 여전히 차단되며 Fedora Atomic/home → /var/home처리는 유지 (#418).- 새 dockur 이미지에서 USB 핫플러그 복구. dockur v5.16 이 QEMU 모니터를
telnet:7100에서 unix socket 으로 이전하면서 라이브 USB 연결 경로가 "QEMU monitor unreachable" 로 깨졌습니다; winpodx 가 이제 소켓과 통신하며 (구 dockur 는 telnet 폴백 유지) (#286). - GUI Devices 패널이 USB 디바이스가 탭 열린 상태에서 뽑힐 때 더 이상 깜빡이지 않음, 그리고 열거 도중 뽑힌 디바이스가 Qt 슬롯에서 예외를 올리지 않음.
Changed
- dockur/windows 핀을 v5.16 (x86_64) 과 dockur/windows-arm
:latest보안 재빌드 (aarch64) 로 업 (#554, #555).
[0.6.0] - 2026-06-05
Removed
- libvirt 백엔드 제거 (breaking).
backend = "libvirt"는 더 이상 유효하지 않습니다 — 기본 dockur 백엔드가 컨테이너 안 QEMU/KVM이고 이제 디바이스 패스스루까지 커버하므로(#286), 얇은 "bring-your-own libvirt 도메인" 래퍼는 존재 이유가 없어졌습니다.--backend는podman | docker | manual만 받고,backend = "libvirt"인 기존 config는 로드 시podman으로 fallback(경고와 함께).libvirtpip extra(libvirt-python) + install.sh/AUR/RPM/DEB 의 libvirt 참조 제거;winpodx[all]이 이제 libvirt-free(libvirt-dev빌드 의존성 없음). 자체 libvirt 도메인에 Windows 운영 중이라면 WinPodX ≤ 0.5.x 유지하거나manual백엔드를 그 RDP 엔드포인트에 연결하세요.
Added
- 재설계된 Start 메뉴 스타일 GUI — 리소스 Dashboard 홈 + 좌측 내비게이션 사이드바 (#460–#471). 창이 이제 Start 메뉴 셸입니다: 좌측 세로 내비게이션 사이드바(페이지당 한 줄, 활성 페이지 강조)와 새 Dashboard 홈 — 실시간 Pod / RAM / CPU 링 게이지 + 디스크 사용량, 자동 복구 상태 카드, 고정/최근 워크스페이스 타일, reverse-open 토글; 앱 런처는 "All apps" 페이지로 이동. 또한 통합 디자인 시스템, 자체 SVG 아이콘 세트(기존 유니코드 글리프 "아이콘" 대체), 좁은/분수 배율 창에서 reflow 하는(컬럼 쌓기, 그리드 타일 드롭) 반응형 레이아웃, fit-to-screen 창 크기, 그리고 명령 바를 겸하는 hero 검색을 도입합니다. 표시 이름이 UI / 사이트 / 문서 전반에서 이제 WinPodX 입니다(소문자
winpodx명령 / 패키지 식별자는 그대로). - 프로젝트 웹사이트 — winpodx.org (#436–#451). 랜딩 페이지 + Features / Get-started / FAQ 페이지, GitHub Pages 로 배포되며 앱과 동일한 언어로 현지화됨.
- Linux 메뉴에서 개별 앱 숨기기 / 표시 (#319, #415). GUI 앱 라이브러리(그리드 + 리스트)의 Hide 액션이 Windows 앱을 Linux 애플리케이션 메뉴에서 제거합니다; 숨긴 앱은 프로필에 영속되고 Hidden 토글로 다시 되돌립니다. CLI:
winpodx app hide <name>/winpodx app show <name>. winpodx pod recreate --keep-iso— ISO 재다운로드 없이 Windows 재설치 (#416). Windows 디스크 + 설치 마커를 지우되 캐시된 설치 ISO 는 유지하므로, dockur 가 Microsoft 에서 ~5–8 GB 를 다시 받지 않고 그 ISO 로 재빌드합니다(캐시된 ISO 가 없으면 일반 다운로드로 fallback).- 호스트 USB / PCI 디바이스를 Windows 게스트로 패스스루 — CLI + GUI (#286). 호스트 디바이스(USB 보안 동글, 캡처 카드, GPU 아닌 PCI 카드 등)를 Windows 게스트에 넘길 수 있습니다. 기본 백엔드가 dockur(컨테이너 안 QEMU/KVM)라 패스스루를 QEMU 레벨에 배선 — libvirt 불필요.
winpodx device list로 호스트 USB/PCI 디바이스 + 할당 여부 확인,device attach <id>/detach <id>로 할당/해제(cfg.pod.devices에 영속), GUI에는 Devices 탭(호스트↔게스트 2컬럼 mover). USB는 실행 중 게스트에 라이브 핫플러그 (cfg.pod.usb_live, 기본 ON):device attach <usb>가 재시작 없이 추가 — dockur 내장 QEMU-monitor를 (<backend> exec ... bash /dev/tcp로) 구동. 커스텀-qmp소켓도device_cgroup_rules도 안 씀 (둘 다 rootless Podman서 Windows 부팅 crash-loop 시켰음). usb_live는 호스트 USB 버스(/dev/bus/usb)만 컨테이너에 bind(rootless서 정상 부팅 검증됨); QEMU(dockur 통해 root)가 기존qemu-xhci컨트롤러에 디바이스 붙임.usb_live = false면 USB 버스 안 넣음. (USB 대용량 저장장치는\\tsclient\media공유로도 동작.) PCI는vfio-pci바인딩이라 컨테이너 QEMU에 핫플러그 불가 → 부팅 시 추가 + 게스트 재시작 필요 + 안전 검사 게이트 — 위험 디바이스(주 GPU, 부팅 디스크 컨트롤러, 활성 NIC)는 명시적--force(CLI) 또는 확인 다이얼로그(GUI) 필요하고, IOMMU 그룹은 통째로 움직이므로 함께 표시됨. 디바이스 id는 hex 검증. - 시스템 트레이 USB 스위처 (#300). 트레이 메뉴에 호스트 USB 디바이스마다 체크 항목이 있는 USB Devices 서브메뉴가 생겼습니다 — 체크하면 실행 중인 게스트로 디바이스 리다이렉트, 해제하면 호스트로 반환. CLI(
device attach/detach) + GUI Devices 탭과 나란한 빠른 접근 표면입니다: 토글은 영속 + 라이브 usbredir attach/detach 를 UI 스레드 밖에서 실행(트레이가 멈추지 않고pkexec프롬프트도 정상 노출)하며, 서브메뉴는 열 때마다 재구성되어 핫플러그된 디바이스 + 현재 할당을 반영합니다. 영속 절반은 이제 세 표면 뒤의 단일 공유 헬퍼(core.devices.assign_device/unassign_device)입니다. - 멀티모니터 RAIL 이 이제 기본으로 동작합니다 — RemoteApp 창을 두 번째 모니터로 끌어도 입력이 계속 먹힙니다 (
cfg.rdp.multimon, 기본값span). 이게 없으면 RAIL 앱 실행이 세션 데스크탑을 모니터 1개 크기로 잡아서, 두 번째 모니터로 끈 창이 그 데스크탑 밖의 호스트 가상스크린 좌표에 놓임 — 클릭이 빗나가다가 아예 안 먹힘. 이제 WinPodX 가 RAIL 앱 실행에/span을 추가해 세션 데스크탑을 전체 호스트 모니터의 bounding box(모니터별MonitorDefArray없는 하나의 넓은 직사각형)로 잡습니다./multimon을 먼저 시도했지만 전체 모니터 레이아웃을 전송해서 게스트의rdprrapRAIL 헬퍼가 처리 못 하고 입력을 완전히 죽임 — 그래서 기본값은multimon이 아니라span. 스팬된 bounding box 에 빈 공간이 생기는 비직사각형 배치에선cfg.rdp.multimon = "off"(또는winpodx setup --multimon off) 설정;multimon은 진단 전용 값으로 남겨둠. --extra-args/cfg.rdp.extra_flags가 이제 멀티모니터 + 리페인트 knob 을 허용합니다 (/multimon,/multimon:force,/span,/gdi:sw|hw,/smart-sizing[:WxH],/monitors:0,1), 각각 값 검증됨. 일부 멀티디스플레이 환경의 RAIL 창-이동 깨짐(해상도/DPI 다른 모니터 사이로 창을 끌면 뭉개지거나 깨짐) 의 수동 레버. 이제cfg.rdp.multimon이 기본span(위 항목)이라 입력 손실 케이스는 기본으로 처리됨; 이 플래그들은 리페인트 / 스케일링 knob 의 실행별 실험용으로 남음.winpodx doctor --fix— 흔한 진단 항목에 대한 멱등 자동 복구 (0.6.0 항목 K).winpodx doctor는 기본적으로 여전히 읽기 전용(권장 명령만 출력, 상태 변경 없음). 새--fix플래그는 알려진 복구기가 있는 각 진단 항목을 자동·멱등 복구로 전환합니다: doctor 가 진단을 수집하고, 모든 warn/fail 항목에 대해 등록된 복구기를 실행한 뒤, 해당 검사만 다시 수행해[fixed]/[still failing]을 보고합니다(복구기가 없는 항목은[skip] no auto-fix available출력). 모든 복구기는 이미 정상인 상태에서는 아무 동작도 안 하므로--fix는 반복 실행해도 안전합니다. 네 가지 복구 제공: (1) 죽은 에이전트 — 포드는 RUNNING 인데 게스트 내부 에이전트/health가 응답하지 않으면, 게스트 내부WinpodxAgentKeepAlive예약 작업을 즉시 실행(에이전트 전송 채널 경유; 에이전트 자체가 닿지 않으면 FreeRDP 로 폴백)하고 에이전트가 돌아올 때까지/health폴링; (2) 오래된 잠금 파일 —~/.local/share/winpodx/run/의.cproc마커 중 소유 PID 가 더 이상 살아있는 FreeRDP 프로세스가 아닌 것을 정리(실행 중인 세션은 안 건드림); (3) 누락된 데스크톱 항목 — 인덱스에는 있으나.desktop파일이 설치되지 않은 앱을 기존 데스크톱 항목 설치 경로(항목 + 아이콘 + MIME + 캐시 새로고침)로 재등록; (4) OEM 버전 드리프트 — 호스트oem_bundle스탬프가 게스트 기록 버전보다 새로우면guest_sync.maybe_autosync실행으로 갱신된 게스트 스크립트 푸시.--fix는 게스트를 건드리는 두 복구기가 동작하도록 느린 컨테이너 헬스/게스트 exec 프로브를 자동 포함합니다. 오래된 잠금·누락된 데스크톱 항목 복구기는 호스트 전용, 죽은 에이전트·OEM 드리프트 복구기는 Windows 게스트를 건드립니다.--fix없이는winpodx doctor동작 그대로.docs/design/ROADMAP-0.6.0.md항목 K 참조.- 깔끔한 인터랙티브 설치 진행 표시 (raw 전체는
install.sh --verbose). Windows 첫부팅 대기가 dockur/wget 원시 라인 수백 줄(…K …… 78% 4.55M 21m22s) + UEFIBdsDxe:부트로더 노이즈를 쏟아냈음. 이제 기본적으로pod wait-ready --logs가 ISO 다운로드를 제자리에서 갱신되며 지워지는 한 줄로 표시 —Downloading Windows ISO [#########-----] 78% 4.55 MB/s ETA 21m22s— 비-다운로드 단계엔Windows is booting…heartbeat, UEFI/mknod노이즈 숨김; 실제 dockur 마일스톤만 화면에 남김. 라이브 라인은/dev/tty에 직접 써서 터미널에서 애니메이션되면서도install.sh의tee캡처 로그를 안 더럽힘(비-TTY 는 가끔 퍼센트 라인 폴백).install.sh --verbose/-v(또는winpodx pod wait-ready --logs --verbose)로 전체 raw 스트리밍. 설치 끝에 요약 박스(버전/백엔드/GUI/venv) + 다음-단계 명령도 표시. 느린 다운로드 시 deadline 자동 연장은 그대로. install.shv2 — sudo 이전 시스템 점검, 설치 모드, 필수 private venv, 실패 시 롤백 (#271 해결).curl | bash설치가 이제sudo건드리기 전에 호스트를 스캔: distro + 버전, podman / docker / libvirt / freerdp / python3 의 존재/버전,/dev/kvm존재 여부, 그리고python3 -m venv가 실제로 동작하는지(Debian/Ubuntu 는python3-venv/ensurepip 를 분리해서 없을 수 있음)를 출력. podman major 4 미만은 too old 로 표시 — dockur/winpodx 는 rootlessgroup_add: keep-groups+ 모던 compose 가 필요한데 Ubuntu 22.04 는 podman 3.4 (#271) — podman 업그레이드(Kubic /devel:kubic:libcontainers) 또는 Docker 백엔드 사용을 안내. 이후 네 가지 모드 제공(--mode r|a|c|n/WINPODX_MODE로 piped/비대화형 시 사전 선택): [R]ecommended(기존 동작 — Podman 백엔드, 누락 deps 전부 설치), [A]utomatic(이미 설치된 것 재사용, 동작 중인 docker/podman/libvirt 백엔드 선택, 최소 sudo), [C]ustom(백엔드 + GUI 여부 선택), [N]o(변경 없이 깨끗하게 취소).winpodx setup으로 전달되는 신규 플래그 둘:--backend podman|docker|libvirt|manual(WINPODX_BACKEND) +--no-gui(WINPODX_NO_GUI=1, headless — PySide6 생략). 이제 Python 은 항상 private venv 에서 실행 —~/.local/bin/winpodx-app/.venv아래;winpodx-run런처가 시스템python3+PYTHONPATH대신 venv 인터프리터를 exec →--user/--break-system-packages시스템 파이썬 오염 없음, reverse-open 아이콘 deps(cairosvg+pyxdg)가 venv 에 깔끔히 설치(더 이상 distro 패키지 best-effort 아님). 그리고 fresh install 실패 시 롤백 — 그 실행에서 만든 winpodx 자체 아티팩트만 제거(venv,winpodx-run+winpodx런처/심링크, desktop 엔트리 + 아이콘, in-progress 마커), 시스템 패키지나 기존~/.config/winpodxconfig 는 절대 안 건드림; 업그레이드 실패 시 동작 중인 설치를 그대로 둠.--manual,--skip-deps,--source,--ref/--main,--win-version,--image-tar는 전부 기존대로 동작.winpodx-gitAUR 패키지 —main최신을 소스에서 설치 (#482, #483, #484). 안정판winpodxAUR 패키지(태그 릴리스 tarball)와 별개로, GitHubmain브랜치에서 빌드하는winpodx-gitVCS 패키지 추가:yay -S winpodx-git, 버전은 git 에서 도출되어yay -Syu --devel이main이 움직일 때마다 리빌드.winpodx를provides/conflicts하므로 둘 중 하나만 설치. 레시피 + 메인테이너 노트는packaging/aur-git/, 문서는docs/INSTALL.md.
Changed
- GUI UX 전면 정비 — 더 명확한 피드백, 더 안전한 동작, 더 적은 전문 용어. 데스크탑 앱 전반에 걸친 폭넓은 패스(새 텔레메트리 없음, 네트워크 없음, bloat 없음): 공유 토스트/알림 + "busy" 다이얼로그 + 인라인 경고 콜아웃 + 액션 가능 에러 레이어(
_widget_helpers)가 이제 어디서나 일관된 피드백을 받칩니다. 하이라이트: 앱 실행과 디바이스 attach/detach 가 조용한 RDP spawn 대신 비차단 토스트("Launching …" → 성공/실패) 표시; 앱 라이브러리가 상황별 빈 상태(Windows 미실행 / 검색 미일치 / 전부 숨김 / 발견 없음) + 통합 그리드-vs-리스트 카드 레이아웃(라벨 Launch, 일관된 Show/Hide, 확인되는 delete), 라벨된 검색 박스, 첫 8개 넘는 카테고리의 "+N more" 오버플로, 상태 바와 화해하는 "X of Y" 카운트 표시; refresh 실패는 액션 가능 다이얼로그(Start pod / Retry / View logs); 기동 다이얼로그가 중단 불가 단계 동안 Cancel 비활성(툴팁 포함) + 정직한 단계별 ETA 힌트 + 명확한 "✓ Ready" 마무리; 상태 배너가 "실행 중이나 transport 저하"(RDP/agent 도달 불가)와 "중지"를 구분하고, FreeRDP fallback 이 동작하는 down 된 agent 는 빨강이 아니라 amber; pod 상태가 더 이상 chip/배너/info-bar 에 삼중 표시 안 됨; Settings 가 어느 컨트롤이 즉시 적용되는지 표시하고, 파괴적 저장 전 wipe/recreate 경고 콜아웃 + 소요 시간 추정 노출, 상시 RAM-예산 줄, power-user knob 의 인라인 도움말 추가; 긴 유지보수 작업(grow-disk / sync-guest / debloat / apply-fixes)은 ETA 있는 busy 다이얼로그 표시; PCI 패스스루는 IOMMU 그룹이 내주는 호스트 디바이스를 평이한 말로 설명; Add-app 다이얼로그가 필드를 설명하고 Windows 실행파일 경로를 가볍게 검증; 키보드 단축키(Alt+N 페이지 전환, Ctrl+F 검색) 추가; License 페이지가 각 third-party 프로젝트를 링크. (Funding 은 repoFUNDING.yml/ README 에만 — 앱에는 절대 노출 안 됨.) - Windows 앱이 이제 네이티브 카테고리에 흩어지는 대신 단일 "winpodx" 메뉴 폴더 아래로 묶입니다. 이전엔 설치된 각 앱이 발견된 카테고리(Office, Graphics, …)를 상속해 네이티브 Linux 앱들 사이에 흩어졌고; 50개 앱 게스트에선 메뉴를 파묻었습니다. 이제 winpodx 가 전용 winpodx (Windows Apps) 서브메뉴를 만듭니다 — Wine 이 쓰는 것과 같은 메커니즘: freedesktop
.directory파일이 폴더 이름을 지정하고,applications-merged/winpodx.menu조각이 커스텀X-winpodx카테고리를 거기로 매핑하며, 생성된 모든.desktop이 그 한 카테고리를 답니다. 폴더는 첫 앱 설치 시 생성되고 마지막 Windows 앱이 제거되면 철거됩니다(둘 다 멱등 +winpodx app refresh에서 자가 치유). KDE Plasma, XFCE, Cinnamon, MATE, LXQt 가 존중; GNOME 오버뷰는 메뉴 폴더를 무시하는 평면 그리드라 거기선 앱이 나타나긴 하지만 묶이진 않습니다. 메뉴 검색은 여전히 이름으로 앱을 찾습니다(Keywords=windows;winpodx;<name>). 기존 설치 마이그레이션은winpodx app refresh재실행. install.sh가 시스템을 건드리기 전에 설치 플랜을 출력합니다. 모드(R/A/C/N) 와 모든 의존성 소스가 결정되면, 인스톨러가 주요 컴포넌트를 균등하게 나열한 짧은 플랜 —python3,venv프로브, 컨테이너 백엔드, FreeRDP,/dev/kvm, GUI — 을 각각 감지 상태 + 이번 실행의 동작(기존 사용 / 설치 / 호스트 요구사항)과 함께, 그리고 배포판 패키지 매니저로 설치할 정확한 패키지 목록 + Windows VM 프로비저닝 단계까지 — 어떤 패키지 설치/sudo전에 보여줍니다. 모드 프롬프트에서 바로 설치로 직행하지 않고 진행 내용이 투명합니다.- FreeRDP 클라이언트 소스를 선택 가능하게 했고, 런처는 Flatpak 클라이언트를 우선하며 네이티브는 fallback / opt-in 입니다 (#269, #366, #393). 기존엔
install.sh가 Flatpakcom.freerdp.FreeRDP이 이미 있어도 네이티브freerdp3패키지를 항상 설치했습니다(중복 — #269). 이제: (1) 런처가 Flatpakcom.freerdp.FreeRDP우선 (core/rdp.find_freerdpauto 순서가 flatpak-first) — 호스트 패키지 편차 없는 자체완결 FreeRDP 3+. 이전의 RAIL 멀티디스플레이 문제(RemoteApp 창을 두 번째 모니터로 끌면 입력 손실)는 이제cfg.rdp.multimon = "span"으로 처리되므로 Flatpak 이 우선 클라이언트로 viable; 네이티브xfreerdp는 Flatpak 이 없거나--freerdp-source native로 명시 고정했을 때 fallback. (2)install.sh가 중복 클라이언트를 설치하지 않음 — FreeRDP(네이티브든 Flatpak 이든) 가 하나라도 있으면 아무것도 안 깖; 둘 다 없으면auto는 네이티브 패키지 설치(경량, Flatpak 런타임 안 받음), 런처 auto 순서는 실제로 Flatpak 이 있을 때만 그걸 우선. (3) Custom 설치 모드에서 주요 의존성마다 소스를 선택 — 컨테이너 백엔드(podman/docker/libvirt), FreeRDP 클라이언트(auto / native / flatpak), GUI — 그리고winpodx setup --freerdp-source <auto|native|flatpak>가 선택을cfg.rdp.freerdp_source에 저장(Flatpak 샌드박스가 문제인 호스트에선native로 네이티브 고정). - Reverse-open refresh 출력이 방향을 명시하고, deprecation / 요약 plumbing 이 중복 제거됩니다.
winpodx host-open refresh가Reverse-open (host apps → Windows "Open with")헤더를 찍어provision중 바로 위에 도는 반대 방향 Windows-앱 discovery 의Discovered/staged/skipped카운트와 안 헷갈리게 하고, staged 셋은winpodx host-open list로 안내합니다. 별도로,winpodx provision요약이 rawapply_fixesdict repr 를 더 이상 안 찍고(N/N fixes OK/k: v한 줄), byte 단위로 동일하던 두pod-deprecation 헬퍼(_deprecate_pod/_emit_deprecation)를 하나로 합쳤습니다. install.sh --verbose가 이제 업그레이드 경로에도 전달됩니다.install.sh --verbose(또는WINPODX_VERBOSE=1) 가 신규 설치에선winpodx provision --verbose로 전달됐는데, 업그레이드 분기에선install.sh가winpodx migrate --non-interactive만 호출하고 verbose 플래그를 안 넘겨서 — 그리고migrate의_rich_wait내부에서verbose=False가 하드코딩돼 있어서 — 조용히 누락됐습니다. 이제winpodx migrate가--verbose/-v를 받고,install.sh는$WINPODX_VERBOSE를provision과migrate양쪽에 전달하며, migrate 의 wait-ready 단계가 플래그를 존중해서 업그레이드 실행도 신규 설치와 동일하게 raw 컨테이너 firehose 를 스트림합니다.--verboseoff 일 때 동작 변화는 없습니다(양쪽 경로 모두 기본은 깨끗한 self-erasing 라인).agent_keepalive가 컨테이너 재시작 직후 agent 깜빡임에 migrate apply 단계를 더 이상 실패시키지 않습니다. 업그레이드winpodx migrate중 컨테이너가 재시작하고(OEM reboot pass) 게스트 내부 agent 는 rdprrap (재)활성화가 트리거하는 TermService 사이클을 거칩니다. 그 구간의 단발/healthOK 는 agent 가 잠깐 떠 있는 것일 수 있고 — 직후 다시 죽어서 apply 단계가 닫힌 소켓을 침(0.6.0 업그레이드 스모크에서 본agent_keepalive: channel failure: /exec socket error: Remote end closed connection without response). 이제 두 겹 방어: (A)require_agentsettle 단계가 진행 전/healthOK 3회 연속(최대 ~30초)을 기다려, 첫 깜빡임이 아니라 agent 가 실제로 안정된 뒤에만 체인을 시작; (B) 모든_apply_*런타임 픽스(agent_keepalive,oem_runtime_fixes,multi_session,vbs_launchers,max_sessions,rdp_timeouts)가 공유하는 채널_apply_via_transport가TransportError(닫힌 소켓 //health타임아웃)를 5초 백오프로 2회 재시도하고, 매 시도마다 re-dispatch 해서 복구된 agent 를 다시 고르거나(여전히 죽었으면 FreeRDP 로 폴백). 실제로 실행된 payload 의 진짜rc != 0은 재시도 없이 첫 시도에 그대로 보고. healthy 한 첫 시도엔 동작 변화 없음.- AppImage 가 더 이상 호스트와 싸우는 번들 컨테이너 스택을 싣지 않습니다 — Thin AppImage 재설계 (#357, #363). 0.6.0 이전 팻 AppImage 는 전체 podman 스택(podman + podman-compose + conmon/crun/netavark/aardvark-dns/pasta/passt/slirp4netns + 그
.so의존성)을${APPDIR}/usr/bin와${APPDIR}/usr/lib에 번들했고, 엔트리포인트가 두 경로를 모두PATH와LD_LIBRARY_PATH앞에 prepend 했습니다. 이미 동작하는 호스트 podman 이 있는 모든 환경에서 이것이 깨졌습니다: #357, Ubuntu 26.04 —podman-compose가 번들 사본으로 해석되고, 그것이 번들podman을 탐색하는데(번들 podman 은 호스트의/etc/containers설정 · subuid/subgid · systemd 통합이 없어 단독 실행 불가)it seems that you do not have podman installed로 죽으며 호스트의 정상 podman 5.7 + podman-compose 1.5 를 가렸습니다; #363, Fedora Bluefin — podman 이 rootless aardvark-dns 용으로 호스트systemd-run을 실행했는데, 앞에 붙은${APPDIR}/usr/lib때문에 그 호스트 바이너리가 AppImage 번들libcrypto.so.3을 로드하게 되어OPENSSL_3.4.0 not found (required by host libsystemd-shared)가 발생, aardvark-dns 가 실패하고 컨테이너 시작이 죽었습니다. PR #365 의_hostenv헬퍼는 증상을 우회한 것이고, 0.6.0 항목 A 는 근본 원인을 제거합니다 — AppImage 에서 전체 컨테이너 스택을 드롭. AppImage 는 이제 번들이 안전한 것만 번들합니다(FreeRDP 3 · Python · Qt · winpodx — 호스트 헬퍼를 띄우지 않는 리프 바이너리들). 호스트 컨테이너 런타임(podman권장,docker/libvirt지원) 은 사용자가 배포판 패키지 매니저로 설치해야 합니다(install.sh와 동일 모델). 다만 컨테이너 스택 제거만으론 ~20 MB 만 감소했습니다(≈296 MB fat → ≈274 MB): 진짜 용량은 PySide6 가 Qt6 전체를 번들하는 것(QtWebEngine 만 ~195 MB)이고 winpodx 는 QtCore/QtGui/QtWidgets/QtSvg/QtDBus 만 링크하므로, 동반 패스(packaging/appimage/slim-pyside6.sh)가 안 쓰는 Qt6 모듈 + 그 플러그인/리소스 + 고아가 된 FFmpeg 라이브러리를 strip 해서 AppImage 를 ~110 MB 로 줄입니다._hostenv는 단일 책임으로 축소됩니다 — 모든 컨테이너 백엔드 서브프로세스에 대해LD_LIBRARY_PATH에서${APPDIR}제거. 호스트 런타임과 그것이 띄우는 호스트 헬퍼들이 번들 libcrypto / libssl 이 아니라 HOST libcrypto / libssl 을 로드하도록 보장 (번들 FreeRDP / Python / Qt 가 여전히 AppImage 자체LD_LIBRARY_PATH에 있는 번들 라이브러리들을 필요로 하기 때문에 #363 완화 조치는 유지).host_path()와resolve_backend_bin()은 제거됨 —${APPDIR}/usr/bin에서 호스트 컨테이너 런타임을 가리는 것이 더 이상 없으므로, 표준subprocessPATH 해석이 호스트podman/docker를 바로 찾습니다. 회귀 테스트(tests/test_appimage_recipe.py) 가 recipe 가podman/podman-compose/conmon/crun/netavark/aardvark-dns/passt/pasta/slirp4netns/fuse-overlayfs중 어느 하나라도 재도입하는 순간 CI 를 실패시킵니다. #357 과 #363 은 근본 원인이 수정되었지만, 원래 reporter 들이 다음 0.6.0 릴리스의 재빌드된 AppImage 를 스모크 테스트할 때까지 OPEN 상태로 둡니다. AppImage 가 아닌 환경에서는 엄격한 no-op(동일 바이너리, 환경 그대로 상속)이므로 ~99% 설치는 바이트 단위로 영향이 없습니다. 자세한 내용은docs/design/ROADMAP-0.6.0.md항목 A 참조. - 게스트 에이전트가 이제 세션 교체에도 죽지 않고 살아남습니다 (다음 재부팅까지 죽어 있던 문제 해결). 게스트 내부 HTTP 에이전트(
agent.ps1, 호스트가 창 없는/exec에 쓰는:8765리스너)의 자동 시작 경로는 단 하나, 대화형 로그온당 한 번만 실행되는HKCU\Run항목뿐이었고, 에이전트는 자동 로그온 세션의 자식 프로세스로 실행되었습니다. 그 세션이 무너지면 — rdprrap 멀티세션이 활성화되기 전에 FreeRDP 연결이 들어와 RDP 단일 세션 강제가 세션을 걷어차거나, rdprrap (재)활성화 중 TermService 사이클이 돌 때 — 에이전트가 세션과 함께 죽고HKCU\Run은 다시 실행되지 않아, 파드를 재부팅할 때까지 에이전트가 죽은 채로 남았습니다(파드가 몇 시간째 떠 있는데도/health타임아웃,pod restart로만 살아남). 이제WinpodxAgentKeepAlive예약 작업이HKCU\Run이 못 해 주던 지속 워치독 역할을 합니다: 멱등한agent-keepalive.ps1을 로그온 시 + 1분마다 실행하여,agent.ps1프로세스가 하나도 없을 때만 기존hidden-launcher.vbs래퍼(콘솔 깜빡임 없음)로 에이전트를 (재)기동합니다 — 정상 동작 중인 에이전트는 절대 죽이지 않습니다. 이 작업은 대화형 자동 로그온 사용자(SYSTEM/S4U 아님)로 실행되어, 앱 디스커버리와 사용자별 reverse-open 등록이 의존하는 사용자 HKCU + 시작 메뉴 컨텍스트를 에이전트/exec가 그대로 유지합니다. World-SID:8765urlacl 예약과 World-읽기 가능C:\OEM\agent_token.txt도 변경 없이 그대로 접근됩니다. OEM 시점(install.bat, OEM 번들 25 → 26)과 apply 체인(winpodx pod apply-fixes/ migrate / guest-sync) 양쪽에서 등록되므로, 이미 프로비저닝된 파드도 컨테이너 재생성 없이 받습니다. 크래시했지만 세션은 살아 있는 에이전트는 약 1분 내 복귀하며, 재로그온 없는 세션 강제 종료는 이미 멱등한 rdprrap 활성화로 애초에 발생을 막습니다(1분 반복은 TermService 사이클이 가라앉은 뒤의 백스톱이기도 합니다). - 커맨드 체계 재편 —
guest,install,doctor가 새 정식 위치 (0.6.0 항목 G).pod서랍에 3개 도메인의 서브커맨드 14개가 섞여 있었고, 진단 표면에는 부분적으로 겹치는 커맨드 3개(info,check,doctor)가 있었습니다. 이번 릴리스부터:winpodx pod는 라이프사이클 서브커맨드만 유지(start,stop,status,restart,recreate,wait-ready).winpodx guest는 게스트 측 작업의 새 정식 위치:apply-fixes,sync(sync-guest에서 이름 변경),sync-password,multi-session,recover-oem.winpodx install는 설치 진행/스토리지 작업의 새 정식 위치:status(pod install-status),resume(pod install-resume),grow-disk(pod grow-disk),disk-usage(pod disk-usage).winpodx doctor가 정식 진단 커맨드이며 새 플래그 두 개 추가:--json(Finding 목록을 JSON 배열로 출력) 및--quick(느린 컨테이너 헬스/게스트 exec 프로브 생략, 로컬 저비용 검사만 실행 < 1초).winpodx info와winpodx check는 기존 출력/동작을 완전히 유지하되, 이제 stderr 에 한 줄 deprecation 알림 출력:[deprecated] 'winpodx info' will be removed in 0.7.0; use 'winpodx doctor'(check 도 동일 패턴). 기존pod <x>서브커맨드는 0.6.x 동안 모두 등록 상태 유지 — 공유 핸들러에 위임하기 전 동일한 deprecation 줄 출력; 별칭은 0.7.0 에서 제거. 완전한 구 → 신 커맨드 매핑:pod apply-fixes→guest apply-fixes;pod sync-guest→guest sync;pod sync-password→guest sync-password;pod multi-session→guest multi-session;pod recover-oem→guest recover-oem;pod install-status→install status;pod install-resume→install resume;pod grow-disk→install grow-disk;pod disk-usage→install disk-usage;info→doctor;check→doctor. 0.7.0 에서 deprecated 별칭 제거.docs/design/ROADMAP-0.6.0.md항목 G 참조. - 백엔드 자동 선택이
backend/select.choose_backend로 일원화됨. 어느 컨테이너 백엔드 쓸지 결정하는 곳이 세 군데였고 Ubuntu 22.04 에서 서로 의견이 갈렸음 —install.shAutomatic-mode picker 는podman → docker → libvirt순회 + podman <4 unusable 게이트(#271), 그런데cli/setup_cmd.pynon-interactive 분기는 한 줄"podman" if which("podman") else "docker"로 버전 게이트 없음 → 설치 path 가 거부한 깨진 3.4 podman 을 setup wizard 가 그냥 선택.choose_backend(prefer, deps, podman_min_major=4)가 이제 단일 Python SoT: 명시적--backend우선, 그 외엔AUTO_PRIORITY = ("podman", "docker", "libvirt")순회 + 첫 usable 선택 (podman 은podman_major_version() >= 4게이트); 아무것도 없으면"podman"fallback (recommended install path 가 설치 가능).install.shbash picker = 하나의 의도된 셸 미러 (pre-venv deps 프로브와 동일 패턴 — Python 설치 전 실행) + Python SoT 가리키는 주석, 테스트가 두 사본의 priority 순서 + 최소 major 를 잠금 → drift 불가능. 0.6.0 정리 작업 일부 (docs/design/ROADMAP-0.6.0.md항목 E). - 호스트 의존성 감지가
utils/deps.py:check_all로 일원화됨. 0.6.0 이전엔 각 consumer (utils/deps.py·core/deps_quickcheck.py·cli/doctor.py·cli/setup_cmd.py) 가 자체shutil.which()리스트로 FreeRDP 바이너리 검사했고 drift 발생 —deps_quickcheck와doctor가sdl-freerdp3/ Flatpak fallback 누락 → 그 둘만 있는 호스트가 GUI Quick Start +winpodx doctor에서 MISSING 으로 잘못 보고됨.check_all()에/dev/kvm(check_kvm()) 도 추가 →setup_cmd의 인라인 KVM 프로브 제거,doctor의_check_kvm도 위임. 모든 Python consumer 가 이제check_all()/check_freerdp()/check_kvm()거침;install.sh셸 측 사전 venv 프로브만 단일 의도된 중복 (Python 설치 전 실행되어야 해서 셸 unique). regression test 가 잠금 —deps_quickcheck.py+doctor.py에 대한 AST 스캔으로 hardcodedshutil.which("xfreerdp...")가 돌아오면 CI fail. 0.6.0 정리 작업 일부 (docs/design/ROADMAP-0.6.0.md항목 D). - 버전 + Windows edition 리스트가 각각 단일 소스로 통합됨.
pyproject.toml이 이제 프로젝트 버전 선언의 유일한 곳;src/winpodx/__init__.py는importlib.metadata.version("winpodx")로__version__derive (비-설치 source checkout 용 fallback0.0.0+source명시) → 릴리즈 prep 때 한 쪽 bump 누락 불가능. 큐레이션된 Windows edition 리스트는core/config.py의WIN_VERSION_LABELSdict 로 이동; CLI 도움말 (winpodx setup --win-version) · 인터랙티브setup프롬프트 · GUI Settings 드롭다운 모두 여기서 derive → edition 추가 = 한 줄. 패키징 버전 CI guard (scripts/ci/verify_versions.py) 도 더 이상__init__.py리터럴 검사 안 함,packaging/rpm/winpodx.spec추가,importlib.metadataround-trip 검증 추가; spec 의 staleVersion: 0.1.5리터럴 = 현재 버전으로 bump + 새 guard 포인터 주석 추가. 0.6.0 정리 작업 일부 (docs/design/ROADMAP-0.6.0.md항목 F + N). winpodx.toml에schema_version마커 추가 (앞으로의 config 마이그레이션 기반). on-disk config 가 버전마다 모양이 바뀌어 왔는데 (키 rename, 섹션 이동) 지금까진_apply()의 "모르는 키 무시" 로 버텨왔음. 그건 추가 엔 OK 지만 rename / 이동 시 사용자 데이터 손실. 저장 파일 맨 위에schema_version = 1+core/config.py에_migrate_config(data, from_version)hook 추가 → 0.7.0+ 의 rename 이 사용자 설정 안 잃고 깨끗하게 업그레이드 가능. hook 자체는 오늘은 의도적 no-op (0.6.0 은 레이아웃 안 바꿈) — 마커만 먼저 박아둬서 기존 0.5.x 파일이 첫 load+save 때 태깅되고, 나중에 실제로 구조 바꾸는 릴리즈가 안전하게 변환 가능. 마커 없는 hand-edit 파일은 schema 0 (pre-0.6.0) 으로 취급. 0.6.0 정리 작업 일부 (docs/design/ROADMAP-0.6.0.md항목 J).- agent 포트 8765 가 이제 단일 Python 상수.
core/agent.AGENT_PORT= 호스트 측 단일 SoT — compose 템플릿 / guest 에 푸시하는 urlacl 문자열 /AgentClientURL 모두 여기서 derive. 게스트 측 (agent.ps1,install.bat,agent-keepalive.ps1,agent-respawn.ps1) = PowerShell 이 Python 상수 import 못해서 자체 리터럴 유지 — 짝 관계는 문서화 + 테스트로 잠금,install.sh의/healthcurl 도 상수 가리키는 주석 포함. 순수 내부 정리; 동작 변화 0. 0.6.0 정리 작업 일부 (docs/design/ROADMAP-0.6.0.md항목 C). - 레거시 FreeRDP host→guest fallback 이 이제 로그에 남음 (퇴역 준비용 계측). 게스트 agent 가 생기기 전엔 host→guest 명령을 FreeRDP RemoteApp 위 PowerShell 로 보냈음; 그 경로가 agent 도달 불가 시 조용한 fallback 으로 아직 남아있음.
debug레벨이라 실제로 얼마나 자주 발동하는지 알 수 없었음. 이제 매 fallback 이winpodx.log에FreeRDP-fallback태그WARNING으로 남음 — 이유(agent/healthdetail) + 어느 작업이 fallback 했는지 포함.grep -c FreeRDP-fallback ~/.local/state/winpodx/winpodx.log로 빈도 카운트. 동작 변화 0 — 무작정 빼서 깨뜨리지 않고, 실사용 데이터 보고 agent 복구 강화 + fallback 축소 수위를 결정하기 위한 측정. - 로깅 위생 정리 — 모듈 전반에서 로거 변수명 통일 (0.6.0 항목 L).
reverse_open/패키지와gui/reverse_open_panel.py는 모듈 로거를logger로 정의했고, 나머지 ~50개 모듈은log를 씀 (둘 다logging.getLogger(__name__)); 호출부를 모두log로 표준화. 로그 레벨 일괄 점검 결과 이미 일관됨 — 실패 / fallback / 거부는WARNING(콘솔 임계값), 정상 마일스톤은INFO, 진단은DEBUG— 라 레벨 변경은 불필요. 순수 내부 정리, 동작 변화 0: 로거 이름은 Python 변수와 무관하게 모듈 경로라 로그 출력은 byte 단위로 동일. 0.6.0 통합 작업의 일부 (docs/design/ROADMAP-0.6.0.md항목 L). - 첫 실행 discovery 재시도를 6→2 로 축소 (0.6.0 항목 M). 프로비저닝 체인(
winpodx provision— 신규 설치 시install.sh가, 그리고setup자동 프로비저닝 경로가 실행)이 게스트에서 설치된 앱을 스캔할 때, 게스트 agent 가 잠깐 준비 안 됐으면 지수 백오프로 discovery 를 재시도함.install.sh가 쓰던 고정 6× 루프는WinpodxAgentKeepAlive워치독(#359) 이전 시절 것 — 이제 keep-alive 가 agent 를 안정적으로 살려두므로 6회는 과하고 깨끗한 첫 실행만 느리게 함.winpodx provision --retries기본값과finish_provisioning(retries=...)기본값을 모두 2 로 낮춰, discovery 는 보고 전 최대 1회(2 s 백오프) 재시도.migrate/pending.resume업그레이드 경로는 기존retries=3유지(항목 B 에서 의도적으로 설정, 여기 범위 밖). 느린 게스트엔winpodx provision --retries N으로 재정의. 0.6.0 통합 작업의 일부 (docs/design/ROADMAP-0.6.0.md항목 M). - 0.6.0 용 문서 갱신 (0.6.0 항목 H).
README.md,docs/USAGE.md,docs/ARCHITECTURE.md,docs/FEATURES.md,docs/INSTALL.md와 각docs/*.ko.md미러가 0.6.0 형태 반영: 새winpodx guest/winpodx install/winpodx doctor명령 표면 (기존pod <x>는 deprecated alias 로 명시, 0.7.0 에서 제거), Thin AppImage 재설계 (FreeRDP + Python + Qt + winpodx 만; 호스트 컨테이너 런타임 필요; #357 / #363 근본 해소), 그리고winpodx provision을 pod 기동 후 단일 체인으로 명시.USAGE.mdcheat-sheet 의 기존 "Pod 관리" 블록은 세 섹션 (pod라이프사이클 /guest작업 /install작업) 으로 분리, System 섹션에 새doctor플래그(--json,--quick,--fix) +provision+migrate추가. 양쪽README.md의 버전 블러브를 0.6.0 으로 재작성.
Fixed
- UWP / 스토어 앱이 이제 Linux 작업 표시줄에 나타납니다 (#472). UWP 앱의 보이는 프레임은
ApplicationFrameHost소유이며 FreeRDP RAIL 로_NET_WM_STATE_SKIP_TASKBAR/SKIP_PAGER표시가 붙어 도착해서, 창 클래스가 이미 런처와 매칭됐는데도 패널에서 빠졌습니다. 이제 winpodx 가 실행 후wmctrl로 창을 다시 리스트합니다(best-effort, X11 / XWayland; 그 외엔 no-op). - 경로에 공백이 든 호스트 파일 열기가 이제 동작합니다 (#473). FreeRDP RemoteApp 명령에 넘기는 UNC 경로(
\\tsclient\home\…)를 이제 큰따옴표로 감싸므로, 게스트가 더 이상 공백에서 분리하지 않습니다 — 이전엔 앱이 첫 토큰만 받고 "path not found" 라고 보고했습니다. - 배율이 다른 듀얼 모니터 호스트에서 앱 실행 실패 / 창 멈춤 문제 (#474). KDE Plasma 6 Wayland(XWayland) + 두 모니터가 서로 다른 fractional 배율일 때, 기본
/span다중 모니터 데스크탑이 이중으로 깨졌습니다: 컴포지터의 소수점 반올림이 두 모니터의 논리 사각형을 타일링 불가로 만들어(1 px 틈, 높이 불일치,desktopScale: 0) FreeRDP 가/span을pre_connect에서 거부(ERRCONNECT_PRE_CONNECT_FAILED) → 아무것도 실행 안 됨; 그리고 세션이 떠도 RAIL 창을 다른 배율 모니터로 드래그하면 멈추고 입력을 안 받았습니다 — FreeRDP RAIL + XWayland 가 모니터별 fractional 배율 간 창 재매핑을 못 함(어떤 클라이언트 플래그로도 못 고치는 상류 한계). 이제 winpodx 가 모니터별 배율을 감지해서(KDEkscreen-doctor, wlrootsswaymsg/hyprctl) 다르면 RemoteApp 을 주 모니터에 고정 — 거기서 앱이 뜨고 정상 반응 — 하고, 두 모니터를 같이 쓰는 유일한 실제 해법을 로그로 안내합니다: 배율을 같게 (또는 KDE 디스플레이 → 레거시 애플리케이션 → 애플리케이션이 직접 배율, XWayland 가 xfreerdp 에 균일한 픽셀 그리드를 줌). 배율이 균일하면/span을 예전 그대로 사용(창이 모니터 간 자유 이동). 단일 모니터 / 감지 불가는 영향 없음;cfg.rdp.multimon = "off"로 무조건 단일 모니터 강제. 연결 시점 백스톱으로, 그래도 그 pre_connect 모니터 에러로 죽는 실행은 단일 모니터 데스크탑으로 재시도합니다. - 실행 중인 RDP 세션을 GUI 와 트레이에서 종료 (#450, #452, #453). GUI Dashboard 의 Running sessions strip 이 각 라이브 앱 세션을 나열하고 클릭하면 종료하며; 트레이에 Terminate Session 서브메뉴가 생겼습니다. 종료는 이제 리더만이 아니라 FreeRDP 프로세스 그룹 전체에 시그널을 보내고(#458) 멈춘 세션은 강제 종료할 수 있습니다(#459); 트레이는 또한 데스크탑의 StatusNotifier 호스트가 올라올 때까지 표시를 재시도하고 하트비트마다 다시 단언하므로 호스트 재시작을 견딥니다(#455, #465).
- Dashboard 리소스 센터가 이제 CPU / RAM / 디스크를 실제로 표시하고 빠르게 갱신됩니다. 실제 환경에서 세 문제가 겹쳤습니다: (1)
podman/docker stats를 설정된 컨테이너 이름으로 질의했지만 podman-compose 가 이름에 prefix 를 붙여(winpodx_winpodx-windows_1) 프로브가 "no such container" 에 걸려 모든 게이지가 비었음 — 이제<cli> ps --filter name=로 이름을 먼저 해석; (2) CPU/RAM 프로브와 게스트 디스크 프로브가 순차 실행돼 느린 디스크 읽기가 스냅샷 전체를 막았음 — 이제 하드 캡과 함께 동시 실행하고, 디스크는 느린 주기로 샘플링하며, 읽기 사이에 비우지 않고 마지막 정상값을 캐시; (3) 디스크 프로브가 매 폴링마다 게스트에 보이는 PowerShell 창을 띄우는 FreeRDP RemoteApp 으로 폴백했음 — 이제 agent 전용(창 없는/exec)이라 수동 대시보드가 콘솔을 띄우지 않음. 마지막으로 rootless podman 은stats에서 CPU%(때로는 MEM)를--로 보고하는 경우가 흔해 정상 pod 에서도 게이지가 비었는데, 이제 컨테이너의 cgroup v2 파일을 직접 읽어(memory.current로 RAM,cpu.stat의usage_usec델타로 CPU%) 채우므로stats가 비우는 rootless 환경에서도 CPU 와 RAM 이 표시됩니다. 많은 rootless 슬라이스는 사용자에게cpu/pids만 위임하고memory는 위임하지 않아cpu.stat은 읽히는데memory.current가 없는데 — 이때 RAM 은 컨테이너 cgroup 의 모든 프로세스VmRSS합산(커널 집계, 컨트롤러 무관; dockur 의 QEMU 프로세스가 대부분 차지)으로 폴백하므로 거기서도 RAM 게이지가 채워집니다. - Dashboard RAM 게이지가 이제 게스트 실제 사용량을 표시하고, 패널이 더 빨리 그려집니다. 호스트 측 메모리 읽기(cgroup
memory.current든 프로세스VmRSS합산이든)는 전부 VM 의 메모리를 보고했는데 — dockur(QEMU) 백엔드는 호스트가 게스트 RAM 거의 전부를 resident 로 봐서 게이지가 100% 근처에 박혀 있었습니다. 이제 RAM 은 Windows 내부에서 agent 를 통해(Win32_OperatingSystem의 total/free 물리 메모리) 가져옵니다 — 게스트가 실제로 쓰는 양을 반영하는 유일한 값. 디스크 프로브의 단일 agent 왕복(둘 다 한/exec)과 느린 주기를 공유하고, 마지막 값을 캐시해 폴링 사이에 비지 않습니다. CPU 는 호스트 측이지만 이제 cgroupcpu.stat델타(즉시 파일 읽기)를 먼저 읽고 수 초 걸리는podman stats샘플링은 폴백으로만 써서, 매 틱마다 멈칫하지 않고 패널이 채워집니다. - Dashboard 가 이제 창이 열리는 순간부터 자동 갱신됩니다. 라이브 갱신 타이머가 nav 의 페이지 전환 핸들러에서만 시작됐는데, Dashboard 는 시작 시 보이는 기본 페이지라 전환이 안 일어나서 — 패널이 한 번 그려진 뒤 다른 페이지로 갔다 와야 갱신됐습니다. 이제 Dashboard 빌드 시 타이머를 시작하므로, 실행 직후부터 리소스와 pod 상태가 스스로 갱신됩니다.
- 좁거나 fractional 배율 창에서 GUI 페이지가 잘리거나 가로 스크롤바가 생기지 않습니다. "All apps" 의 Pinned/Recent 선반이 wrap 안 되는 행이라, 선반이 길면 페이지 전체가 viewport 보다 넓어져서 앱 그리드 오른쪽이 잘렸습니다(hi-DPI fractional 배율서 가장 심함). 이제 모든 페이지의 스크롤 영역이 가로 스크롤바를 끄고, 타일 그리드는 타일당 폭을 조금 더 잡아 마지막 열이 넘치지 않게 하며, Dashboard/All-apps 선반은 실제로 들어가는 타일 수만큼만 표시(나머지는 아래 그리드에서 접근 가능)하고 리사이즈 시 재배치합니다.
- Settings 콤보박스가 지나가며 스크롤할 때 값이 바뀌지 않습니다. Settings 페이지를 스크롤하다 드롭다운 위를 지나가면 값(배율, 백엔드, 에디션 등)이 조용히 바뀌었습니다. 이제 콤보박스/스핀박스는 포커스되지 않으면 휠을 무시하고(값 조정하려면 먼저 클릭) 휠은 페이지 스크롤로 넘어갑니다.
- 업그레이드가 실행 중인 GUI / 트레이를 재시작해 새 버전이 즉시 적용됩니다 (#467). 인스톨러가 업그레이드 끝에 실행 중인
winpodx tray/ GUI 를 우아하게 재시작합니다(setsid경유라 인스톨러보다 오래 살아남음); pod 는 계속 실행 중. fresh 설치에선 no-op. - 파일 공유가 이제 Fedora Atomic / Silverblue / Kinoite 에서 동작합니다 (#418, #420). 거기선
/home이/var/home의 symlink 라서, UNC 경로 변환이 홈 폴더 파일을 "공유 위치 밖" 으로 보고했습니다. 이제 비교 전에 home 과 media 베이스 경로를 실제 대상으로 resolve 합니다. - Setup 이 컨테이너 백엔드 데몬이 실제로 도달 가능한지 검증합니다 — PATH 의 CLI 설치 여부만이 아니라 (#395, #419). 죽은 데몬(예: 오래된
DOCKER_HOST)을 뒤에 둔 podman / docker CLI 가PATH에 있으면 의존성 체크를 통과한 뒤 pod 시작에서 실패하곤 했습니다; 이제 체크가 도달성을 프로브하고 명확한 DAEMON DOWN 상태를 보고합니다. manual백엔드가 이제 loopback 대신 VM 주소에서 게스트 에이전트에 도달합니다 (#426). 에이전트 클라이언트가 Windows 머신이 다른 곳에 있어도 항상127.0.0.1:8765를 노렸습니다 — 그래서 VM 을 가리키는manual백엔드(예:LTSC11P.local의 VMware 게스트)에서 RDP 는 됐지만 모든 에이전트 기반 기능이 FreeRDP-only 로 떨어지고, 에이전트가 VM 주소에서 멀쩡히 응답하는데도winpodx check가agent_health도달 불가로 보고했습니다. 이제AgentClient가cfg.rdp.ip— RDP 도달성 체크가 쓰는 바로 그 주소 — 에서 호스트를 derive 하므로,manual에선 VM 을 따라가고 podman/docker 에선127.0.0.1로 남습니다(컨테이너가 8765 를 호스트 loopback 에 publish 하고cfg.rdp.ip가 이미 loopback). 호출부 변경 없음; 클라이언트 생성자 한 곳 수정.- Reverse-open 의 Windows shim 이 더 이상 Microsoft Defender 에 격리되지 않습니다 (#425).
winpodx-reverse-open-shim.exe(작고 stripped 된 서명 없는 Rust 바이너리)가 Defender 의 ML 휴리스틱에Trojan:Win32/Rafvartar!rfn— false positive — 로 걸립니다.install.bat은 이미C:\OEM과C:\winpodx를 제외했지만,register-apps.ps1은 shim 과 슬러그별winpodx-<slug>.exe사본을C:\Users\Public\winpodx아래 staging 했는데 거기는 제외 안 돼서 — Defender 가 격리하고 reverse-open 이 조용히 깨졌습니다. 첫 부팅 Defender-제외 단계가 이제C:\Users\Public\winpodx와winpodx-reverse-open-shim.exe프로세스도 커버합니다. shim 은 또한 임베디드 Windows VERSIONINFO 리소스(CompanyName / ProductName / FileDescription / 버전 —winresource빌드 스크립트 경유)와 함께 빌드됩니다 — 메타데이터 없는 stripped PE 가 AV 휴리스틱에서 더 나쁜 점수를 받으므로, 실제 publisher 정보를 박는 것이 슬러그별 사본도 상속하는 값싼 정당성 신호입니다. (장기적으론 shim 을 Authenticode 서명 / MS 에 false positive 제출해야 하지만, 제외가 winpodx 자체 게스트의 turnkey 픽스입니다.) - Setup 탭의 "Enable reverse-open" 체크박스가 이제 리스너를 라이브로 시작/중지합니다 (#425). 박스 체크는
cfg.reverse_open.enabled만 뒤집고 상태 라벨만 새로고침했음 — 데몬은 다음 pod 기동까지 떠 있지 않아 사용자가 "Start daemon" 도 눌러야 했습니다. 이제 체크박스가 활성화 시 즉시 리스너를 시작하고 비활성화 시 중지합니다(플래그 영속). best-effort + 조용함: 게스트가 아직 안 떠 있으면 플래그는 그대로 영속되고 리스너는 다음 pod 시작 때 올라오며, 그 예상된 경우엔 에러 모달이 없습니다. - Fresh 설치가 dockur 가 bridge-NAT 네트워킹을 고르는 호스트에서
wait-ready에 영원히 멈추지 않습니다 (#269, #387). 게스트 포트 8765 의 에이전트가 Windows 부팅 후 호스트에서 도달 불가였음 — noVNC 가 완료된 데스크탑을 보여주는데wait-ready가 무한 폴링. 근본 원인: compose 가USER_PORTS: "8765"를 설정했지만 dockur 의 네트워크 모드는 auto-select 로 뒀음. dockur 의 기본 bridge-NAT 경로가 성공하는 호스트(주로 rootful 백엔드)에선 그 경로가USER_PORTS를 조용히 무시 해서, QEMU 가 8765 를 게스트로 포워딩하지 않았습니다(Podman 호스트↔컨테이너 publish 는 정상; 컨테이너↔게스트 hop 이 갭). dockur 는 user-mode(passt) / slirp 경로에서만USER_PORTS를 참조 — 그래서 rootless 호스트는 영향 없었던 것(rootless 는 bridge 를 못 만들어 dockur 가 이미 passt 로 fallback 해 포트를 포워딩). compose 가 이제NETWORK: "user"를 핀해서 dockur 가 호스트 무관하게 항상 포트 포워딩 user-mode 경로를 씁니다. winpodx 는 게스트에 포워딩 포트(RDP 3389, 웹 뷰어 8006, 에이전트 8765)로만 도달하고 게스트를 호스트 LAN 에 둘 필요가 전혀 없으므로 user-mode 가 여기선 올바른 모드입니다; rootless 호스트엔 no-op(이미 passt 였음). install.sh가 Recommended 모드에서 너무 오래된 podman 으로 무작정 진행하지 않습니다 (#271). Automatic 모드는 이미podman < 4호스트(Ubuntu 22.04 = 3.4)를 docker / libvirt 로 넘겼지만, Recommended 모드와 명시적--backend podman은 그 폴백을 건너뛰어서 — 설치가 끝까지 진행된 뒤 패키지를 깐 다음에야 프로비저닝에서 실패했음. 이제 백엔드 확정 직후(어떤 패키지 설치 전에) 게이트가 동작: 선택된 백엔드가 podman 인데 너무 오래됐으면, 대화형 실행은 설치된 docker / libvirt 로 전환(또는 위험 감수 진행 / 중단)을 묻고, 비대화형 실행은 시스템을 변경하지 않고 깨끗이 종료하며--backend docker|libvirt, podman 업그레이드, 또는 강제용--allow-old-podman/WINPODX_ALLOW_OLD_PODMAN=1을 안내합니다. #271 의 "graceful exit" 부분을 완성합니다(distro/버전 체크 + 런타임 선택기는install.shv2 작업서 이미 제공됨).- 업그레이드 시 더 이상 설치 모드를 묻거나 옛 첫부팅 로그를 재생하지 않습니다. 업그레이드/재실행 UX 두 가지: (1)
install.sh가 winpodx config 가 이미 있어도[R]ecommended / [A]utomatic / [C]ustom / [N]o모드 메뉴를 띄웠음 — 업그레이드는 기존 backend/config 를 재사용하고migrate를 돌리므로 무의미. 이제 기존 설치를 감지해 프롬프트 없이 Automatic 으로 해결. (2)pod wait-ready --logs(업그레이드의 migrate 경로가 사용)가podman logs --tail 100으로 tail 했는데, 이미 떠있는 컨테이너에선 원래 첫부팅 ISO 다운로드 + 이미지 빌드 출력을 재생 —--verbose에선 매 업데이트마다 Windows 를 다시 받는 것처럼 보였음. 이제 pod 가 이미 떠있으면(RDP 도달 가능) 기록을 재생하지 않고(--tail 0), 진행 중 다운로드를 보여줄 가치가 있는 진짜 첫부팅에만--tail 100유지. - Flatpak FreeRDP 가 이제 풀데스크탑이 아니라 앱별 RemoteApp 을 띄웁니다.
flatpak run com.freerdp.FreeRDP는 앱의 기본 명령 — RAIL 없는 SDL 클라이언트(FreeRDP #9078) — 를 실행해서, Flatpak 으로 앱 하나를 띄우면 단일 앱 창 대신 Windows 전체 데스크탑/로그인 화면이 떴습니다. 이제 WinPodX 가 Flatpak 을flatpak run --command=xfreerdp … com.freerdp.FreeRDP로 호출해 RAIL 되는 유일한 클라이언트인 X11xfreerdp바이너리를 강제하고, WinPodX 의 모든 RDP 플래그가 필요로 하는 샌드박스 권한을 부여해서 Flatpak 이 네이티브 클라이언트처럼 동작합니다:--share=network(로컬 RDP),--socket=x11/--socket=wayland(RAIL + 클립보드),--socket=pulseaudio(사운드),--socket=cups(프린터),--device=dri(디스플레이),--filesystem=home+ 이동식 미디어 마운트 루트(\\tsclient\home+\\tsclient\media드라이브 리다이렉션). 0.6.0 이 Flatpak 우선 사용을 시작한 뒤 드러났습니다. - 설치 모드 메뉴(R/A/C/N)가 이제
curl … | bash에서도 동작합니다. 대화형 모드 프롬프트(+ Custom 모드의 백엔드/GUI 서브프롬프트, 의존성 설치 확인)가[ -t 0 ]으로 게이트되고 stdin 에서 읽어서, 표준curl … | bash설치(stdin 이 스크립트 파이프지 터미널이 아님)에선 조용히 스킵되고 항상 Recommended 로 기본 처리됐습니다. 이제 install.sh 가 제어 터미널/dev/tty도 감지하고 모든 프롬프트를 거기서 읽으므로, 터미널에서 실행한curl … | bash는 모드 메뉴를 띄우고 선택할 수 있습니다(라이브 진행 라인이 출력에 이미 쓰는/dev/tty트릭과 동일). 완전 비대화형 실행(CI / cron / stdin 과/dev/tty둘 다 없음)은 비대화형으로 남아 Recommended 기본,--mode/WINPODX_MODE는 여전히 프롬프트 없이 미리 선택합니다. - Fresh 설치 프로비저닝이 flaky 게스트 agent 를 타이머로 포기하지 않고, 실제로 회복될 때까지 기다립니다. agent-first settle 게이트와 discovery 단계가 고정 윈도(settle ~30초 상한; discovery 2회 재시도, 수 초)로 포기해서, 첫 부팅 reboot 직후 게스트 agent 가 잠깐 죽으면 — keepalive 워치독 사이클(~60초)만큼 늦을 수 있음 — 프로비저닝이 deferred 되고 설치는 나중에
winpodx app refresh로만 끝났습니다. 이제 두 단계 모두 시간 캡 없이 pod liveness 신호 기반으로 대기합니다: pod 가RUNNING인 한WinpodxAgentKeepAlive워치독이 agent 를 되살리므로,/health를 폴링하며 안정적으로 올라오면(settle 은 3회 연속 OK) 진행 — agent 가 얼마가 걸리든 discovery 를 inline 으로 완료합니다. 대기는 pod 자체가 멈출 때만 끝납니다(워치독 없음 → 진짜 회복 불가).pod wait-ready의 wget-ETA 동적 deadline(#126) 과 같은 "실제 신호로 bound, 임의 시간 아님" 철학. bounded 재시도 budget(--retries, 0.6.0 항목 M)은 이제 비-agent 일시적 discovery 에러에만 적용됩니다.winpodx migrate(업그레이드 경로)도 동일한 인내 회복을 받습니다. - deferred 된 fresh 설치가 더 이상 스스로 롤백되지 않습니다. Windows 다운로드+부팅은 잘 됐는데 agent-first discovery 가 deferred 된 fresh
install.sh실행에서(winpodx provisionexit 5 — 예: 첫 부팅 reboot 후 게스트 agent 가 1분 늦게 올라옴), install.sh 가 설치 전체를 롤백해서 — 스스로 끝날 상태인데 ~15분짜리 ISO 다운로드+부팅을 날렸습니다. 근본 원인: bash 는set +e중에도 실패한 파이프라인 에ERRtrap 을 발동시켜서, rc 처리(이미 non-zero provision 을 "pending 기록, 설치 유지" 로 다룸)가 돌기 전에 롤백 trap 이 provision 파이프에서 발동했습니다. 이제 provision 호출이set +e에 의존하지 않고ERRtrap 을 명시적으로 비활성(trap - ERR/ 이후 재무장)하며, exit 4(wait-ready 가 오래 걸림) / exit 5(discovery deferred)를 명시적 deferred-실패아님 케이스로 처리합니다: 남은 단계를 pending 으로 기록하고(다음winpodx실행 또는winpodx app refresh시 자동 재개) 설치를 유지합니다. 진짜 초기 실패(deps / venv / 컨테이너 생성)는 이전처럼 trap 으로 롤백됩니다. - 설치 / provision 이 이제 등록한 앱을 카운트만이 아니라 목록으로 보여줍니다. discovery 단계가 공유하는
_register_desktop_entries는Registered N app(s) in your desktop menu.카운트만 찍어서 —winpodx provision/install.sh/winpodx migrate중 사용자가 어떤 앱이 들어왔는지--verbose로도 못 봤습니다. 이제 등록된 앱 이름을 하나씩 나열하고(winpodx app refresh와 동일), discovery 카운트와 등록 카운트가 다르면(번들 프로필이 없는 discovered 앱은 등록 불가)Note: N discovered app(s) had no bundled profile and were not added to the menu: …한 줄로 그 차이를 설명합니다 — 더 이상 조용히 사라지지 않습니다. migrate --verbose가 두 apply 경로 중 하나에만 전달됐습니다 (방금 친 verbose 픽스의 회귀).run_migrate의 cross-version 업그레이드 분기가_apply_runtime_fixes_to_existing_guest(non_interactive)를verbose=verbose없이 호출해서 —install.sh가migrate --non-interactive --verbose로 모는 바로 그 경로에서 — 실제 버전 교차 업그레이드 시 raw 컨테이너 firehose 가 여전히 누락됐습니다(그 분기에서--verbose로컬이 dead). 두 호출부 모두 이제verbose=verbose를 전달합니다.- Windows-exec 래퍼 스크립트가 더 이상 world-readable 로 쓰이지 않습니다.
run_in_windows()가~/.local/share/winpodx/windows-exec/<desc>.ps1(와 그 디렉터리)를 프로세스 umask(보통0644/0755)로 썼습니다. FreeRDP-폴백 비밀번호 회전 경로에선 그 스크립트가 갓 회전된 평문 Windows 비밀번호를 담으므로, 다른 로컬 사용자가 잠깐 읽을 수 있었습니다. 이제 디렉터리는0700, 스크립트는0600— agent 토큰 /compose.yaml/ config / 회전 마커가 이미 쓰는 방식과 동일. - 세션 lock
.cproc가 launch 중 잠깐 빈 파일로 truncate 되지 않습니다.launch_app이 FreeRDP 자식 PID 를 알기 전에 PID lock 파일을"w"모드(0바이트로 truncate)로 열어서, 동시 reader(list_active_sessions/ idle 모니터)가 빈 파일을 보고 살아있는 세션 lock 을 corrupt 로 판단해 unlink 할 수 있는 창이 있었습니다. 이제 lock fd 를O_TRUNC없이 열고, 자식이 생긴 뒤flock하에ftruncate+write+fsync한 번으로 PID 를 씁니다 — reader 는 이전 PID 또는 새 PID 만 보고 빈 파일은 절대 못 봅니다. - 경로에 콤마가 든 discovered Windows 실행파일이 더 이상 FreeRDP
/app:서브키를 주입할 수 없습니다. FreeRDP-3/app:program:<exe>,name:<name>인자가app_executable을 raw 로 보간했는데, 인접한default_args와 UWP AUMID 는 바로 이것 때문에 이미 sanitize/검증 중이었습니다. 이제app_executable도 결합 인자 생성 전 동일한 콤마→스페이스 처리를 받습니다. - pod resume 실패가 이제 긴 불투명 RDP 타임아웃 대신 명확한 메시지로 즉시 실패합니다.
ensure_pod_awake가resume_pod결과를 무시해서, 실제로 unpause 안 된 pod 가 그대로launch_app에 넘어갔습니다. 이제 resume 시도 후is_pod_paused재확인하고, 여전히 paused 면ProvisionError("failed to resume paused pod …")를 raise 합니다. - 첫 부팅 discovery 가 총 앱 수가 그대로여도 UWP 갭을 메운 retry 를 유지합니다.
discover_apps의 retry-on-empty 가 총 개수가 엄격히 더 많을 때만 retry 결과를 유지해서, 누락된 UWP(스토어) 앱을 회복했지만 총 개수가 같은 retry — retry 를 촉발한 바로 그 신호 — 가 버려졌습니다. 이제 UWP 항목이 더 많은 retry 도 유지합니다. - 프로비저닝 통합(0.6.0 항목 B)의 첫 컷이 획일화하며 날린 4개 동작 복구. 4개 post-create 경로를
finish_provisioning으로 합치면서 조용한 wait + 단일 agent 게이트로 획일화 → 각 경로가 이슈별로 쌓아둔 동작을 잃었음: (1) 동적 대기 — 자기지움 라이브 진행줄 + 느린 링크용 wget-ETA deadline 자동확장(#126) — 이제 fresh 설치가 한 줄 뒤 침묵하는 멈춤처럼 안 보임; wait-ready 단계를 richpod wait-ready로 라우팅하는 주입식wait_fn으로 복구. (2) agent-first 설치 보호(#271):require_agent=True가 이제 apply+discovery 단계 내내WINPODX_REQUIRE_AGENT=1을 export (one-shot settle 재프로브뿐 아니라) → discovery/apply 가 first-boot 중 install.bat autologon 세션을 kick 할 수 있는 FreeRDP fallback 대신 defer; discovery 의 지속적 agent-unavailable 은 generic 실패 기록 말고 pending(exit 5)으로 깨끗이 defer. (3) 업그레이드 →winpodx migrate:install.sh가 이제 fresh/upgrade 분기 — fresh 는provision --require-agent, upgrade 는migrate(기존 guest 에 갱신된 스크립트/agent.ps1동기화 + 이미지 핀 + 동일 apply→discovery→reverse-open 체인); 첫 컷은 둘 다provision돌려 업그레이드 guest 가 stale 스크립트로 남았음. (4)pending.resume"migrate" step 도 이제guest_sync실행 → 나중에 resume 된 deferred 업그레이드도 guest 스크립트 갱신.migrate는 reverse-open 단계도 획득(이제 업그레이드 경로의 유일 driver).docs/design/PROVISION_UNIFY_FIDELITY_AUDIT.md참조. install.sh가 이제 reverse-open 아이콘 의존성을 설치 → placeholder 아이콘 대폭 감소.curl | bash설치는 winpodx 를 venv 없이 시스템python3로 돌려서, winpodx 의 정식 아이콘 의존성 —cairosvg(SVG→PNG) +pyxdg(전체 freedesktop 아이콘 테마 해석) — 이 없었음. 그래서 아이콘이 SVG 이거나 비-Hicolor 테마(Papirus, breeze, …)에 있는 Linux 앱은 Windows "연결 프로그램" 메뉴에서 일반 placeholder 로 떨어짐 (한 smoke 의 placeholder 18개 중 13개가 단지cairosvg누락). 이제 설치 시 없으면python3-cairosvg+python3-pyxdg를 distro 패키지 매니저로 설치 — best-effort + 비차단(앱은 정상 실행; 패키지나 sudo 가 없으면 placeholder 로 graceful degrade). AppImage 는 이미 둘 다 번들이라 영향 없었음.- Guest sync 가 더 이상 콘솔 창을 깜빡이거나 업그레이드 후속 체인을 망치지 않음. guest sync 가 실제로 처음 발화할 때(예: 0.5.8 → 0.5.9 업그레이드 — 스탬프가 호스트보다 오래됨), OEM-pull / urlacl / agent-재시작 단계가 FreeRDP RemoteApp(
run_in_windows)으로 갔는데 — 호출마다 보이는 PowerShell/콘솔 창이 뜸(콘솔 깜빡임 회귀). 이제 guest sync 의 나머지처럼 windowless agent/exec채널(run_via_transport)로 감 (세 단계 모두 agent 가 살아있을 때 실행 — 재시작의/exec는 scheduled task 가 agent 를 죽이기 전에 리턴). 별도로 agent 재시작이 fire-and-forget 이라 설치 후속 작업(migrate apply 체인, 앱 discovery, reverse-open)이 재기동을 레이스해서 agent 도달 불가 → pending-resume 로 떨어짐. 이제sync_guest가 재시작 후 agent/health가 다시 응답할 때까지(여유롭게, bounded) 기다린 뒤 리턴 → 후속 단계가 살아있는 agent 를 봄. 재기동이 비정상적으로 느리면 pending-resume 가 backstop. - 다색 XPM 으로만 배포되는 reverse-open 아이콘(예: veracrypt)이 더 이상 빈 placeholder 로 떨어지지 않음. Pillow 내장 XPM 디코더는 픽셀당 1글자(≤256색)만 처리 — veracrypt 아이콘(유일한 아이콘이
/usr/share/pixmaps/veracrypt.xpm, 1770색/2글자per픽셀)은KeyError나서 Windows "연결 프로그램" 메뉴에 일반 placeholder. 이제 winpodx 가 그런 XPM 을 작은 순수 파이썬 리더로 디코드(컬러테이블+픽셀행 파싱 → RGBA 이미지 직접 생성) → 실제 아이콘 표시. 신규 의존성 0, 외부 도구 0 — AppImage 포함 모든 설치 방식에서 동일하게 동작. 남는 placeholder 는 진짜 불가피한 경우뿐(앱이 아이콘을 아예 안 배포).
기여자
이번 릴리즈에서 다룬 이슈를 제보해 주신 분들께 감사드립니다: @ismikes (#269, #357, #387, #393, #450), @vlombardino (#271), @vkkindia (#286), @vw72 (#319), @jmayniac (#363), @MirzaAyBaig12 (#366), @urbantigerau (#395), @notnotno (#418, #425), @sundaysfantasy (#426), @mhmdzaien (#473), @nemonein (#474) — 그리고 코드 기여 @ismikes (#423).
[0.5.9] - 2026-05-27
Highlights
다국어 UI, 스스로 커지는 디스크, 재설치 없이 갱신되는 게스트.
- 트레이 / GUI / CLI 전체가 번역 가능해졌고 7개 언어로 완전 번역 (en/ko/zh/ja/de/fr/it) — 시스템 로케일에서 자동 선택,
winpodx language또는 GUI 드롭다운으로 변경 (한국어 잔존 텍스트 #335 수정). - Windows C: 드라이브가 가득 차면 스스로 커지고 —
winpodx pod grow-disk로 수동 확장도 가능 — dockur 가 남기는 WinRE 복구 파티션 너머까지 확장 (#318). - 게스트 동기화: 호스트에서 winpodx 업그레이드 시 새 agent / fixes / 바이너리를 실행 중인 게스트에 재설치 없이 적용 (
winpodx pod sync-guest, pod 시작 시 자동). - 로그인 시 pod 자동시작 이 실제 opt-in 기능으로 (
winpodx autostart on). - 수정: 미디어 미장착 시 USB 바로가기 오류, RemoteApp 이 로그인 화면을 렌더 (#332), atomic distro 에서 AppImage
podman-compose ModuleNotFoundError(#322), 너무 빡빡해 일찍 포기하던 설치 agent-wait.
Added
- 선택 가능한 완전 번역 UI — 7개 언어 (en/ko/zh/ja/de/fr/it). WinPodX 자체 트레이 / GUI / CLI 텍스트가 번역 가능해졌고, ~814개 사용자 가시 문자열 전부 래핑 + 6개 비영어 언어로 완전 번역 (placeholder, 백틱 명령 스니펫, 제품명은 verbatim 보존).
[ui] language(기본auto) 가 호스트 로케일($LANG)에서 언어를 고르고 영어로 폴백; CLI(winpodx language ko, 인자 없으면 현재 표시) 또는 GUI 설정 → winpodx UI 언어 드롭다운(다음 실행 시 적용)으로 변경. 영어가 소스/기준 — 항상 100% — 미번역 문자열은 영어로 폴백(공백 없음). 카탈로그는winpodx/locale/<lang>.json의 평면{english: translation}맵 — 언어 추가는 코드 변경 없이 파일 드롭. 한국어 잔존 보고(#335) 대응. - 로그인 시 pod 자동시작 (opt-in).
cfg.pod.auto_start는 저장만 되고 안 쓰이던 플래그였는데 이제 동작 — 단 기본 off, 명시적 opt-in (로그인마다 Windows 부팅은 무거움).winpodx autostart on(또는 GUI 설정 체크박스)으로 켬: 트레이 자동시작 항목 설치 +auto_start설정 → 로그인 시 뜨는 트레이가 pod 시작(중단 상태면 재개).winpodx autostart off/status로 관리. 백그라운드 + best-effort (실패해도 pod 정지로 두고 로그만, 트레이 안 죽음); 이미 실행 중이면 no-op. - 게스트 동기화 — 재설치 없이 호스트 업데이트를 실행 중 게스트에 적용. 호스트에서 winpodx 업그레이드해도 게스트의
agent.ps1, urlacl 예약(#269), rdprrap/shim 바이너리, 레지스트리 fixes 가 wipe-reinstall 전까지 stale 했음./oem은 호스트config/oem의 live bind mount 라 호스트 업그레이드 후 컨테이너엔 이미 새 파일이 있음; WinPodX 가 이를 실행 중 게스트에 전달(pod recover-oem와 같은 채널이지만 agent/exec로 자동화)하고 idempotent fixes 재적용, agent 재시작(서비스 중인/exec를 안 죽이게 일회성 scheduled task 로), 게스트 버전 스탬프(C:\winpodx\install-state\guest_version.json). 게스트 스탬프가 호스트보다 오래되면 pod 시작당 1회 자동 실행(guest_autosync, 기본 on);winpodx migrate도 트리거(패키지 / AppImage / flatpak 업그레이드는install.sh를 안 거치므로 한 커맨드로 전체 게스트 갱신), 수동은winpodx pod sync-guest [--force], GUI 도구 → 게스트 동기화. agent 변경 없음.docs/design/GUEST_SYNC_DESIGN.md참고. - Windows 디스크 자동/수동 확장 (#318). Windows C: 드라이브가 스스로 커짐: 시스템 볼륨이
disk_autogrow_threshold_pct(기본 80%) 넘게 차고 pod 가 idle 이면 WinPodX 가 가상 디스크를disk_autogrow_target_free_pct여유 공간 복원에 필요한 만큼(기본 30%,disk_autogrow_increment단위 — flat bump 아님) 키우고, 컨테이너 재생성으로 dockur 가 이미지 확장,Resize-Partition으로 C: 확장 — 수동 디스크 관리 불필요. 확장은 호스트 여유 공간(안전 reserve 제외)으로 제한;disk_max_size는 이제 고정 cap 이 아닌 선택적 명시 상한(기본 빈값). 동일 작업 수동 노출:winpodx pod grow-disk(증분 1단계),winpodx pod grow-disk 128G(절대 목표),winpodx pod grow-disk --extend-only(기존 미할당 공간으로 C: 확장만),winpodx pod disk-usage(크기/여유/사용%). GUI 도구 페이지에 디스크 확장 액션. 자동 확장은 idle 일 때만 실행해 live RemoteApp 세션을 안 끊고, 게스트측 작업은 agent/exec재사용(agent 변경 없음). 기본 on;disk_autogrow = false로 수동 관리. 주의: dockur 는 온라인 디스크 resize 가 없어 매 확장마다 컨테이너 재생성(짧은 게스트 재부팅) — WinPodX 가 이래서 idle 에 자동 확장 스케줄. dockur Windows 레이아웃은 C: 바로 뒤에 WinRE 복구 파티션을 둬서 커진 공간이 그 뒤로 가 평범한Resize-Partition으로 못 닿음; extend 단계가 이를 감지해 WinRE 분리, 막는 복구 파티션 제거, C: 확장, WinRE 재활성화(전용 파티션 없으면C:\Windows내). @drjwhitty(Linux Mint 22.2) 보고.
Fixed
- 업그레이드 재설치가
[4/4]OEM reboot pass 대기에서 멈춤. 이미 프로비저닝된 게스트에install.sh재실행 시pod wait-ready --logs가 phase 4 에서 멈춤. 원인 둘: (1)--logs --tail 100이 최초 첫부팅 다운로드 로그를 재생 → 스테일 wget ETA 라인이 대기 deadline 을 수십 분으로 부풀림; (2) phase 4(_wait_for_oem_reboot) 가 agent 전환 중일 때 그 부풀린 시간 내내 블록 — "마커 안 뜸 → 업그레이드" 탈출이 clean probe 에서만 발화하고 agent 가 연결 거부 중엔 안 됐음. 수정: 컨테이너 RUNNING 후엔 deadline 연장 안 함(이후 ETA 라인은 재생된 과거); phase 4 를 180s 로 하드 캡(다운로드 아니라 마커 폴링); 업그레이드 탈출이 probe 결과 무관하게 appear-grace 시계로 발화. - 미디어 미장착 시 USB 바로가기 오류. install.bat 은 항상
\\tsclient\media를 가리키는USB바로가기를 만드는데, WinPodX 는 removable-media base(/run/media/$USER등)가 있을 때만 그 드라이브를 redirect 해서 — USB 안 꽂은 채 클릭하면 "\\tsclient\media에 접근할 수 없습니다 … 잘못된 주소 접근" 오류. 이제 WinPodX 가 항상media드라이브를 redirect: 장착 시 실제 base, 아니면 빈 placeholder 디렉터리 — 바로가기가 오류 대신 빈 폴더를 엶. - UI 잔존 한국어 텍스트 (#335). 트레이 "종료" 확인 대화상자와 CPU/RAM 티어 프리셋 라벨(
Low/Mid/High)이 한국어로 하드코딩돼 영어 로케일 사용자가 종료 시 한국어 프롬프트를 받음. 둘 다 영어로 (이번 릴리스의 완전 i18n 레이어가 별도 처리). @camegone(CachyOS) 보고. - 설치: agent-readiness 대기가 이제 동적 + 여유롭게, 오해 소지 오류 없음. 느린 첫 부팅(cold cache / 느린 디스크 / 긴 ISO 다운로드로 설치가 늦어짐)에서 post-RDP agent
/health대기가 하드 180s 로 잘려 일찍 포기 — 무서운agent didn't answer within 180sWARN 누출 후 agent 가 몇 분 내 뜰 것임에도 apply-fixes / discovery 스킵. 이제 caller 의 전체(동적, ISO-ETA 연장) deadline 을 따름 —pod wait-readyphase 3 가 자동 연장 deadline 을 넘김. 별도로 게스트 버전 스탬프 read/write 가 이제 FreeRDP 대신 windowless agent/exec채널(run_via_transport)로 감 (#346) — 신규 첫 부팅에서 전환 중인 agent 면 clean 하게 실패하고 다음 pod 시작에 스탬프 재시도, 설치 중간에 무서운FreeRDP timed out after 30s/ERRCONNECT_ACTIVATION_TIMEOUT경고 누출 안 함. 스탬프는 best-effort 라 info 레벨로 표시("deferred; will retry next start"), 경고 아님. 설치 후 앱 discovery 재시도 예산도 3회 → 6회(~50s)로 늘려 apply 체인의 TermService/rdprrap 사이클을 버티게 함 — 다음 실행의 pending-resume 로 안 떨어지게. - 첫 실행 setup 프롬프트가 매번 뜨던 문제 (#341). "winpodx has not been set up yet" 는 config 파일 존재가 아니라 내부
initialized플래그로 게이트됨. non-interactive 기존-config 경로가 그 플래그 플립 전에 "skipping setup" 출력 후 return → 플래그가false인 config(install.sh 자체 skip 경로로 생성됐거나 플래그 도입 전 작성)는 프롬프트가 영원히 반복 — "Auto" 눌러도 setup 이 short-circuit, 다음에 또 뜸. 이제 skip 경로도 initialized 표시. @ntruhan(Fedora 44) 보고. - RemoteApp 이 로그인/잠금 화면으로 실행 → 깨진 렌더 (#332). 앱이 가끔 자기 대신 Windows 로그인 배경을 띄움 (
xf_Pointer: Invalid appWindow스팸) — 앱은 실행됐지만 게스트 세션이 로그인/잠금 화면을 거치는 도중(dockur autologon 세션이 잠깐 재생성) FreeRDP RAIL 창이 생성돼 stale 로그인 framebuffer 를 그리고 앱을 다시 안 그림. 버전 무관(현재 FreeRDP 에서도 재현). 이제 winpodx 가 RemoteApp 연결 전 게스트 콘솔이 interactive(explorer.exe떴고LogonUI.exe없음)할 때까지 대기 — best-effort, agent-only 라 FreeRDP probe 창이 안 깜빡임; agent 도달 불가/타임아웃이면 기존대로 진행. @tolistim(Mint 22.3) 보고. - AppImage:
podman-composeModuleNotFoundError(#322). fat AppImage 가 Fedora 의/usr/bin/podman-compose런처 스크립트는 번들했지만 그게 import 하는podman_composePython 모듈은 안 함(순수 Python 이라ldd가 못 끌어옴), 스크립트 shebang 이 호스트python3로 떨어짐 — atomic distro 엔 모듈 없음. Pod 생성이ModuleNotFoundError: No module named 'podman_compose'로 실패. 이제 빌드가podman-compose를 AppImage 번들 인터프리터에 pip 설치하고python3 -m podman_compose로 실행하는usr/bin/podman-composewrapper 를 함께 제공(pip console script 는 shebang 이 죽은 빌드타임 경로라 직접 못 씀 — AppRun 이python3 -m winpodx쓰는 이유와 동일). @jmayniac(Bluefin / Fedora atomic) 보고.
[0.5.8] - 2026-05-24
신뢰성 + 도달성 release: 사용자가 보고한 두 fresh-install 실패 (#269 agent 8765 bind 불가, #287 install.bat 미실행) 수정 + distro 무관 fat AppImage + standalone winpodx setup 의 end-to-end provision 완성.
Highlights
Fresh-install 신뢰성 + self-contained AppImage. fresh-install 막힘 2개 수정, 느린 연결/호스트도 timeout 안 남, immutable distro 용 단일 파일 AppImage 에 FreeRDP + Podman 번들.
- #269 수정 — agent 의
http://+:8765/urlacl 예약을 World SID 로 생성 → non-admin guest User 가 bind 가능. agent.ps1 가 retry + 실패시 urlacl 상태 로깅 (@ismikes 협업). - Fat AppImage (#227) —
winpodx-fat-x86_64.AppImage가 Python + Qt + FreeRDP + Podman + podman-compose 번들;winpodx setup-host가 호스트 측 kvm-group / subuid 를 pkexec 한 번으로 처리 (@leandromqrs 요청). - Dynamic + generous timeout (#126) —
pod wait-ready가 dockur 다운로드 ETA 로 자동 연장, compose-up activity-based, 모든 fixed timeout 확대 (@xiyeming 보고). winpodx pod recover-oem(#287) — dockur 첫 부팅 OEM 복사 실패 시 C:\OEM 재stage + install.bat 실행 (driver @ankranidiotis).- Setup wizard 완성 (#255) —
--customize가 edition / language / region / keyboard / tuning 까지 prompt, standalonewinpodx setup이 full provision (wait-ready + discovery + reverse-open) 실행. podman-compose강제 (#288) —podman compose가 docker-compose 로 delegate 해서 keep-groups silently fail 하던 문제 해소 (@magicdiablo 보고).
Added
winpodx pod recover-oem— dockur 첫 부팅 OEM 복사 실패 수동 복구 (#287). dockur 의$OEM$ → C:\OEM복사가 silently fail 하면 (install.bat 미실행 → agent 미설치 → 8765 RST) 컨테이너서/oemtar → 컨테이너 8766 포트 임시 HTTP server → noVNC PowerShell 로 download + extract + install.bat 실행 안내. podman/docker 만. Driver: @ankranidiotis (Linux Mint 22, Podman 4.9.3).pod wait-readydynamic deadline +winpodx setup-host(#126, #227).pod wait-ready가 dockur wget ETA 파싱해서 deadline 연장 (상한 없음 — stall 시 ETA 안 나와서 자연 만료, +60min slack).winpodx setup-host(=python -m winpodx.setup_wizard) 가 AppImage 가 user space 에서 못 하는 호스트 작업 (kvm 그룹,/etc/subuid+/etc/subgid, kvm 모듈 persistent) pkexec 로 처리. @xiyeming 보고.- Fat AppImage (FreeRDP + Podman 번들 + pkexec 위저드) (#227). #302 의 lean AppImage (Python + winpodx + Qt 만) 를 fat 번들로 확장:
packaging/appimage/레시피가 python-build-standalone 의 portable Python 3.11 다운로드 → winpodx wheel +gui+reverse-openextras 설치 → Fedora 41 binary overlay (xfreerdp / wlfreerdp / sdl-freerdp, podman, podman-compose, conmon, crun, netavark, slirp4netns, passt, pasta) + host-critical 제외 transitive.so의존성 모두 번들 (glibc / libX11 / libGL / libwayland / libxkbcommon 은 호스트 사용 — 번들하면 desktop integration 깨지거나 glibc mismatch crash). 단일winpodx-fat-x86_64.AppImage(~290 MB squashfs / ~920 MB AppDir) user space 가능한 모든 self-contained. 새winpodx setup-hostsubcommand (+python -m winpodx.setup_wizard) 가 AppImage 가 user space 에서 못 하는 호스트 측 작업 —kvm그룹 멤버십, rootless podman 용/etc/subuid+/etc/subgid, kvm 모듈 persistent load — 단 한 번의pkexecpolkit 프롬프트로 처리. 타겟 사용자: immutable distro (Fedora Silverblue / Kinoite / Aeon, Steam Deck),curl install.sh | bash불가 환경. CI 가 python-appimage 우회 (bundled appimagetool 가 PATH wrapper 무시 + FUSE 없는 GitHub runner 에서 silently fail) → 새 pipeline 이 AppDir 처음부터 빌드 + Fedora 41 docker 컨테이너로 binary overlay + 미리 설치된 extract-and-wrapappimagetool로 pack. 번들 third-party license 텍스트 (FreeRDP / Podman / podman-compose 등) 는packaging/appimage/licenses/에 vendor 해서 AppImage 안에 동행 (CI 의dnfnodocs 정책 무관, required dir 없으면 build fail-closed). README + INSTALL.md (en + ko) 업데이트. @leandromqrs 제안. - Setup wizard 완성 —
--customizeprompt + standalone full provision (#255). wizard 가 edition, language, region, keyboard, tuning profile prompt (이전엔 CLI skip, GUI 전용). standalonewinpodx setup이 install.sh 처럼 post-create flow (wait-ready, apply-fixes, discovery, reverse-open) 실행.--create-onlyflag 로 install.sh 경로 유지. - CLI + GUI first-run setup prompt (#255 PR 1).
cfg.pod.initialized가 False (또는 config 없음) 일때 첫winpodx <cmd>호출시 3지선다 prompt:[Y]es(auto — 호스트 감지 default, no prompts),[C]ustom(wizard — 모든 knob 선택),[n]o(skip). Skip-list 가 introspection / config / uninstall / gui / tray 명령 + non-TTY stdin bypass. GUI 도 첫 launch 시 동일 모달. setup 성공 후initializedTrue 로 flip 되어 prompt 재발 안함. winpodx setup --customize플래그 (#255 PR 1). Wizard 모드 opt-in (기존 인터랙티브 prompts; debloat / tuning / anti-detection knobs 포함한 full multi-step wizard 는 PR 7). 기본winpodx setup은 이제 non-interactive (호스트 감지 default).install.sh --manual플래그 (#255 PR 2).winpodx setup+pod wait-ready+ 앱 디스커버리 + reverse-open setup skip. 바이너리 + desktop entry + 아이콘은 정상 설치; provisioning 은 다음winpodx실행시 발화하는 first-run prompt (CLI Y/C/n 또는 GUI 모달) 로 defer. Auto path 거치지 않고 커스텀 knob (edition / 언어 / debloat / tuning / anti-detection) 선택하고 싶을 때 사용.WINPODX_MANUAL=1env var 동등.docs/INSTALL.md+ 한국어 미러에 "수동 설치" 섹션 추가.winpodx doctor서브커맨드 (#255 PR 6). 읽기 전용 진단. ~9 개 체크 (install source 감지, freerdp 존재, /dev/kvm 존재, 백엔드 PATH, config + 바이너리 상태, 컨테이너 health, pending setup 마커, autostart entry 무결성,cfg.pod.initialized플래그) 후 항목별[OK]/[WARN]/[FAIL]결과 + 다음 명령 제안 출력. 읽기 전용 — state 변경 안함, 사용자가 제안 복붙. FAIL 시 exit 1; CI / install 스크립트의 빠른 health gate 로 유용. 빠르게 동작 (< 2s healthy install) — 모든 subprocess probe 짧은 timeout, network 호출 없음.winpodx setup가 GUI launcher.desktop등록 (#255 G7). standalonewinpodx setup호출 (pip install, dev checkout, 또는 install.sh / 패키징이 GUI entry 안 떨군 경로) 이 이제data/winpodx.desktop를~/.local/share/applications/winpodx.desktop로 복사 — install.sh 가 curl install 에 하던 것 mirror./usr/share/applications/winpodx.desktop이미 있으면 skip (패키지 install 이 그 경로 소유, user-level 복사는 upgrade 시 패키지 버전 shadow).install_gui_launcher_desktop()가winpodx.desktop.icons의install_winpodx_icon()옆에 위치.winpodx uninstall단일 bash 스크립트로 consolidation (#255 follow-up). 이전 #255 PR 3 가 기존 ~300줄uninstall.sh옆에 ~470줄 Python 구현을winpodx.cli.uninstall에 추가 — 두 구현이 평행으로 drift (MIME cache / gtk-update-icon-cache / 게스트 레지스트리 scrub / 최종 pkill sweep / 볼륨 glob / storage bind-mount wipe / GUI launcher .desktop 제거 등 10+ 항목 갈림), bin/winpodx 심볼릭 링크가 purge 후에도 살아남는 버그 (#278 / #279) 반복 발생. Consolidation:uninstall.sh가 이제 canonical 구현.winpodx uninstall은 ~50줄 Python wrapper —/usr/share/winpodx/,~/.local/bin/winpodx-app/,~/.local/share/winpodx/,sys.prefix/share/winpodx/, 또는 dev checkout 에서uninstall.sh찾아os.execvp— Python install 이 반쯤 깨져도 uninstall 동작. 동일한uninstall.sh가 이제 모든 채널 (debian/winpodx.install, rpm %files, AUR PKGBUILD package(), pip wheel 용 pyproject shared-data, curl install bundle dir) 에 well-known 경로/usr/share/winpodx/uninstall.sh로 ship. Install-source 감지 (dpkg / rpm / pacman) 가 bash 로 이동; 바이너리를 패키지 매니저가 소유하면 먼저sudo apt remove/sudo dnf remove/sudo pacman -Rns실행 권장 후, 패키지의 postrm hook 이--from-postrm으로 같은 스크립트 재진입해서 user-side cleanup — dpkg/rpm db 와 디스크 상태 동기화 유지하는 유일한 순서.postrm-common.sh단순화: Python CLI 호출 대신/usr/share/winpodx/uninstall.sh --from-postrm --yes [--purge]로 delegate. 플래그 정규화:--yes(이전--confirm— silent alias 로 유지),--purge,--from-postrm(internal, postrm hook 이 set). PR 3 이 도입한--no-package-prompt플래그 제거 (동작이--from-postrm으로 흡수). 테스트:tests/test_uninstall_consolidation.py삭제 (Python 헬퍼들 더 이상 없음),tests/test_uninstall_wrapper.py(10 tests: 후보 경로 커버리지, execvp argv forwarding, 스크립트 못 찾을시 SystemExit) +tests/uninstall_smoke.sh(샌드박스 $HOME 스모크 — run 후 모든 예상 경로 사라짐 assert, shellcheck-clean) 추가.uninstall.sh가 pip / source install 감지 (#255 G6). 이전엔unknown으로 fall through — pip 사용자에게 hint 없었음. 이제 site-packages 또는 dev-checkout 경로 식별 후 user-side cleanup 뒤pip uninstall winpodx를 그 install kind 의 canonical 제거 명령으로 출력. 감지는 휴리스틱 (스크립트가 venv prefix 몰라서pip uninstall직접 구동 불가) → hint 는 정보용, auto-exec 아님.- debian / rpm / aur 패키지 매니저 post-install / post-remove 후크 (#255 PR 4). 모든 패키지 install 경로가 이제
winpodx setup(또는 winpodx 만 실행해도 first-run prompt 가 처리) 안내 banner 로 끝남. 모든 패키지 remove 경로가 tray / GUI / helper 프로세스 pkill + reverse-open listener stop (winpodx host-open stop-listener) 하는 작은 cleanup 실행 —apt remove/dnf remove/yay -Rns후 orphan 프로세스 안 남음. debian 의apt purge는 추가로 winpodx config dir 가진 사용자별로winpodx uninstall --purge --yes --no-package-prompt실행 (apt purge시맨틱 match). rpm 과 aur 은 purge 개념 없음 — post-remove 후크가 full wipe 방법 안내 (winpodx uninstall --purge --yes). 공통 로직은packaging/scripts/postrm-common.sh에 살고, 모든 distro 가/usr/share/winpodx/packaging/postrm-common.sh로 설치. 신규 파일:debian/postinst,debian/postrm,debian/winpodx.install,packaging/aur/winpodx.install. 수정:packaging/rpm/winpodx.spec(%post + %postun 추가),packaging/aur/PKGBUILD(install=winpodx.install + 스크립트 설치),.github/workflows/aur-publish.yml(PKGBUILD 옆에 winpodx.install push). winpodx --versioninstall source 접미사. 출력이 이제winpodx 0.5.8 (installed via apt)/(curl install)/(pip install)/(install source not detected)— 사용자가 provenance 즉시 확인.winpodx info [System]install source 라인. 동일 감지를 info 명령의 System 블록에 winpodx / OEM bundle / rdprrap / distro / kernel 와 함께 노출.winpodx.utils.install_source— 신규 helper.dpkg -S/rpm -qf/pacman -Qo(각 3s timeout) 로 install provenance 감지,~/.local/bin/winpodx-appcurl-install 휴리스틱 +site-packagessource-checkout fallback. 후속 uninstall / version / info / doctor consumer 용InstallSource(kind, label, package_name, removal_command)반환.- Windows-on-KVM 튜닝 프로파일 확장 (#245).
auto와safe가 이제 Hyper-V enlightenments (hv-relaxed,hv-vapic,hv-vpindex,hv-runtime,hv-synic,hv-reset,hv-frequencies,hv-reenlightenment,hv-tlbflush,hv-ipi,hv-spinlocks=0x1fff,hv-stimer,hv-stimer-direct) +virtio-rng-pci(/dev/urandombacked) 적용. multi-vCPU 게스트에서 스케줄링 / VM-exit 큰 개선; 엔트로피 풀 빠르게 채워져 첫 부팅 CryptoAPI / TLS handshake stall 방지.auto추가로 nested-virt CPU feature (+vmxIntel /+svmAMD) +hv-evmcs(Intel) —/sys/module/kvm_intel/parameters/nested(또는kvm_amd) 가Y일때 자동 노출 — Windows 게스트 안에서 Hyper-V / WSL2 / Docker Desktop 활성화. 호스트 nested KVM opt-in 안 됐으면 no-op.safe는 호스트 측 명시 opt-in 필요한 nested-virt +hv-evmcs제외. 새 CLI override:winpodx pod start --tuning {auto,safe,off,manual}으로 one-shot per-run 프로파일 선택 (winpodx.toml 영구 저장 안 됨). GUI Settings 에 Tuning Profile 드롭다운 + 호스트에서 resolved 프로파일이 무엇을 적용하는지 보여주는 liveformat_tuning_summary()패널 추가. 새TuningCapability.nested_kvmprobe +TuningProfile.apply_hv_enlightenments/apply_virtio_rng/apply_evmcs/apply_nested_virtflag. performance튜닝 프로파일 + Settings 카드 재배치 (#245 follow-up).auto와 동일하나dedicated_hostgate 우회 — 호스트 현재 idle CPU / free RAM 무관하게 CPU pinning + no-balloon 강제 on. 박스가 winpodx 에 거의 dedicated 이고 게스트 latency 우선시 다른 호스트 워크로드 희생 OK 일때 사용. Hard-gated 항목 (+invtsc,io_uring) 은 capability 감지 유지 —performance가 QEMU 거부할 CPU flag 강제 불가. CLI:winpodx pod start --tuning performance. GUI: Settings 에 전용 "Performance Tuning" 카드 신설 — dropdown 상단,format_tuning_summary()감지 패널 같은 카드 frame 내부 하단.- Settings UI polish + 디자인 토큰. 기존 "Container / VM" 카드 (10 form rows, 옆 7-row RDP 카드보다 큼) 를 "Hardware" 카드 (Backend / Edition / CPU / RAM / Idle / Max Sessions = 6 rows) + 풀-width "Localization" 카드 (Language / Region / Keyboard / Timezone = 4 rows) 로 split — 상단 row 가 height-balanced 2 column 레이아웃으로 읽힘.
winpodx.gui.theme에 디자인 토큰 레이어 추가:SPACE_XS/S/M/L/XL/XXL,RADIUS_XS/S/M/L/XL/XXL,FONT_CAPTION/BODY/SUBHEAD/HEADER/TITLE/HERO— 카드/헤더/요약 패널/form 라벨에 흩어진 magic number (24, 22, 14, 12, 11, ...) 가 named scale 하나로 수렴. Settings 페이지 + tuning 카드는 새 토큰으로 변환; 나머지 페이지는 follow-up 에서.
Changed
winpodx setup기본이 non-interactive auto 로 flip (#255 PR 1). 이전엔 호스트 감지 default + 일부 prompt 있는 인터랙티브. 이제 완전 auto (= 기존--non-interactive동작). 위자드 원하면winpodx setup --customize.--non-interactive플래그는 install.sh + 스크립트 호출자용 deprecated alias 로 유지.- Timeout 전반 확대 + compose-up activity-based (#298, #126). 느린 호스트/연결이 진행 중인 작업을 fail 안 시키게:
compose up이 activity-based (120s 고정 대신 출력 5min 침묵 시에만 kill, 4h hard cap);wait_for_windows_responsive90→300s 기본 / 호출부 180→600s; agent/health+ FreeRDP RDP-port probe 2→5s;transport.exec/run_in_windowsguest 호출 2-3x (sync-password 30→90s 등);compose down60→180s; daemon suspend/resume/inspect 30/10→90/30s. poll-loop 내 짧은 TCP probe (pod_status의check_rdp_port) 는 의도적으로 짧게 유지 (poll interval 역할). - CPU sub-flag 들을
ARGUMENTS에서 dockur 의CPU_FLAGS+VMXenv 로 이동; hv- 는 dockur 에 위임.* 세 문제 한 번에 해결: (1) PR #289 의-msg timestamp=on워크어라운드 — proc.sh:137 의 strip 후 빈 ARGUMENTS bash 슬라이스 회피용. (2) PR #281 의 hv-* enlightenments 가 dockur 의hv_passthrough(defaultHV=Y) 와 중복 → QEMU 10 의Ambiguous CPU model string. Don't mix both "-hv-evmcs" and "hv-evmcs=on"warning. (3) PR #281 의 명시적+vmx/+svmnested-virt 주입이 dockur 의VMXenv 와 중복. winpodx 가 이제 dockur 의 문서화된 env-var interface 사용: x86 에선CPU_FLAGS: "arch_capabilities=off,+invtsc"(dockur 가 안 추가하는 두 sub-flag), nested-virt 원할 때VMX: "Y"(dockur 가 호스트 CPU 보고+vmx/+svm선택). ARGUMENTS 는 이제 virtio-rng device 쌍만 carry. proc.sh:137 strip 코드 path 가 절대 trigger 안 됨 (ARGUMENTS 에-cpu host,없음) →-msg timestamp=onmarker 제거. ambiguous-string warning 사라짐 (우리가hv-evmcs안 추가 — dockur 가 hv-* set 전체 소유). winpodx 가 QEMU sub-flag deprecation 에서 decouple: QEMU 11/12 가 hv-* 문법 바꿔도 dockur 가 처리, 우리는 무관.
Fixed
- Agent 8765 bind 실패 — urlacl 잘못된 소유자 (#269). install.bat 완전 실행됐는데 (RDP listener up, HKCU\Run 등록, agent spawn) agent 가 매 부팅 FATAL:
HttpListener.Start() failed: ... 'http://+:8765/' ... conflicts with an existing registration. agent 가 non-admin User 라 strong-wildcard prefix bind 에 urlacl 예약 필요. 기존netsh http add urlacl ... user=Everyone listen=yes >nul 2>&1가 wrong-owner 예약 생성 orERROR_ALREADY_EXISTS(183) — 둘 다>nul가 숨김. 이제: 8765 중복 예약 모두 delete 후sddl=D:(A;;GX;;;WD)(World SID, locale-proof) 로 add, 결과 setup.log 로깅 +netsh http show urlacldump. agent.ps1 가 bind 5회 retry + 실패시 urlacl 상태 agent.log 에 dump. OEM bundle 24 → 25. @ismikes (Kubuntu 26.04) 협업으로 진단 — install.bat 변경 3개 test branch 로 배제 후 그의 agent.log 스크린샷이 urlacl 충돌 특정. - dockur
proc.sh:137ARGUMENTS-strip crash, 에이전트 절대 설치 안 됨 (#287, #269). dockur (qemus/qemu 베이스 이미지 경유) 가 compose 의ARGUMENTS:env 를 파싱,-cpu host,<sub-flags>를 자체 CPU_FLAGS 파이프라인용으로 strip 한 후 남은 부분에ARGUMENTS="${args::-1}"bash 슬라이스. 사용자 tuning profile 이 다른 거 아무것도 생성 안 하면 (예:tuning_profile = off) 남은 부분이 빈 문자열 → bash 슬라이스proc.sh: line 137: -1: substring expression < 0에러로 fail. proc.sh 가 그 라인에서 죽음 →/oem이 Windows 게스트로 복사 안 됨 →install.bat실행 안 됨 → winpodx 에이전트 서비스 미설치 → 8765 포트가 TCP accept 후 handshake 마다 RST →winpodx pod wait-ready60 분 timeout. @ankranidiotis (v0.5.7, Linux Mint 22) + @ismikes (Kubuntu 26.04) 둘 다 fresh install + default tuning 에서 보고._qemu_arguments_for_host()워크어라운드: extra QEMU arg 가 emit 되지 않을 때-msg timestamp=on(QEMU 로그 라인 timestamp, 그 외 no-op) append → proc.sh 의 strip 후 ARGUMENTS 에 최소 한 개 공백-separated 토큰 남음 → bash 슬라이스 가 빈 문자열 보지 않음.auto/safe/performancetuning profile 은 이미 hv-* / virtio-rng extras emit (#245) → 영향 없음. 워크어라운드는off+ 향후 순수-cpu host,...라인 만드는 모든 프로파일에 적용. podman-compose강제,podman composedelegation 불신 (#288). docker-compose CLI plugin 설치된 Fedora 계열에서podman compose가 docker-compose 로 delegate →group_add: [keep-groups](rootless/dev/kvmpassthrough 용) 인식 못함 →Unable to find group keep-groups로 컨테이너 시작 실패. install.sh 가 이제 standalonepodman-compose바이너리 요구,backend.podman._compose_cmd가 distro 별 설치 안내 raise. @magicdiablo (Nobara) 보고.-no-hpet제거 — QEMU 10 (dockur v5.15+) 가 invalid option 으로 거부. PR #281 의 hv-enlightenments 작업이 auto / safe / performance 프로파일이 Hyper-V 머신 타이머 선택할 때 QEMU ARGUMENTS 에-no-hpetappend. dockur v5.15 가 qemus/qemu v7.30 베이스 이미지 경유 QEMU 10.x ship; QEMU 10 이-no-hpet플래그 완전히 drop (대체는-machine ...,hpet=off— dockur 의 machine type 을 override 하게 됨, risky). 컨테이너 시작이qemu-system-x86_64: -no-hpet: invalid option으로 폭망. 플래그 제거의 functional 영향 zero —hv-stimer+hv-stimer-directenlightenments 가 이미 Windows 를 HPET clock source 에서 빠지게 함. PR #289 머지 후 main HEAD 에서 @ismikes (Kubuntu 26.04) 보고.- install.bat 의
[agent-install]구조화 마커 (#269 진단). agent-install 각 step (hkcu-run-register,spawn,post-spawn-probe,urlacl) 를[agent-install] step=<name> status=<enter|exit>로 fence, HKCU\Run write + Start-Process try/catch 로 예외 로깅, 5s post-spawn probe 가 8765 listener 확인 + agent.log tail → agent 설치 실패가 setup.log 에서 self-localize. - RPM spec
License:tag 를MIT AND Apache-2.0로 정정 (#301).packaging/rpm/winpodx.spec가License: MIT였는데config/oem/rdprrap-*-windows-x64.zip가 stascorp/rdpwrap (Apache-2.0) 번들. Fedora packaging guidelines 는 SPDX expression 이 redistributed binary content 의 모든 license 명시해야 함.debian/copyright와THIRD_PARTY_LICENSES.md는 이미 정확. RPM spec 가 이제 매칭.
[0.5.7] - 2026-05-21
#247 (항목별 debloat picker, 4 phase) + #254 (locale + edition save flow, 3 phase) 마무리 + 동시 발견된 #266/#267 OEM 권한 regression fix 묶음 feature 릴리즈.
Highlights
CLI + Qt + TTY picker 로 항목별 debloat, dockur TZ env var 통한 Windows 게스트 타임존 wiring, GUI Settings → Container/VM Localization dropdown + 저장시 자동 recreate, winpodx pod recreate [--wipe-storage].
winpodx debloat—data/debloat/items.toml항목별 카탈로그 (11개 항목, 4개 프리셋). CLI--list/--preset/--items/--undo/--menu+ 체크박스 + 리스크 뱃지 Qt picker 다이얼로그 (#247).- Windows 게스트 타임존이 이제 호스트 자동감지되고 dockur 의
TZenv var 로 전달;cfg.pod.timezoneGUI dropdown +winpodx config set pod.timezone --auto+ setup wizard prompt 모두 wired (#254). - Settings → Container/VM 가 기존 Edition picker 옆에 Language / Region / Keyboard / Timezone dropdown 추가; dirty 필드시 통합 save flow 가
_recreate_container(또는 language/edition 변경시_wipe_pod_storage선행) 자동 실행 (#254 P3). winpodx pod recreate [--wipe-storage]—compose.yaml재생성 + 컨테이너 destroy/재생성 (기본은 Windows 디스크 보존;--wipe-storage가 language/edition 변경용 Windows 재설치 트리거) (#254 P1).Config.save()가 이제pod.language/pod.region/pod.keyboard/pod.timezone/pod.tuning_profile도 영구 저장 — 본 fix 이전엔 이 5개 필드가 load 만 되고 write 안돼서winpodx config set가 다음 save 에 사라졌음.
Added
winpodx debloat --menu텍스트 모드 picker (#247, phase 4). SSH / TTY 전용 환경 사용자를 위한 Qt 다이얼로그 대안 (headless-friendly). 순수input()+print()— curses 의존 없음, display server 없음, 터미널 capability 스니핑 없음. 루프가 카탈로그를 번호 매긴 리스트 + 현재 선택 상태 + 프리셋명 + 리스크 뱃지 +(one-way)태그로 렌더, 그 후 명령 prompt:<N>항목 N 토글,<name>이름으로 토글,p프리셋 목록,p <name>프리셋 전환,aapply,qquit,hhelp.run_menu가input_fn/print_fninjectable 수용해서 동일 코드 경로가 인터랙티브 셸과 CI mocked-stdin 통합 테스트 둘다 처리.- GUI debloat picker 다이얼로그 (#247, phase 3). Tools → Debloat 가 이제 normal 프리셋 blind 실행 대신 모달 다이얼로그 (
gui/debloat_picker.py) 를 띄움. 카탈로그 각 항목이 체크박스 + 색상 리스크 뱃지 (녹/황/적) + description 툴팁 한 행으로 노출; 프리셋 라디오 (Normal / Full / Performance / Speed / Custom) 가 체크박스 상태 seed, 항목 토글시 라디오가 Custom 으로 전환. Apply 가 기존run_via_transport채널로 선택 실행; Cancel 은 게스트 무영향. one-way 항목 (onedrive, startup_programs) 은 행에 "(one-way)" 태그 표시해서 #247 P2 의--undo경로로 되돌릴 수 없는 선택을 사용자가 한눈에 인지. winpodx debloat --undo+ 항목별 undo 스크립트 + state JSON 추적 (#247, phase 2). 카탈로그 각 entry 가 선택적undo_script필드 보유 가능; reversible 9개 (11개 중) 가undo/<name>.ps1sibling 으로 ship — apply 를 되돌림.winpodx debloat --undo --items <list>(또는--undo --preset <name>) 가 undo 경로 실행; one-way 항목 (onedrive,startup_programs) 포함된 mixed 선택은 명확한 "items have no undo path" 에러로 중단해서 부분 undo 가 일어나지 않음. Apply orchestrator 가 매 성공 apply 후%ProgramData%\winpodx\debloat-applied.json에 항목별 entry 기록, undo orchestrator 가 제거 — GUI picker (phase 3) 가 각 체크박스 옆에 "currently applied" 상태 표시 위한 기반.winpodx debloat --list/--preset/--items(#247, phase 1). 기존 모놀리식winpodx debloat(항상 동일한 70줄debloat.ps1실행) 을 항목별 카탈로그 + 선택 resolver 로 리팩터. 카탈로그는data/debloat/items.toml에 11개 항목 (telemetry, ads, onedrive, sysmain, web_search, widgets, scheduled_tasks, startup_programs, visual_effects, search_indexing, transparency) 와 함께 ship, 각 항목은scripts/windows/debloat/의 작은 per-item.ps1가 백킹. 4개 프리셋 (normal,full,performance,speed) 이 일반적 선택을 seed; 인자 없는winpodx debloat는normal(telemetry + ads) 으로 기본 처리해서 기존 동작 유지.--list는 게스트 접촉 없이 전체 카탈로그 + 프리셋 출력.--items는 쉼표 구분 명시 이름 리스트 수용,--preset와 동시 지정시 우선. 본 phase 범위 밖 (#247 별도 추적): 항목별 undo, GUI picker 다이얼로그, TTY-menu (--menu).- GUI Settings → Container / VM 카드에 Localization dropdown 4개 추가 (#254, phase 3). 기존 Edition / CPU / RAM 옆에 4개 신규 행: Language, Region, Keyboard, Timezone. 각 행의 첫 옵션은
Auto (...)이고 빈 TOML 문자열에 매핑 (= compose 시점 호스트 자동감지); timezone 행의 auto label 은 실시간detect_timezone()결과를 노출해서 게스트에 들어갈 값 미리보기 가능. Save 핸들러는 recreate prompt 를 두 경로로 분기: timezone / CPU / RAM / port / user 만 dirty 면 "Restart now?" 평이한 프롬프트 (Windows 디스크 보존, OEM tzutil 이 매 recreate 마다 재적용), Edition 또는 language / region / keyboard 중 하나라도 dirty 면 "Wipe and reinstall now?" 강한 프롬프트 (dockur 가 그 env 들을 초기 설치에만 적용하므로 일반 recreate 는 silent no-op). Wipe 수락 시winpodx pod recreate --wipe-storage와 동일한_wipe_pod_storage헬퍼 호출. winpodx config set pod.<key> --autoshorthand (#254, phase 2). 명명된 키의 호스트 감지값을 positional<value>대신 사용. 현재 wired:pod.timezone(utils.locale.detect_timezone의 IANA 이름). 다른 locale 키 (language / region / keyboard) 는 명확한 "not yet supported" 에러를 #254 링크와 함께 출력하고 non-zero exit — 해당 detector 는 follow-up.--auto와 positional<value>는 상호 배타.winpodx setup인터랙티브 timezone prompt (#254, phase 2). wizard 가 CPU / RAM row 뒤에cfg.pod.timezone을 prompt; 기본값은 호스트 감지 IANA zone. non-interactive setup (install.sh 의 call path) 는cfg.pod.timezone을 기본 빈 문자열로 두어 compose 생성시 호스트 자동감지가 첫 부팅에 작동.cfg.pod.timezone— Windows 게스트 타임존 wiring (#254, phase 1). 신규 config knob. 빈 문자열 (기본) → compose 시점에 호스트 자동감지 (timedatectl show --property=Timezone --value→readlink /etc/localtime→/etc/timezone→UTCfallback chain). 감지된 IANA 이름은 CLDR 파생data/locale/windows_zones.toml테이블 (001wildcard subset 약 150개 entry) 로 Windows TZ ID 로 번역. 번역값이<oem_dir>/timezone.txt에 기록되고 OEMinstall.bat가 첫 부팅 시 이 파일을 읽어 기존의 무조건tzutil /s "UTC"대신tzutil /s "<id>"실행. TOML 에 명시된 IANA 이름이나 bare Windows TZ ID 도 그대로 수용; 감지 실패시 UTC 는 파일을 쓰지 않아 게스트가 강제 UTC 로 전환되지 않고 현재 zone 유지. 호스트 자동감지는 신규 설치 시 Windows 게스트가 호스트 locale 과 무관하게 UTC 기본값으로 떨어지던 @ismikes 보고 (#204) 를 해소.winpodx pod recreate [--wipe-storage]서브커맨드 (#254, phase 1). 현재 config 로부터compose.yaml재생성 후 컨테이너 destroy + 재생성 — first-boot env knob 변경 (timezone, edition, backend) 적용에winpodx pod restart보다 명확한 primitive.--wipe-storage는 Windows 디스크 볼륨 / 바인드 마운트까지 파괴해서 dockur 가 전체 설치 재실행하게 함 — language / region / keyboard / edition 변경은 dockur 가 그 env vars 를 Windows 초기 설치 시점에만 적용하기 때문에 게스트에 실제 반영되려면 wipe 필요. wipe 경로는 명시WIPE입력 confirmation 요구.config/oem/install.bat가C:\OEM\timezone.txt존재시 읽음 (#254, phase 1). 파일 부재시 기존tzutil /s "UTC"동작 유지.data/locale/windows_zones.toml— IANA → Windows TZ ID 매핑 테이블 (#254, phase 1). pyproject shared-data 레이아웃을 통해share/winpodx/data/locale/에 배포. Refresh 헬퍼 (scripts/ci/refresh_windows_zones.py) 는 follow-up.
Changed
- 타임존 wiring 을 dockur 의 native
TZenv var 중심으로 재설계 (#267, #254 P1 refactor). 원래 P1 설계는 OEM bind-mount dir 에timezone.txt파일 drop +install.bat에tzutil블록으로 처리 — per-config 파일을 source bundle 오염 없이 두려고 OEM dir 항상-복사 강제. 그게 PR #95 가 원래 회피했던 parent-dir traversal 문제 재도입: dockur 의 in-container OEM cp 가 non-root sub-UID 로 돌아서 0700 인~/.config또는~/.local조상 traversal 불가 → 모든 OEM 파일에cp: cannot stat /oem/./<file>: Permission denied. dockur 의TZenv var (이미 Sysprep unattend.xml 의<TimeZone>처리) 로 전환해서 OEM 파일 요구 자체 제거,_find_oem_dir가 #254 이전 two-regime 레이아웃 (user-writable 시 bundle-direct / read-only 시 copy) 으로 복원, umask 077 perm 트랩 제거.install.bat의 옛tzutil블록 삭제;utils/locale.py(호스트 감지 + IANA→Win 매핑 테이블) 는 GUI 표시 및 wizard auto-default 위해 유지.
Fixed
- umask 077 호스트에서 fresh install 시 OEM cp
Permission denied캐스케이드 (#267). 증상: dockur 첫 부팅 OEM 복사가 모든 OEM 파일에cp: cannot stat '/oem/./<file>': Permission denied실패 — #254 P1 의 always-copy OEM dir 경로가 도입한 0700~/.config/조상을 컨테이너의 non-root sub-UID 가 traverse 불가. 사용자가 bundle 소유시 bundle-direct OEM mount 로 복원해서 수정 (위 Changed entry 의 깊은 refactor 참조). Config.save()가 이제pod.language/pod.region/pod.keyboard/pod.timezone/pod.tuning_profile도 영구 저장 (#254, phase 1). 본 fix 이전엔 이 5개 필드가winpodx.toml에서 로드만 되고 다시 write 되지 않음 — 프로그램적 변경 (winpodx config set pod.language ...) 이 다음 save 에서 조용히 버려졌음. 직접 편집한 값은 덮어쓰는 게 없어서 살아남고 있었음. 이제 5개 필드 모두 save/load roundtrip.
[0.5.6] - 2026-05-21
@ismikes (#214) 의 보고로 시작된 핫픽스 릴리스. 모던 rootless podman + pasta (Ubuntu/Kubuntu 26.04 기본) 에서 "Launching... 떴는데 RDP 창 안 뜸" 증상 수정, winpodx info 의 VNC reachability 거짓 부정 수정, uninstall.sh 종료 전 belt-and-braces tray sweep 추가.
Highlights
Kubuntu 26.04 에서 "Launching..." 에 걸리던 RDP launch 재동작, winpodx info 의 VNC reachability 거짓 보고 종결 (#214).
core/rdp.py가 더 이상 FreeRDP 를podman unshare --rootless-netns로 감싸지 않음 — wrap 이 xfreerdp3 를 컨테이너 net ns 안에 넣어서 호스트 측 publish 가 보이지 않게 만들고 있었음. @ismikes 보고, @smoore100 진단, 양쪽 모두 fix 확인.winpodx info의 VNC reachability probe 가 RDP X.224 handshake → plain TCP accept 로 변경 (VNC 는 RFB, RDP 아님).uninstall.sh가 종료 전 final tray/GUI sweep 추가 — uninstall 윈도우 중간에 올라온 프로세스 (다른 터미널 GUI launch, XDG-autostart race, dbus activation) 도 종료.
Fixed
- 모던 rootless podman + pasta 에서 앱 launch 가 "Launching..." 에서 멈추던 문제 수정 (#214). Kubuntu 26.04 (Wayland + Plasma 6 + FreeRDP 3.24.2) 에서 @ismikes 제보, @smoore100 진단.
core/rdp.py가 모든 FreeRDP 호출을podman unshare --rootless-netns로 감싸서 xfreerdp3 를 컨테이너의 network namespace 안에서 실행 → 호스트 측 publish 포트가 보이지 않음 → "Launching..." 만 찍히고 RDP 창은 안 뜸. 이 wrap 제거; FreeRDP 가 이제 호스트 loopback 에서 직접 실행,127.0.0.1:<rdp_port>가 기존 podman publish 로 정상 도달. 부수 이득: FreeRDP launch path 의podman바이너리 의존 해소 (flatpak-FreeRDP-only 셋업도 동작), #214 의 Plasma 6 / Wayland / FreeRDP 3.24 엣지케이스 unblock. winpodx info가 NoVNC 가 정상 서빙중인데 "VNC unreachable" 로 거짓 보고하던 문제 수정 (#214). VNC 포트 (기본 8007) 가check_rdp_port로 probe 되고 있었음 — X.224 Connection Request 보내고 TPKT 응답 대기. RFB 말하는 VNC 서버 상대로는 무의미 → 항상 False 반환. VNC 는 이제check_tcp_port(plain TCP accept) 사용 — "NoVNC 엔드포인트 연결 수락중인가" 와 일치하는 probe.uninstall.sh종료 전 final tray/GUI sweep.WINPODX_NO_TRAY_SPAWN=1+ section-0apkill은 uninstall.sh 가 tray 를 건드리는 유일한 행위자인 경우만 cover. uninstall 윈도우 중간에 올라온 tray/GUI 프로세스는 놓침 — 다른 터미널에서 GUI 창 열어서 이미 tray spawn 한 경우, KDE/GNOME XDG-autostart race,cli/main.py의 spawn 체크를 우회하는 dbus-activated launch. 요약 직전 quiet finalpkill -f 'python.*winpodx'+pkill -f winpodx-apppass 추가로 잡음.
Internal
packaging/obs/obs-publish.yml의 download grep 을 현재 태그 버전에 앵커. Rolling-release repo (Tumbleweed / Slowroll 의 lazy GC) 가 OBS publish 인덱스에 남긴 stale 옛 RPM 이 release asset 으로 재업로드되지 않게 됨. v0.5.5 가 처음 표면화 — stale 0.5.4 RPM 2개가 Release 페이지에 들어가서 수동 삭제 필요했음.
[0.5.5] - 2026-05-21
@tolistim (#216) 과 @ismikes (#215) 의 사용자 리포트로 시작된 follow-up 릴리스. 호스트 config 와 Windows guest 패스워드를 desync 시키던 setup 재실행 lockout 수정, 사용자 설정 없이 자동으로 켜지는 호스트 적응형 Windows-on-KVM 튜닝 프로파일, 그리고 오래 idle 된 pod 이 tray 에서 starting 으로 고정되던 증상 종결.
Highlights
winpodx setup 재실행 안전 + dockur compose 호스트 자동 튜닝 + idle pod 가 tray 를 starting 으로 얼리지 않음 (#215, #216).
- 기존 설치에서
winpodx setup재실행이cfg.rdp.password를 조용히 덮어쓰던 문제 수정;LOGON_FAILED_BAD_PASSWORDlockout 사라짐. @tolistim 제보 (#216). - 새
cfg.pod.tuning_profile(기본"auto"): WinPodX 가 compose 시점에 호스트 probe (invariant TSC, kernel ≥ 5.6 = io_uring,vm.nr_hugepages, idle CPU + RAM headroom) 후 호스트가 지원하는 모든 안전 Windows-on-KVM 튜닝 활성화. 지원 CPU 에서+invtsc가 x86_64 QEMU args 에 들어감.winpodx info/winpodx setup가 새[Tuning]블록 출력해서 자동 적용 set 항상 가시적. @ismikes 제안 (#215). safe/off/manual프로파일 escape hatch; 호스트 사전 설정 필요한 항목 (hugepages, CPU pinning, VFIO) 은 자동 처리 대신 운영자 작업으로 문서화.- 새
PodState.UNRESPONSIVE+ 자동 회복.pod_status()가 부팅 중 컨테이너 (STARTING, 가동시간 600초 미만) 와 오래 돌아간 컨테이너의 RDP 응답 정지 (UNRESPONSIVE, 가동시간 600초 이상) 를 구분. tray 가RUNNING → UNRESPONSIVE전이 감지 시 desktop notification 발사 + 백그라운드 worker 가 agent transport 로 in-guestTermService재시작. 성공 시 "pod recovered" notification, 실패 시winpodx pod restart안내 notification. OEM install.bat 도powercfg모든 timeout 을0으로 +PlatformAoAcOverride클리어해서 Modern Standby 가 idle 세션 NIC 를 끊지 못하도록 차단.
Added
-
PodState.UNRESPONSIVE+ 자동 회복 흐름. 컨테이너가 10분 이상 가동되었는데 RDP 포트가 응답 안 하면pod_status()가 새UNRESPONSIVE반환 — 이전에는 영원히STARTING으로 잘못 보고됨. tray 가 이전 상태를 캐시하고RUNNING → UNRESPONSIVE전이 시notify_pod_unresponsive+ 백그라운드 worker (try_recover_rdp, 신규core/pod/recovery.py) 발사. 회복 절차: agent 에게Restart-Service -Force TermService요청 + RDP 포트 30초 동안 재 probe. 성공 →notify_pod_recovered. 실패 →notify_pod_needs_manual_restart에 실패 원인 (agent unreachable/rdp still down) 표시해서winpodx pod restart안내.Backend.uptime_secs()가 새 ABC 메서드; Podman + Docker 는inspect -f '{{.State.StartedAt}}'로 구현; libvirt + manual 은None반환해서 legacySTARTING으로 fallback. -
config/oem/install.bat가 RDP 를 죽일 수 있는 Windows idle timeout 전부 비활성화. Ultimate Performance plan 만으로는 부족 — Modern Standby (S0 low-power idle) 가 여전히 virtio NIC 를 끊고 TermService 를 stall 시킬 수 있음. AC + DC 양쪽의standby/hibernate/monitor/disktimeout 을0으로 설정,HKLM\SYSTEM\CurrentControlSet\Control\Power\PlatformAoAcOverride클리어해서 Desktop class power 동작 강제, 그리고 현재 scheme 의STANDBYIDLE 0까지 보강. idle pod 의RUNNING → UNRESPONSIVE전이 근본 원인 제거. -
호스트 절전 / 복귀 시 tray 가
starting으로 얼리는 문제 해결. 두 갈래: 게스트에는 SYSTEM-level 스케줄 태스크가Win32_PowerManagementEvent구독 (config/oem/power-monitor.ps1), EventType 7 / 18 (suspend / modern-standby 복귀) 5초 후TermService재시작해서 in-guest RDP listener 를 다음 probe 전에 새로 만듦. 호스트 tray 는 QtDBus 로org.freedesktop.login1의PrepareForSleep(active=False)signal 구독해서 resume 5초 후 pod state 갱신 → 기존 UNRESPONSIVE 자동회복 경로 즉시 트리거 (30초 poll 기다리지 않음). 양쪽 모두 graceful degrade — D-Bus 구독 best-effort, 스케줄 태스크는 fresh OEM apply 시에만 설치 (기존 pod 는 follow-up 의winpodx pod apply-fixes로 적용). -
OEM 설치 끝에 스케줄된 재부팅으로 레지스트리 변경 실제 적용.
PlatformAoAcOverride = 0(Modern Standby off) 과 NIC binding 같은 reg 키가HKLM에 들어가지만 실행 중 세션은 다음 부팅까지 옛 값 유지. install.bat 가 이제C:\winpodx\oem_reboot_pending.txtmarker 작성 + 두번째 부팅 시 marker 삭제하는RunOnce키 등록 + 마지막 동작으로shutdown /r /t 15발사.winpodx pod wait-ready에[4/4] Waiting for OEM reboot pass...단계 추가; agent transport 로 marker 폴링. upgrade-path 설치 (marker 없음) 는 30초 appear-grace window 후 short-circuit 으로 기존 flow 변화 없음. -
Tray UX 개편: 항상 떠있는 tray + Dashboard 단축키 + 확인 후 Quit. tray 가 이제 GUI 창과 pod 를 건드리는 모든 CLI 서브커맨드 (
setup/gui/tray제외) 에서 자동 spawn →winpodx app run만 쓰는 사용자도 UNRESPONSIVE 자동 회복 드라이버 활용 가능. tray 아이콘이 제대로 표시 (번들winpodx-icon.svg가tray.show()전에 설정 — 이전에는 KDE Plasma / GNOME 이 아이콘 없다고 표시 자체 안 함). 새 "Open Dashboard" 메뉴 항목이 최상단. Quit 가 이제 QMessageBox 로 확인 ("winpodx 를 완전히 종료할까요?") 후stop_pod+pkill -f 'winpodx gui'+app.quit실행 — 실수 클릭으로 pod 30초 재시작 비용 발생 방지.$XDG_RUNTIME_DIR/winpodx/tray.lockflock 으로 중복 인스턴스 차단. -
ensure_readyCLI launch 시 2단계 RDP 회복. CLI 런치 (winpodx app run, GUI 앱 클릭 등) 가 FreeRDP spawn 전에 stall 된 RDP 게스트를 자가 치유: 1단계 =try_recover_rdp(agent 로 TermService 재시작, ~5-30초, Windows uptime 유지), 2단계 =recover_rdp_if_needed(컨테이너 전체 재시작, ~30초, Windows uptime 리셋). 이전 동작은 connection-refused 로 launch 실패; 이제는 cheap 경로 먼저, agent 도달 불가 시에만 escalate. -
GUI Settings: "Launch winpodx tray at login" 체크박스. Settings 페이지의 새 토글이 XDG autostart 스펙 통해
~/.config/autostart/winpodx-tray.desktop작성/삭제 — KDE / GNOME / XFCE / Cinnamon 모두 portable. 파일 존재가 source of truth (cfg.toml 필드 / 데몬 불필요), 사용자가 손으로 .desktop 떼서 opt out 가능.X-GNOME-Autostart-enabled=true포함 → GNOME 세션 매니저가 DE 기본값 리셋 후에도 인식. 토글 즉시 적용 — Save Settings 클릭 불필요. -
uninstall.sh가 새 tray + autostart 표면 처리. 두 신규 섹션: (0a) 파일 삭제 전winpodx gui+winpodx tray프로세스pkill -f+ 1초 grace — 두 프로세스가 install / runtime / config 디렉토리에 FD 열고 있음 + tray 가 곧 사라질 컨테이너에 대해 자동 회복 알림 계속 발사하는 걸 차단; (6b)--purge여부 무관~/.config/autostart/winpodx-tray.desktop항상 제거 — .desktop 은 winpodx 전용이라 binary 없으면 의미 없고, 두면 다음 로그인에winpodx: command not found발생. -
설치 중 tray spawn 과 UNRESPONSIVE 자동 회복 억제. install.sh 가
$XDG_CONFIG_HOME/winpodx/.install_in_progressmarker 작성 (EXIT/INT/TERM trap 으로 삭제). 세 호출 지점이 marker 확인: (1)maybe_spawn_tray가 marker 있으면 skip —[3/4]/[4/4]단계 중 GUI/CLI auto-spawn 안 함; (2) tray 의 RUNNING → UNRESPONSIVE 전이 핸들러가 marker 있으면 알림 + 회복 worker skip — 이전 세션에서 떠있던 tray 도 조용히; (3)ensure_ready의 2단계 RDP 회복도 marker 있으면 skip — install 중 사용자가winpodx app run해도 진행 중인 Sysprep 을 컨테이너 재시작으로 날려먹지 않음. install 진행 중 false-positive "Pod stopped responding" 알림 제거. -
호스트 적응형 Windows-on-KVM 튜닝 프로파일 (#215).
cfg.pod.tuning_profile이 dockur compose 에 대한 WinPodX 의 튜닝 적극성을 제어. 기본값"auto"는 compose 생성 시점에 호스트를 한 번 probe (constant_tsc + nonstop_tsc, kernel ≥ 5.6 = io_uring,vm.nr_hugepages > 0, idle CPU + RAM headroom) 하고 표준 튜닝 중 호스트가 지원하는 것을 적용 — 현재는 invariant TSC 노출하는 x86_64 호스트에+invtsc. 다른 프로파일:"safe"(Tier-1 만 —+invtsc+ Windowsplatform_tick, 호스트 사전설정 불필요),"off"(dockur 기본만),"manual"(safeshape; 향후 개별 knob override).winpodx info+winpodx setup둘 다 감지된 capability + resolved profile 을 출력해서 사용자가 무엇이 자동 적용됐는지 확인 가능. @ismikes 제안 (#215). sysguides.com Windows-on-KVM 튜닝 중 호스트 사전설정 필요한 항목 (hugepages, CPU pinning) 은 WinPodX 가 자동 처리하지 않고docs/USAGE.md에 사용자 호스트 작업으로 문서화.
Fixed
-
winpodx setup재실행이 Windows 패스워드를 조용히 덮어써서 사용자가 잠기는 문제 수정 (#216). 기존 설치에서 cores/RAM 만 변경하려고 setup 을 다시 돌리면 Windows 패스워드 프롬프트가 또 떠서, 입력한 값이winpodx.toml에 저장됨. dockur 는USERNAME/PASSWORDenv var 를 첫 부팅에만 적용하기 때문에 호스트 config 와 Windows guest 계정이 desync 되고 다음 RDP launch 가LOGON_FAILED_BAD_PASSWORD로 실패. interactive 프롬프트가 이제 기존 config +cfg.rdp.user/cfg.rdp.password채워진 상태를 감지해서 보존하고,winpodx rotate-password(Windows-side 패스워드 변경 메커니즘 사용) 사용을 안내. non-interactiveinstall.shsetup 과 신규 설치는 변경 없음. Linux Mint 22.3 / v0.5.4 에서 @tolistim 제보. -
문서: Fedora 42 / 43 / 44 설치 snippet 이 dnf5 syntax 사용 (#228). 옛
sudo dnf config-manager --add-repo <URL>는 Fedora 41+ 의 dnf5 에서 subcommand 모양 변경으로 실패. snippet 이 이제sudo dnf config-manager addrepo --from-repofile=<URL>; dnf4 형식은 Fedora ≤40 (EOL) 용으로 아래 노트.README.md/docs/README.ko.md/docs/INSTALL.md/docs/INSTALL.ko.md/packaging/obs/README.md갱신. @payayas 제보. -
install.sh가 하드웨어 가상화 꺼졌을 때 거짓으로 success 보고하던 문제 수정 (#220). Linux Mint LMDE 7 에서 @pnogaret2019-code 제보. install loop 가qemu-system-x86이 이미 깔린 상태를 "all dependencies installed successfully" 로 처리했지만 BIOS 에서 VT-x 가 꺼져있어/dev/kvm은 여전히 없었음. install.sh 가 이제 (a) 설치 전 사전 경고를 강하게 표시 — 진짜 원인 3개 (BIOS, kernel module, kvm 그룹) 를 진단 명령과 함께 안내, (b) install loop 이후/dev/kvm재확인하고 없으면 livelscpu/lsmod/id출력 + 중단. README + 한국어 미러에 "최소 요구사항" 섹션 추가 — 동일 3행 체크 표 — 사용자가 curl-install 라인 복사하기 전에 게이팅 요구사항을 먼저 봄. "설치는 됐는데 Windows 가 안 떠요" 류 버그 리포트 가장 큰 단일 원인 차단. -
install.shUbuntu 24.04+ / Debian 13 에서 가상 패키지qemu-kvm대신 실제qemu-system-*선택 (#200). VirtualBox 안 xubuntu 26.04 에서 @n-osennij 제보. apt 가E: Package 'qemu-kvm' has no installation candidate에러 — Ubuntu / Debian 신규 버전이qemu-kvm을 가상 패키지로 만들고 실제 provider 는qemu-system-x86(또는qemu-system-x86-hwe).pkg_name()가 이제 PR #198 의 freerdp2 → freerdp3 selector 와 동일한apt-cache showprobe 패턴 사용:qemu-system-x86우선 (aarch64 면qemu-system-arm),qemu-system-x86-hwefallback, 그 다음 옛 distro 용qemu-kvmfallback. VirtualBox 측 nested virtualization 요구사항은 호스트 환경 — WinPodX scope 밖이라 새 "최소 요구사항" README 섹션이 커버. -
dockur/windows 이미지 pin v5.15 로 bump (#223, #224).
DOCKUR_IMAGE_PIN/DOCKUR_IMAGE_ARM_PIN+config/oem/VERSIONS.txt가 신규 digest 추적 (sha256:32abe0836aee...x86_64,sha256:5775bcfd335b...aarch64). v5.14 이후 upstream 누적 fix 흡수, WinPodX 측 compose 표면 변경 없음. -
install.sh가 Ctrl+C 시Pod is starting, not running. Start the pod now and apply? [Y/n]단계 넘어 진행하던 문제 수정. 옛 install loop 의 단일EXIT INT TERMtrap 이 install marker 정리만 하고 다음 단계로 떨어졌음. trap 3개로 분리 (EXITcleanup-only,INTcleanup +exit 130,TERMcleanup +exit 143) +wait-ready | tee파이프라인을set +e로 래핑 —|| truerescue 가PIPESTATUS[0]를 덮어쓰기 전에 실제 wait-ready rc 보존, SIGINT 인지 실제 실패인지 분기 가능. -
winpodx setup --non-interactive플래그 — install.sh 가 사용하는 setup migration 용. install.sh 는 최초 설치 후 setup migration 호출.--non-interactive없으면 storage-path prompt 에서 막혀 curl-install 가 stall. setup 이 플래그를 받고, 설정시 모든 interactive prompt 를 "accept default" 로 처리. -
uninstall.sh가 uninstall 중간에 tray 가 다시 살아나던 문제 수정. Section 0a 가 tray 를pkill했지만, section 0b 가 reverse-open teardown 위해winpodx host-open stop-listener/unregister-guest호출.cli/main.py가{setup, gui, tray}외 모든 subcommand 에서 tray 자동 spawn 시키므로host-open이 새 tray spawn 트리거 → section 0a 작업 즉시 무효화.uninstall.sh가 이제WINPODX_NO_TRAY_SPAWN=1export;maybe_spawn_tray()가 이 env 감지하면 short-circuit, uninstall.sh 가 실행하는 모든winpodxCLI subprocess 가 env 상속 → spawn skip.
[0.5.4] - 2026-05-19
사용자 리포트 follow-up 릴리스. 느린 회선 compose timeout (#212), #213 / #214 가 드러낸 silent "launched but no window" 실패 모드 제거, CI matrix 에 Ubuntu 26.04 추가 (#206), Windows 언어 / 지역 / 키보드 설정 (#201), 동적 데스크톱 해상도 (#197) 수정.
메모: v0.5.3 이 같은 날 먼저 태그됐으나 구식
hatchling의 Trove classifier whitelist 미스매치로 Debian/Ubuntu.deb빌드가 실패. v0.5.4 가 동일 feature set + 패키징 fix 로 대체; 같은 날 두 릴리스 페이지 피하려고 v0.5.3 릴리스 페이지는 삭제. v0.5.3 태그는 history 로 유지.
Highlights
pull 친화적 compose timeout, 가시적 FreeRDP launch 오류, CI 에 Ubuntu 26.04, 그리고 모든 지원 distro 에서 .deb 빌드 그린으로 만든 패키징 fix.
podman-compose up -d/down기본 timeout 120초 → 1800초;WINPODX_COMPOSE_TIMEOUT_SECS환경 변수 지원 (0= 제한 없음). @jimed-rand 가 제보한 #212 수정.launch_app가 spawn 500ms 후 프로세스를 폴링해서 stderr 로그 경로와 함께FreeRDP exited immediately with rc=...raise. @mozjacksson 의 #213 + @ismikes 의 #214 수정.- CI matrix 에 Ubuntu 26.04 패키지 + Python 3.14. #213 / #214 의 미테스트 플랫폼 공백을 닫음 (by @juampe, #206).
- Windows
[pod] language / region / keyboard키;user/password/home와 동일한 YAML 인젝션 hardening (by @juampe, #201). - 데스크톱 세션이 이제 FreeRDP 클라이언트 창 크기에 맞춰 동적으로 리사이즈 (by @Zeik0s, #197).
.deb의/usr/share/winpodx/에 LICENSE / README 배포; Debian/Ubuntu 의 GUI License 탭이 다시 동작 (by @juampe, #201).- GUI 시작 시 Qt 스타일시트 파서 경고 제거 (by @juampe, #203).
- 패키징 fix:
pyproject.toml에서 minor-version Python Trove classifier (:: 3.9…:: 3.13) drop. 구식hatchling(Debian 12 는 1.12.0, Ubuntu 24.04 는 1.18.0) 이 자체 classifier whitelist 를 유지해서 release 이후 추가된 minor-version classifier 를 거부 — v0.5.3.deb빌드 실패 원인. version-agnostic:: 3 :: Only로 대체. 지원 버전 정보는 그대로requires-python = ">=3.9"와 CI matrix 에서 제공.
Added
- Windows 언어 / 지역 / 키보드 설정. Pod 설치 언어, 지역 포맷, 키보드 레이아웃을
winpodx.toml의[pod] language / region / keyboard로 설정 가능. 기본값English/en-001/en-US(기존 config 와 호환); 한국어 Windows 설치는Korean/ko-KR/ko-KR처럼 지정. 세 필드 모두PodConfig.__post_init__의_DANGEROUS_YAML_CHARSsanitisation 과 compose 렌더 시점의_yaml_escape두 단계 방어를 거침 — 기존user/password/home와 동일한 패턴.docs/INSTALL.md와docs/INSTALL.ko.md에 자주 쓰는 10개 언어 조합 표 추가. fresh Windows 설치에만 적용; 이미 설치된 pod 는 volume 삭제 후winpodx setup재실행 필요. (by @juampe, #201)
Changed
- CI matrix 가 이제 Python 3.14 를 테스트하고 Ubuntu 26.04 패키지도 빌드합니다 (#206). #213 / #214 에서 드러난 미테스트 플랫폼 공백을 닫습니다. (by @juampe)
Fixed
-
compose up/down timeout 을 이미지 pull 친화적으로 조정. 기본 compose timeout 을 120초 → 1800초로 늘리고
WINPODX_COMPOSE_TIMEOUT_SECS환경 변수를 지원합니다 (0= 제한 없음). @jimed-rand 가 보고한 #212 수정. -
.deb패키지에 LICENSE 및 README.md 가 누락되던 문제 수정.pyproject.toml의[tool.hatch.build.targets.wheel.shared-data]가 이제LICENSE와README.md를/usr/share/winpodx/에 배포. GUI License 탭이 Debian/Ubuntu 설치본에서FileNotFoundError로 깨지지 않음. (by @juampe, #201) -
GUI 시작 시 Qt 스타일시트 파서 경고 수정.
main_window.py가 bare property 선언 (background: ...) 과 full QSS selector 룰 (QPushButton { ... }) 을 혼합해서 Qt 스타일시트 엔진이 파싱을 거부하던 문제. 중앙 widget 에objectName="centralRoot"부여하고 배경 룰을QWidget#centralRoot { ... }로 스코프화. 시각 변화 없음, 로그의Could not parse stylesheet of object QWidget(...)경고만 제거. Library 페이지 Launch/Edit 버튼도 파서 경고 추적 중 가독성을 위해 인라인 QSS 를 멀티라인 블록으로 변환. (by @juampe, #203) -
동적 데스크톱 창 해상도 — 이제 데스크톱 세션을 생성할 때 해상도가 FreeRDP 클라이언트 창 크기에 맞춰 동적으로 조정됩니다. 이 기능이 데스크톱 세션의 기본 동작으로 추가되었습니다. 테스트를 통해 검증되었습니다. (@Zeik0s 작성, #197)
-
빠른 종료 시 FreeRDP stderr 를 launch 오류로 표시. "실행됨" 메시지만 보이고 창이 뜨지 않는 silent failure 를 제거합니다. Plasma 6 / Wayland 및 Python 3.14 edge case 진단에 도움. @mozjacksson 이 제보한 #213 및 @ismikes 가 제보한 #214 수정.
-
Debian/Ubuntu
.deb빌드가Unknown classifier: Programming Language :: Python :: 3.X로 실패. Debian/Ubuntu 가 패키징한 구식hatchling이 자체 Trove classifier whitelist 를 유지하기 때문에 해당 hatchling release 이후 추가된 minor-version Python classifier 를 거부. minor-version classifier 는 cosmetic 이었고 실제 Python 버전 커버리지는requires-python과 CI matrix 에서 제공. 모든 hatchling release 가 받아들이는Programming Language :: Python :: 3 :: Only로 대체.
[0.5.2] - 2026-05-14
당일 install-path hot-fix 릴리스. 2025+ 인기 distro (Debian 13 Trixie, Ubuntu 24.10+) 에서 신규 설치를 차단하던 install.sh 회귀 두 건을 수정하고, Atomic Fedora 지원을 단일 트랜잭션 OBS-layered 설치 경로로 추가.
Highlights
install.sh가 Debian 13 / Ubuntu 24.10+ 에서 다시 동작 — 하드코드된freerdp2-x11의존성을 해당 distro 에서 해결할 수 없었음 (stock repo 에freerdp3-x11만 존재).pkg_name()의 Debian/Ubuntu 분기가 이제apt-cache show로 probe 해서 가능하면freerdp3-x11선택, 옛 시스템에서만freerdp2-x11로 fallback. 이전에는 Debian 13 / Ubuntu 24.10+ 신규 사용자가 설치 자체 불가. (reported by @basti189, #198)- Atomic Fedora 설치 경로 (Silverblue / Kinoite / Sericea / Bluefin / Bazzite) via OBS
rpm-ostree install --apply-live— 단일 트랜잭션, 패키지마다 재부팅하는 루프 없음. winpodx RPM 의Requires:가 FreeRDP / podman / python3 / tomli 를 transitively 끌어와서winpodx만 layer 하면 충분. 부팅된 deployment 가 라이브 적용을 거부하면 staged install + 재부팅 안내로 fallback. (by @Zeik0s, #163)
Added
- Atomic Fedora 설치 지원 (Silverblue / Kinoite / Sericea / Bluefin / Bazzite).
install.sh가rpm-ostree를 autodetect 하고 per-package dnf 루프 대신 단일 트랜잭션 OBS-layered 설치로 분기: 호스트의 FedoraVERSION_ID에 대한 OBS repo probe (현재 published: Fedora_42 / Fedora_43 / Fedora_44),/etc/yum.repos.d/에.repo파일 drop,rpm-ostree install --apply-live --idempotent winpodx로 부팅된 deployment 에 재부팅 없이 레이어링. 실행 중인 deployment 가 라이브 적용을 받지 못하는 경우 (kernel / init 건드리는 레이어 등)rpm-ostree install --idempotent winpodx로 staged + 재부팅 안내로 fallback. winpodx RPM 의Requires: freerdp >= 3.0+python3-tomli와Recommends: podman+python3-PySide6가 transitively 끌어옴 — 별도 의존성 리스트 유지 불필요. README "Supported distros" 표에 Atomic Fedora 행 + Fedora Atomic Desktops "Works on" 배지 추가;docs/INSTALL.md에install.sh없이 수동 설치 절차 문서화. 한국어 미러 동시 갱신. (by @Zeik0s, #163)
Fixed
install.sh가 Debian 13 (Trixie) 및 최신 Ubuntu (24.10 / 25.04 / 25.10) 에서freerdp2-x11 not found로 실패하던 문제 수정. 해당 배포판은 stock apt repo 에freerdp3-x11만 포함하고freerdp2-x11패키지 자체가 제거됨.pkg_name()의 Debian/Ubuntu 분기를apt-cache show로 probe 해서freerdp3-x11우선 선택, 오래된 시스템 (Debian <=12, Ubuntu 22.04 stock) 에서만freerdp2-x11로 fallback 하도록 변경. FreeRDP 3 이 어차피 권장 타겟 — v0.5.1 launcher 가 시작 시 major 버전 감지해서 어느 쪽이든 적절한/app:syntax 사용. (reported by @basti189, #198)
[0.5.1] - 2026-05-14
유지보수 + 사용성 릴리스. 큐레이트 Windows 에디션 확장, headless 설치 ergonomics, Ubuntu 22.04 LTS 사용자용 FreeRDP 2 호환 fix, 그리고 GUI 코드 베이스를 future-proof 하는 내부 Qt 리팩토링. v0.5.0 과 이 태그 사이에 9 PR 머지.
Highlights
LTSC IoT, Win10 LTSC, Tiny, Server, 또는 직접 만든 ISO 선택 가능 — 그리고 Ubuntu 22.04 의 기본 FreeRDP 2.11 에서도 winpodx app run <app> 작동. 이번 릴리스는 주로 v0.5.0 사용자 리포트 follow-up: 한 곳에서 더 많은 Windows 에디션 선택, install.sh 와 winpodx setup 의 정식 --win-version 플래그, FreeRDP 2.x 에서 Microsoft Store 가 뜨던 RemoteApp launch 버그 수정.
- Windows 에디션 picker (#178, #183, #185, #186) —
cfg.pod.win_version이 GUI 설정 → Container/VM 카드,winpodx setup --win-version,install.sh --win-version에서 전체 Win10+ 큐레이트 셋 (11, 10, ltsc11, ltsc10, iot11, tiny11, tiny10, 2025, 2022, 2019, 2016) 을 받습니다. 커스텀 ISO 는docs/ARCHITECTURE.md의 수동 워크어라운드로 문서화. (reported by @gabe39, #178) - FreeRDP 2 / Ubuntu 22.04 LTS 호환 (#189) —
winpodx app run <app>이 FreeRDP 2.11.x 에서 더 이상 Microsoft Store 를 띄우지 않음. Win32 RemoteApp 실행이 FreeRDP 3 의 조합 syntax/app:program:X,name:Y,cmd:Z대신 separate-flag 형식 (/app:+/app-name:+/app-cmd:) 사용. (reported by @poetman, #158) - ARM64 (aarch64) 호스트 지원 — Pi 5 / Ampere 시스템 정상 설치 (#176, #177). 호스트 아키텍처를 install / setup 단계에서 감지; aarch64 호스트는
dockurr/windows-arm이미지로 기본 라우팅, compose 템플릿이 x86 전용arch_capabilities=off서브옵션 없이-cpu host만 emit — 이전엔 QEMU 의host-arm-cpu머신 모델이Property 'host-arm-cpu.arch_capabilities' not found로 거부하면서 크래시. 신규 aarch64 CI job 이 회귀 감지. 남은 Phase 4 (Windows-on-ARM RDP listener 안정화) 는 #141 / #140 에서 추적. (reported by @tslpre, #140) - Discovery 정리 (#182) —
winpodx app refresh가 reverse-open Linux 앱 shim 을 Windows 앱으로 재임포트하지 않음. 다음 refresh 시 기존 오염된 항목 self-heal pass 로 자동 제거. - 주간 Docker Hub digest watcher (#180) — release-tag-only watcher 가 놓치는 silent dockur rebuild (태그 없는 보안 패치) 를 감지.
- 내부:
WinpodxWindowQt 클래스가 2745 줄에서 148 줄 orchestrator + 10 single-responsibility mixin 으로 분해 (#181). 동작 변경 0; 미래 Rust 포팅에 깔끔하게 매핑. - 항상 켜진 하단 로그바 + Live 로그 레벨 picker. 두 piece 가 함께 작동:
- 메인 윈도 맨 아래에 영구적 2-line 로그 ticker — 어떤 페이지를 보고 있든 표시됨. 최신 2개
log_signal라인 (winpodx logger +cfg.logging.level == "RAW"일 때 pod tail) 이 실시간으로 flash. - Terminal 탭에
DEBUG / INFO / WARNING / ERROR / CRITICAL / RAW드롭다운 — 실행 중인 logger 즉시 retarget +cfg.logging.level에 persist. RAW 는 평행podman logs -f스트림 ([pod]접두사) 도 활성화 — dockur / QEMU / Windows 측 메시지가 Terminal 풀뷰 + 하단바 둘 다 흐름. "Windows 안 부팅" / "ISO 다운로드 멈춤" / agent down 진단에 유용. - tail 들은 GUI 시작 시 한 번 켜져서 계속 실행 → 어떤 페이지든 하단바 갱신. Terminal 의
Live (app)/Live (pod)/Stop tail버튼은 제거됨 (always-on 디자인에서 잉여).
- 메인 윈도 맨 아래에 영구적 2-line 로그 ticker — 어떤 페이지를 보고 있든 표시됨. 최신 2개
- 컨테이너 재생성을 동반하는 설정 저장이 이제 전체 bring-up (Windows 부팅 대기 → 에이전트 settle → 런타임 fix 적용 → 앱 discovery → reverse-open 매니페스트 push) 을 진행 다이얼로그와 함께 자동 실행. "에디션 바꿨더니 앱이 안 떠요" 워크플로 종료.
Added
winpodx setup --win-version EDITION+install.sh --win-version VER플래그. 새WINPODX_WIN_VERSION환경변수.docs/INSTALL.md"Windows 에디션 선택" 섹션 참고. (#178, #185, #186)- GUI 설정 → Container/VM 카드에
_KNOWN_WIN_VERSIONS로 채워진 "Windows Edition" 콤보 추가. Read-only dropdown; 큐레이트 리스트 밖의 커스텀 dockur 태그는winpodx.toml직접 편집으로 설정 (docs/ARCHITECTURE.md"Advanced: Custom Windows ISO" 참고). (#178, #183) core/config.py의_KNOWN_WIN_VERSIONS상수 (12 큐레이트 에디션).PodConfig.__post_init__가 공백/case 정규화 + 모르는 값 WARN (reject 안 함). (#178, #183)- ARM64 (aarch64) Linux 호스트 지원 — #141 Phase 1+2+3.
install.sh와core/config.py:_default_pod_image가uname -m으로 호스트 아키텍처 감지 → aarch64 호스트는dockurr/windows-arm이미지로 라우팅 (dockur 프로젝트의 별도 ARM 빌드와 매칭).core/pod/compose.py가 aarch64 에서-cpu host의arch_capabilities=off서브옵션 제거 — 그건 x86 전용 KVM CPUID 하드닝이고 QEMUhost-arm-cpu머신 모델은Property 'host-arm-cpu.arch_capabilities' not found로 거부 (Pi 5 사용자가winpodx pod start에서 본 정확한 증상). 신규 aarch64 CI 매트릭스 (#177) 가ubuntu-24.04-arm에서 테스트 스위트 실행해서 아키텍처 인지 코드 경로 회귀 silent 차단. 남은 Phase 4 (Windows-on-ARM RDP listener 안정화,xtajitx86 에뮬레이션 위 rdprrap 호환성) 는 #141 + #140 에서 별도 추적. (reported by @tslpre, #140 — Pi 5 / RaspiOS Trixie repro + 전체 QEMU 에러) check-windows-updates워크플로우에:latestdigest drift 감지 추가 — release-tag bump 없이 digest 가 바뀌면 "silent rebuild" 추적 이슈 오픈.config/oem/VERSIONS.txt에 두 새 필드dockur-digest=/dockur-arm-digest=. (#180)docs/ARCHITECTURE.md의 새 "Advanced: Custom Windows ISO" 섹션 — 자체 ISO 사용자를 위한 수동win_version = "custom"+compose.yaml마운트 워크어라운드 문서화. 한국어 미러 갱신. (#178, #184)CONTRIBUTING.md"Crediting contributors in Highlights" 섹션 — CHANGELOG Highlights 의 inline(by @user, #PR)/(reported by @user, #issue)컨벤션 명시. 한국어 미러 갱신. (#187)- 새
[logging]config 섹션 +level필드 (DEBUG | INFO | WARNING | ERROR | CRITICAL | RAW).setup_logging()가 시작 시 읽음 — 모든 winpodx CLI / GUI 실행이 선택된 레벨 사용. 기본INFO; 모르는 값은 reject 없이INFOfallback. Terminal 탭 드롭다운이 live logger 변경 + TOML persist.RAW는 winpodx-only meta-level: Python logger 를DEBUG로 고정 + Terminal 에 평행podman logs -ftail 동시 시작 → dockur / QEMU / Windows 측 출력이 winpodx 로그와 같은 줄 흐름에 노출 (pod 로그는[pod]접두사 + 어둡게). - 새 License GUI 탭. MIT 라이선스 본문 + "Third-party components" 섹션 (winpodx 가 의존하거나 ship 하는 upstream 프로젝트 목록: dockur, FreeRDP, PySide6/Qt, rdprrap, stascorp/rdpwrap, rcedit, Catppuccin) 과 각자의 라이선스 명시. LICENSE 파일은
bundle_dir()로 해소해서 모든 설치 모드 (소스 체크아웃, wheel, FHS, installer 드롭) 에서 렌더링됨. - 컨테이너 재생성 후 auto bring-up. GUI 설정 페이지에서 CPU / RAM / 포트 / 사용자 / Windows 에디션 변경 시 이전에는
_generate_compose+_recreate_container만 호출하고 끝났습니다 — 새로 만든 게스트에는 부팅된 Windows (에디션이 바뀌면 새 ISO 다운로드부터), 에이전트, rdprrap 멀티세션 apply, 앱 discovery, reverse-open 매니페스트가 모두 없는 상태였고 사용자가 수동으로 여러 CLI 명령을 돌려야 했습니다. 이제 저장 핸들러가 워커 스레드에서 전체 bring-up 을 자동 체인합니다: (1)pod_status == RUNNING+check_rdp_port대기 (cfg.install.wait_ready_stage2_secs, 기본 900초), (2)AgentClient.health()+ host token ready 대기 (cfg.install.wait_ready_stage3_secs, 기본 1800초 — 새 ISO 다운로드 + Sysprep 시간이 길어서 큼), (3) rdprrap 활성화 + RDP 타임아웃 + OEM 베이스라인을 위한winpodx.core.provisioner.apply_windows_runtime_fixes, (4)discovery.scan+persist_discovered, (5) reverse-open 매니페스트용winpodx host-open refresh. 새BringUpProgressDialog(window-modal, 무한 진행률 바, Cancel 버튼) 가 각 phase 의 라벨 + sub-detail 을 표시; 동일한 라인이 기존log_signalfan-out 통해 always-on 하단 로그바에도 흐릅니다. Cancel 은 best-effort — phase 1/2 폴링 루프는 2초 사이클 안에서 이벤트 honour 하지만 phase 3-5 의 긴 blocking apply / discover / sync 호출은 도중 중단 불가 (버튼은 "Cancelling..." 텍스트로 disable 되어 요청이 등록됐음을 사용자에게 알림). 새BringUpMixin(src/winpodx/gui/_main_window_bringup.py),WinpodxWindow에 세 새 시그널 (bringup_phase,bringup_done,bringup_started), 그리고_main_window_settings.py의 기존_recreate워커 끝에서의 kick 으로 구현. +5 tests (test_main_window_bringup.py) — happy-path 순서, phase-1 cancel, phase-3 failure surfacing, non-blocking 엔트리, fresh-cancel-event-per-run 커버.
Changed
- Qt
WinpodxWindow클래스가 mixin 으로 분해.src/winpodx/gui/main_window.py가 2745 줄에서 148 줄로 (-95%); page builder, page behavior, chrome 구성, pod 제어, worker orchestration 이 각각 자신의 private_main_window_*.pymixin 모듈로 이동. 순수 구조 리팩토링 — 기능 변경 0, 모든 method resolution 이 MRO 통해 보존됨. (#181) core/discovery._is_junk_entry가C:\Users\Public\winpodx\reverse-open\bin\하위 실행파일 reject (새reverse_open.sync.is_guest_shim_pathhelper 통해 query) — 호스트의 reverse-open Linux 앱 shim 이 Windows 앱으로 잘못 임포트되지 않도록.persist_discovered()가_purge_reverse_open_entriesself-heal pass 실행해서 다음 refresh 시 기존 오염 항목 제거.scripts/windows/discover_apps.ps1의Add-Result가 게스트 측에서도 같은 경로 필터 — defense in depth. (#182)docs/USAGE.md의win_versionTOML 예시가 pre-#183 서브셋 대신 새 큐레이트 셋 반영. 한국어 미러 갱신. (#184)- License 탭 third-party acknowledgments 완성.
libvirt-python(LGPL-2.1+),docker/ docker-py (Apache-2.0),tomli(MIT), reverse-open shim 에 정적 링크되는getrandomRust crate (MIT OR Apache-2.0) 항목 추가. 선택 의존성인[libvirt]/[docker]extras 가 이제 upstream 라이선스 조건에 따라 LGPL / Apache attribution 을 GUI 에 노출; vendored.exe에 정적 링크된 reverse-open shim 의 dual-licensed Rust 의존성도 parity 차원에서 명기. - BringUpProgressDialog 디테일 강화. auto bring-up 다이얼로그가 이제 5-phase 체크리스트 (단계별 elapsed 타이머 포함), 폴링 단계 (Pod ready / Agent ready) 동안의 attempt 카운터, 그리고 dockur 컨테이너의
podman logs -f실시간 tail 을 펼침 가능한 영역으로 보여줍니다. 신규 에디션의 ISO 다운로드 + Sysprep 단계 (Phase 2 — 최대 30분+) 동안 Windows 가 실제로 무엇을 하고 있는지 사용자가 볼 수 있게 한 목적입니다. pod-log expander 는cfg.logging.level과 무관하게 라이브 tail 을 보여주기 위해 다이얼로그 lifetime 에 한정된 자체podman logs -fsubscription 을 spawn 하며, 항상 켜진 하단 로그바용 fan-out 으로WinpodxWindow.log_signal을 통해 흘러오는[pod]-prefix 라인도 같이 수신합니다.bringup_phase시그널의 첫 인자가 이제 안정적인 phase-ID slug (phase_1_pod/phase_2_agent/phase_3_fixes/phase_4_discovery/phase_5_refresh) — 표시 문구와 독립적으로 다이얼로그가 emission 을 올바른 체크리스트 행에 라우팅합니다. +6 dialog-side 테스트 추가.
Fixed
winpodx app run <slug>가 FreeRDP 2.x 호스트 (Ubuntu 22.04 LTS) 에서 Microsoft Store 를 띄우던 문제 + FreeRDP 3.x 호스트 (Tumbleweed, Fedora 42/43 의 FreeRDP 3 PPA 등) 에서Unexpected keyword로 실패하던 문제 둘 다 수정. Win32 RemoteApp 이 감지된 FreeRDP major 버전에 따라 분기: FreeRDP 3 은 조합 syntax/app:program:X,name:Y,cmd:Z유지 (FreeRDP 3 가/app:를<key>:<value>,...로 파싱해서 bare path 의C:drive prefix 를Unexpected keyword로 reject); FreeRDP 2 는 separate/app:PATH+/app-name:NAME+/app-cmd:CMDflag 형식 사용 (FreeRDP 2 가 조합 string 을 literal program path 로 파싱, launch 실패, Windows shell handler fallback 으로 Microsoft Store 띄움). 버전은xfreerdp --version으로 프로세스당 한 번 감지 후 캐시. UWP 경로는 변경 없음. (reported by @poetman, #158)src/winpodx/__init__.py의__version__이0.4.3으로 stale 상태였고 패키지 버전과 안 맞았음. 이번 릴리스에0.5.1로 bump;winpodx --version, migrate 버전 비교, 설치 마커가 이제pyproject.toml과 일치.- YAML 인젝션 hardening — compose 템플릿의 double-quoted scalar 에 들어가는 4개
[pod]config 필드. 손편집winpodx.toml의win_version = '11"\nEVIL: "x'(또는 같은 형태의container_name/image/disk_size) 이 YAML scalar 를 깨고 dockur 서비스에 임의 env key 주입 가능했음. 2-layer fix:core/config.py:PodConfig.__post_init__가 위험 문자 (",\\,\n,\r,$,`) reject +disk_sizeregex 검증;core/pod/compose.py:_build_compose_content가 4개 값 모두_yaml_escape통과 (defense in depth).cli/setup_cmd.py가--win-version적용 후__post_init__재실행 — direct 속성 변경으로 검증 우회 불가. 5개 새 테스트 추가. - License 탭 acknowledgment 표 정정. 스모크 + legal review 에서 3개 오류 발견:
rdprrap이 GPL-3.0 으로 표기됨 (실제 MIT, 같은 메인테이너);Catppuccin Mocha를 GUI palette 로 credit (실제 theme 은 GitHub Primer Dark —src/winpodx/gui/theme.py참고);PySide6라이선스 표기 부정확. Catppuccin 제거; rdprrap MIT 로 이동; GitHub Primer Dark 추가 (GitHub Inc. 2013 copyright); PySide6 정확하게LGPL-3.0-only WITH Qt-LGPL-exception-1.1. rdprrap NOTICE 의 추가 upstream 3개 (stascorp/rdpwrapApache-2.0,llccd/TermWrapMIT,llccd/RDPWrapOffsetFinderMIT) 도 GUI 에 노출. 선택 의존성 (Pillow, cairosvg, pyxdg) 도 각자 라이선스로 추가.
Changed
.gitignore확장 — CLAUDE.md Section 8 ("AI Tooling Footprint Ban") 에 따라.mcp.json,AGENTS.md,CLAUDE.local.md,demo.png추가 + 표준 scratch / editor / OS 패턴 + 방어적 secret 모양 denylist (*.env,*.pem,*.key,id_rsa*등 — 현재 트리에 해당 파일 없음).- Arch Linux 지원 상태 정정.
README.md"지원 distro" 표가pacman / AUR | Supported라고 표기했고,docs/INSTALL.md가yay -S winpodx안내했지만 — 둘 다 현재 사실이 아님. AUR publish 워크플로우는 준비되어 있지만AUR_SSH_PRIVATE_KEY시크릿이 프로비저닝되지 않아 secret-gated, 따라서winpodxAUR 패키지가 출시된 적 없음. README 행을 "소스 설치 지원 · AUR 패키지는 메인테이너 온보딩 대기" 로 변경,INSTALL.md"Arch Linux (AUR)" 섹션을install.sh안내로 교체, 네이티브 패키지 매니저 도입부 명확화. 한국어 미러도 함께 업데이트.
[0.5.0] - 2026-05-13
Reverse-open (#48) 이 end-to-end 출시 — 이 릴리스의 headline 기능. Linux 앱이 Windows guest 우클릭 "Open with…" 메뉴에 기본으로 노출되고, 짧은 메뉴 + 긴 "다른 앱 선택" 다이얼로그 양쪽에 앱별 아이콘이 정상 표시됨. Phase 2 시리즈 완료 (a / b / c / d) + 전체 fix-forward 스택: per-app .cmd wrapper, Rust .exe shim, 짧은 메뉴 가시성, Firefox handoff, uninstall --purge scope, multi-resolution ICO, Desktop 폴더 단축키 + Quick Access 핀, 그리고 embedded-EXE-icon chooser 수정.
Chooser 아이콘 문제에 대한 부연: Win10/Win11 Explorer 는 chooser entry 아이콘을 EXE 의 embedded PE resource 에서만 읽고, Applications\<exe>\DefaultIcon 도 per-slug ProgID 의 \DefaultIcon 도 무시함. PR #171 이 유일하게 동작하는 경로 — per-slug .ico 를 per-slug .exe 의 PE resource 에 직접 embed (vendored rcedit, electron/rcedit v2.0.0, MIT). PR #165 의 hard-link inode 공유 최적화는 의도적으로 폐기 (~500 KB × N 앱 디스크) — Win10/Win11 에서 chooser 아이콘과 inode 공유를 동시에 유지하는 경로는 없음.
Highlights
Reverse-open 이 end-to-end 출시 (#48). Linux 앱이 Windows 게스트 우클릭 "Open with…" 메뉴에 기본으로 노출, 양쪽 chooser surface 에서 앱별 정확한 아이콘. 선택 시 호스트 xdg-open 으로 파일 열기 round-trip.
- Reverse-open Phase 2 시리즈 완료 (#157, #159, #160, #161, #162) — host 측 discovery, listener daemon, Qt Settings 카드, guest handler + sync transport, default-on.
- Reverse-open fix-forward 스택 (#164–#171) 이 real-Windows smoke 거치고 — 최종 #171 의 vendored rcedit 통한 EXE embed 아이콘 (동작하는 chooser-icon fix) 으로 마무리.
- PR #165 의 hard-link inode 공유는 의도적으로 폐기 (~500 KB × N 앱 디스크); Win10/Win11 에서 chooser 아이콘과 공유 inode 를 동시 유지하는 경로 없음.
cfg.reverse_open.enabled기본값True; 끄려면winpodx host-open disable또는 GUI Settings 패널.
추가
-
Reverse-open Phase 2 chooser 아이콘 fix-forward (#164 → #171). Phase 2c registration 이 #161 에 들어갔는데 real-Windows smoke 에서 3가지 문제 발견 — EXE 경로 dedup 때문에 chooser 항목이 하나로 collapse, 짧은 메뉴에서 항목 누락, 항목이 generic .exe glyph 로 렌더링. fix-forward 스택:
- #171 — vendored rcedit 으로 per-slug
.exe에 per-slug 아이콘 embed. 각winpodx-<slug>.exe가 shim 의 독립 copy 가 되고 PE resource section 에 매칭되는<slug>.ico를 들고 있음. #170 의 ProgIDDefaultIconsurface 는 chooser 가 무시; embedded EXE 아이콘이 Win10/Win11 이 honour 하는 유일한 surface.rcedit.exe(1.36 MB) 는config/oem/reverse-open/shim/bin/에 vendor 되고 base64 sync payload 로 guest 에 push.register-apps.ps1가-RcEditExe파라미터 추가, 슬러그 staging 은Copy-Item+rcedit --set-icon.unregister-apps.ps1도 rcedit 청소 미러링. - #170 — Scaffolding (#171 로 대체됨). Per-slug ProgID
HKCU\Software\Classes\winpodx-<slug>추가 (긴 다이얼로그에서<ext>\OpenWithProgids\winpodx-<slug>value link 의 anchor), shell 아이콘 캐시 invalidate 위한SHChangeNotify(SHCNE_ASSOCCHANGED)P/Invoke, 그리고 listener spawn 사이트에slug=…+ 치환된argv=…캡쳐하는 1줄 INFO 로그 추가 (잘못 동작하는 spawn 의 사후 디버깅용). 이 PR 의 ProgIDDefaultIcon쓰기는 #171 에서 제거 (EXE 가 아이콘 담고 있어서 중복); ProgID 자체, SHChangeNotify 호출, spawn-argv 로그는 유지. - #169 — Desktop 폴더 단축키 + Quick Access 핀. Start Menu 의
Linux Apps폴더를 가리키는Desktop\Linux Apps.lnk폴더 단축키와Shell.Application"pintohome" verb 통한 Quick Access 핀. 두 surface 가 Win11 의 "다른 앱 선택 → 이 PC 에서 다른 앱 찾기" 파일 브라우즈 다이얼로그에 노출되어, 사용자가%APPDATA%\Microsoft\Windows\Start Menu\Programs\Linux Apps\까지 직접 네비게이션 안 해도 됨. - #168 — Multi-resolution ICO + 전체 freedesktop mimeapps walk. 아이콘 변환이 16/24/32/48/64/128/256 px 프레임을 담은 ICO 를 생성 — chooser, taskbar, 설정 > 기본 앱 모두 비흐릿한 이미지 선택. Discovery 가 freedesktop 스펙대로 모든
mimeapps.list를 walk (XDG_CONFIG_HOME → XDG_CONFIG_DIRS →$XDG_DATA_HOME/applications→$XDG_DATA_DIRS/applications), 사용자의 primarymimeapps.list뿐 아니라 — default-handler 감지가xdg-mime query default가 resolve 하는 것과 일치. - #167 —
uninstall.sh --purge가 guest reverse-open scrub 생략.--purge는 "host nuke, container 도 사라질 것" 케이스용 — pod 가 곧 삭제될 거면 guest 에 닿아unregister-apps.ps1돌리는 건 의미없고 느림. - #166 — 짧은 메뉴 가시성 + chooser 캐시 + Firefox handoff.
OpenWithList쓰기를 sub-key 로 수정 (value 가 아니라 — Windows 는OpenWithList의 value 들을 MRU 리스트로, sub-key 이름을 populating 리스트로 취급). 레지스트리 쓰기 뒤ie4uinit.exe -show호출로 logoff 없이 chooser 캐시 invalidate. Firefox 오픈 파일 handoff 를 전용 경로로 라우팅 — 다중 인스턴스 launch 가 기존 인스턴스로 정상 dispatch. - #165 — Rust
.exeshim + NTFS hard link. 기존 PowerShell/.cmd shim 을windows_subsystem = "windows"Rust 바이너리로 교체 — chooser 항목 launch 시 콘솔 창 flash 없음. Per-slug 항목은 원래 source shim 바이너리의 NTFS hard link 로 landing — 같은 inode, flat 디스크 footprint (이 최적화는 #171 에서 폐기, 위 참조). - #164 — 앱별
.cmdwrapper + Linux-default scope + Start Menu 단축키. 이전 ProgID 기반 등록은 Windows 가 EXE 경로로 dedupe 해서 여러 앱이 chooser 항목 하나로 collapse — 앱별.cmdshape 가 각 slug 에 distinct 경로 제공. 등록 scope 가 "사용자가 실제로 Linux default 로 설정한 앱" 으로 narrow (is_default_for사용) — 모든 MIME-handling 앱 등록은 chooser 를 flood.install.sh가 reverse-open sync 자동 trigger;uninstall.sh에host-open unregister-guest단계 포함. Start Menu 단축키는 모든 discovered Linux 앱에 생성 (default-handler 상태 무관) — non-default 앱이 "다른 앱 선택 → 이 PC 에서 다른 앱 찾기" 통해 도달 가능 유지.
- #171 — vendored rcedit 으로 per-slug
-
Reverse-open Phase 2c — guest 측 핸들러 + sync transport + default-on. #48 의 end-to-end 루프 완성: 사용자 opt-in 없이 Linux 앱이 Windows guest 의 우클릭 "Open with…" 메뉴에 등장.
cfg.reverse_open.enabled기본값 True. 3개 guest 측 스크립트 (config/oem/reverse-open/register-apps.ps1,unregister-apps.ps1,shim/winpodx-reverse-open-shim.ps1) + 새 host 측 push 모듈 (src/winpodx/reverse_open/sync.py). shim 은 apps manifest + ICO 와 함께 sync payload 에 base64 임베드되어 — 이 PR 이전에 만들어진 기존 pod 에서도 작동 (dockur 의 OEM 번들 스테이징에 의존하지 않음). sync transport 는 기존 bearer-auth/execendpoint 위에서 180초 budget 으로 동작, 성공 시cfg.reverse_open.last_synced_at영속.winpodx pod start가 host 측 listener daemon 을 자동 spawn (idempotent — 이미 돌면 no-op),winpodx pod stop이 pod 중지 전에 teardown.winpodx host-open refresh가 agent 도달 가능하면 guest 로 push 하고pushed N app(s) + M icon(s) → registered보고. PowerShell shim 은\\tsclient\home\.local\share\winpodx\reverse-open\incoming\에 atomic<uuid>.json.tmp쓰고<uuid>.json으로 rename — host listener 가 부분적 request 를 절대 픽업 안 함. guest 측 레지스트리 shape: 앱별HKCU\Software\Classes\winpodx-<slug>ProgID +shell\open\command의 shim 호출, 그리고 앱의 MIME 타입이 매핑되는 모든 확장자에 대한HKCU\Software\Classes\<ext>\OpenWithProgids\winpodx-<slug>(built-in curated 테이블이 top ~25 명확 매핑 커버, 나머지는 타입 문자열에서 per-MIME 추정). +13 tests (test_reverse_open_sync.py). reverse-open Phase 2 시리즈 완료, 남은 follow-up 은 shim 의 Go 포팅 (PowerShell 의 ~300-500 ms cold-start 보다 빠름; 추적 중이지만 차단 아님). References #48. -
Reverse-open Phase 2d — Qt GUI 의 Settings 페이지 카드. Phase 2a/2b 의 모든 CLI 기능을 GUI 로 노출 — enable 토글, allowlist + denylist (
+ Add/− Remove버튼이 CLI 와 같은validate_slug문법 통과), 라이브 daemon 상태 (running / pid, cached app 수, generated-at 타임스탬프), 4개 액션 버튼이 기존host_open핸들러로 디스패치 (refresh & sync,start daemon,stop daemon,refresh status). Widget 빌더 (winpodx.gui.reverse_open_panel.build_panel) 는 Qt 를 lazy import 해서, 단위 테스트 가능한 순수 Python 헬퍼 (PanelStatus,build_panel_status,validate_slug,add_slug,remove_slug,format_status_line) 가 headless 테스트 환경에서 동작. slug + list mutation 헬퍼는 CLI 의add/remove와 같은 semantics 라 slug 가 GUI 와winpodx host-open사이에서 round-trip. Settings 페이지가 RDP / Pod 카드 옆 컬럼 레이아웃에 패널을 wiring, 저장은 기존_save_settings흐름에 통합 — 별도 "Save" 버튼 없이 패널이 공유cfg.reverse_open에 쓰고 페이지 전체 save 가 원자적으로 영속. 패널 생성 실패는 Settings 페이지를 막지 않음 (logged + skipped) — 이 모듈의 Qt 회귀가 나머지 Settings 를 다운시킬 수 없음. +17 tests (test_reverse_open_panel.py). Phase 2c (Go shim + guestregister-apps.ps1/unregister-apps.ps1+ agent/reverse-open/syncendpoint) 만 남고 real-Windows smoke gate 에 묶여 있음. References #48. -
Reverse-open Phase 2b — inotify 대체 폴링 listener, daemon lifecycle, apps DB hot-reload, 신규
host-opensubcommand 4종. Phase 2a (PR #157) 가 apps.json manifest + 앱별.ico를 stage 했고, Phase 2b 는 guest 가 쓴 request 파일을 소비할 host 측 daemon 을 켬. 새 모듈: (1)reverse_open/apps_db.py—AppsDatabase+AppEntry(apps.json 에서 schema 검증 load),substitute_pathargv 빌더 (%f/%u/%F/%U첫 개만 치환, 나머지 drop, never re-shelled). (2)reverse_open/listener.py— 500 ms 폴링 루프, group/world-writable 디렉토리 또는 다른 uid 소유면 preflight 거부, 64 KB 크기 cap + 8단계 JSON 깊이 cap (파서 exhaustion 방어), Phase 1 의SeenUUIDs링 버퍼로 replay 방어, schema 검증 (version=1, slug-shapeapp, NUL-freepath,\\\\tsclient\\prefix), spawn 전safe_open_uncTOCTOU-safe path 해소, 200건 in-flight cap (오래된 것 우선), 300초 janitor 사이클, 9가지 카운터 stats. subprocess spawning 은 매개변수화되어 테스트가 fork 없이 capture 가능. (3)reverse_open/lifecycle.py— fork-fork-pipe daemon spawn (5초 ready sentinel),$XDG_RUNTIME_DIR/winpodx/reverse-open.pid에 atomic 0o600 pid 파일, SIGTERM/SIGINT graceful shutdown, SIGHUP 가 apps.json reload (그래서host-open refresh가 daemon 을 재시작 안 함), stale-pid 회복 (프로세스 사라졌는데 파일만 남으면 status 쿼리에서 정리), idempotentstart_listener, daemon body 에os._exit사용 (fork-without-exec parent stack 으로 SystemExit 누수 방지). 새winpodx host-open명령 4개:start-listener,stop-listener,daemon-status(모두--json지원), 그리고refresh가 daemon 동작 중이면 자동으로 SIGHUP 보내고 JSON 요약에daemon_reloaded보고. +60 tests acrosstest_reverse_open_apps_db.py(12),test_reverse_open_listener.py(27, Phase 1 의safe_open_unc를 통한 실제 fork-and-process 통합 테스트 포함),test_reverse_open_lifecycle.py(14, 실 daemon spawn 후 request 처리 round-trip 포함),test_cli_host_open.py(+5). Phase 2c (Go shim + guestregister-apps.ps1/unregister-apps.ps1+ agent/reverse-open/syncendpoint — real-Windows smoke gate 필요) 와 Phase 2d (GUI Settings card) 는 deferred 유지. References #48. -
Reverse-open Phase 2a — host 쪽
.desktopdiscovery, ICO 변환,winpodx host-openCLI. Phase 1 의 dormant foundations 를 host-only 사용 가능한 흐름으로 끌어올림. 새winpodx host-open refresh명령이xdg-open처럼$XDG_DATA_HOME/applications+$XDG_DATA_DIRS를 walk, 기존cfg.reverse_open의 allow / denylist 적용, freedesktop 아이콘을 16/24/32/48/64/128/256 px Windows.ico로 변환 (raster 는 Pillow, SVG 는 cairosvg → svglib fallback, 둘 다 없으면 generic placeholder),~/.local/share/winpodx/reverse-open/에apps.jsonmanifest 를 stage — Phase 2b 의 sync layer 가 guest 로 push 할 준비. Discovery 는 input 을 강하게 검증 —Exec=라인의 shell metacharacter 거부, Wine / winapps / winpodx wrapper 제거 (재귀 방어),Hidden/NoDisplay/OnlyShowIn/NotShowIn존중,TryExec=검증,mimeapps.list의 user default 표시. 그래서~/.local/share/applications/의 악성.desktop이 staged manifest 를 통해 Phase 2b 의 spawn 경로로 shell 명령을 smuggling 할 수 없음. CLI 는 또한status,list(live-scan 또는--cached),enable/disable,add/remove(allow / denylist 관리,--deny토글 + slug-shape 검증) 도 노출. 새 optional extrawinpodx[reverse-open]가Pillow,cairosvg,pyxdg를 가져옴; 없으면host-open refresh가 여전히 manifest 는 만들지만 앱당 placeholder ICO 를 쓰고 경고 로깅. +60 tests acrosstest_reverse_open_discovery.py(25),test_reverse_open_icons.py(14),test_cli_host_open.py(21). Phase 2b (inotify listener +lifecycledaemon + agent/reverse-open/syncendpoint), Phase 2c (Go shim + guest 쪽register-apps.ps1/unregister-apps.ps1), Phase 2d (GUI Settings card) 는 deferred. References #48.
[0.4.4] - 2026-05-10
패치 릴리스. v0.4.3 에서 의도치 않게 buffered 됐던 pod wait-ready --logs 실시간 출력 복구 + half-uninstalled 자동 복구 + cachyos rotation/sync-password agent-first 우선 + codec flag allowlist 정정 + --extra-args escape hatch + btrfs warning suppression + agent-first install / reverse-open 의 dormant Phase 1 foundations.
수정
-
install.sh의pod wait-ready --logs출력이 다시 실시간으로 흐름. v0.4.3 가 half-uninstalled-state 진단을 위해 wait-ready 호출을tee로 캡처하도록 바꿨는데, 의도치 않은 부작용으로 Python 의 stdout 이 line-buffered (terminal) 에서 4KB-block-buffered (pipe) 로 전환됨. 모든[1/3] container ...,[container] ...,[2/3] RDP,[3/3] activation라인이 buffer 에 누적됐다가 wait-ready 종료 시점에 한 번에 flush — 사용자는 ISO 다운로드 내내 아무 출력도 못 보다가 끝에서 한꺼번에 wall of text 받음.install.sh가 이제 wait-ready 호출 앞에PYTHONUNBUFFERED=1을 prepend, 파이프가 있어도 stdout 을 line-buffered 로 강제.tee캡처 파일 내용은 변하지 않아no such container구분은 그대로 작동. 2026-05-10 smoke test 중 보고됨. -
config 는 살아있는데 컨테이너만 사라진 half-uninstalled 상태가
winpodx setup --non-interactive(install.sh 가 호출) 에서 자동 복구됨. 이전에는 비-purgeuninstall.sh(winpodx 파일은 지우되 컨테이너는 남기는 모드) 후install.sh재실행 시Existing config found, skipping setup.만 찍고 곧바로pod wait-ready로 진행, 거기서Error: no container with name or ID "winpodx-windows" found: no such container로 폭사하는 흐름이었음 — 컨테이너도 함께 날아간 상태였기 때문 (사용자가 인터랙티브 모드에서 모르고--purge를 골랐거나, 외부 정리 작업이 winpodx 컨테이너까지 잡아간 경우). 유일한 복구 경로가 전체--purge재설치였는데, 사용자가 stack trace 만 보고 알아내야 할 일이 아님. setup 이 이제 non-interactive 의 existing-config 분기에서 missing-container 상태를 감지 —cfg.pod.backend in ("podman","docker")이고pod_status가 RUNNING/PAUSED/STARTING 이외를 반환하며<backend> ps -a직접 프로빙이cfg.pod.container_name을 찾지 못할 때 —ensure_ready(cfg)를 호출해서 기존compose.yaml로 컨테이너를 idempotent 하게 재생성.install.sh의pod wait-ready실패 경로도 stderr 를 구분: 캡처된 출력에no such container가 포함되면 이전의 generic 60-min-timeout 메시지 대신 "container is missing — likely from a partial uninstall" 힌트 + 정확한--purge복구 명령을 출력. -
winpodx rotate-password와winpodx pod sync-password가 in-guest agent 를 먼저 시도하고 실패 시에만 FreeRDP RemoteApp 으로 fallback. cachyos 에서 xfreerdp3 의 bidirectional drive redirect 가 winpodx 가run_in_windows용으로 쓰는 headless RAIL 세션에서 깨져 있음 — 스크립트는 guest 에서 실행되지만\tsclient\home\...\windows-exec\에 쓰인 결과 파일이 host 로 surface 안 됨. 결과적으로_change_windows_password(자동 rotation + 명시적rotate-password명령) 와sync-password복구 경로 양쪽 모두 45초 timeout 까지 block 후 channel failure 로 끝남. redirect 가 정상인 openSUSE Tumbleweed 에서는 같은 코드 경로가 잘 동작했기 때문에 오랫동안 놓쳤던 버그.sync-password가 둘 중 더 critical — rotation 이 이미 깨져서 cfg.rdp.password 가 Windows 와 sync 안 되었을 때 사용자가 마지막으로 쓰는 복구 명령인데, 그것마저 hang 되면 출구가 없음.core/rotation/_change_windows_password와cli/pod/_sync_password모두 이제dispatch(cfg, prefer="agent")를 먼저 호출 — agent 가 아직 설치 전이면 (TransportUnavailable) 이전과 동일하게 FreeRDP 경로로 fallback, agent auth 실패 (TransportAuthError) 는 fallback 없이 즉시 보고 (전송 채널 transient state 가 아니라 config drift 신호이므로). 부수 효과로, agent 가 reachable 일 때sync-password는 recovery password prompt 자체를 skip — agent 는 bearer-token 인증이라 사용자가 원래 install.bat 비밀번호를 잊어도 lock-out 안 됨. pre-rotation_ROTATION_PENDING_MARKERrecovery 계약은 그대로 — 어느 transport 가 돌았든 mid-rotation disconnect 면 trigger.docs/TRANSPORT_ABC.mdrule #6 ("NEVER use Transport for password rotation") 을 supersede; rule 의 두 원래 논거 — FreeRDP authenticate 에 OLD 비밀번호가 필요하다는 점, agent 프로세스 메모리에 NEW 비밀번호가 노출된다는 점 — 모두 더 이상 성립하지 않음 (AgentTransport 는 bearer token 인증; 두 transport 모두 guest 에서 PowerShell argv +net userargv 로 동등하게 새 비밀번호를 노출하며, 차이는 agent 경로의 in-memory HTTP buffer 1개 vs FreeRDP 경로의 on-disk script file 1개뿐). -
_BARE_FLAGSallowlist 에서 invalid 한 OPTIONAL-typed codec 플래그 제거 (+/-gfx-h264,+/-rfx,+/-nsc,+/-jpeg,+/-avc444). v0.4.3 스택 머지 직후 xiyeming 의 새 escape hatch 첫 테스트 (winpodx app run notepad-exe --extra-args="-gfx-h264") 가 FreeRDP 자체의 cmdline 파서에서[ERROR][com.winpr.commandline]: Failed at index 15: Unexpected keyword로 실패. 근본 원인: FreeRDP 3.x 가 플래그를COMMAND_LINE_VALUE_BOOL(진짜+/-name토글) 과COMMAND_LINE_VALUE_OPTIONAL(/name:value만 허용) 로 나누는데, 위 codec 플래그들은 모두 OPTIONAL — bare-toggle 문법은 처음부터 실패할 운명. 원래 allowlist 패치는 그걸 BOOL 처럼 허용해서 unsafe-looking 인풋이 winpodx 필터를 통과한 뒤 FreeRDP 가 거부하는 confusing log 만 남겼음. 이제 filter 레이어에서 명확한Blocked unsafe extra_flag경고와 함께 거부, GUI tooltip / CLI help 텍스트도 올바른 우회 syntax 안내:--extra-args="/gfx:RFX"(RemoteFX 강제 → H.264 협상 자체를 안 함; 기존/gfxvalue-regex 가 이미 통과시킴). 진짜 BOOL 토글 (+/-gfx-progressive,+/-gfx-thin-client,+/-gfx-small-cache,+/-wallpaper등) 은 기존대로 작동.
추가
-
Agent-first install Phase 1 foundations — host-side state model, config schema, redactor, JSON Schema, CLI stubs.
docs/design/AGENT_FIRST_INSTALL_DESIGN.md(PR #144) 에 기술된 agent-first install 아키텍처의 5단계 롤아웃 중 첫 번째. 모두 foundation 코드 —cfg.install.agent_first = False(기본) 인 한 동작 변경 0. 새 모듈: (1)core/install_state.py—GuestInstallStep/GuestInstallStatedataclass +fetch_install_state(cfg)가 agent 통해C:\winpodx\install-state\*.done마커를 읽고, agent unreachable 시$XDG_STATE_HOME/winpodx/last_install_state.jsoncache 로 fallback, never raises. (2)core/agent_install_state.py— atomic 마커 파일 primitives (atomic_write_marker,read_marker,list_completed_steps) + corruption-tolerantRetryCounter클래스 + install-failure redactor (redact_log_line/redact_payload) 가net user <user> <pw>argv,Authorization: Bearer ...헤더,password=/token=/apikey=쿼리 패턴, 40자 초과 base64 블롭을 디스크 저장 전에 제거하고write_install_failure가 사용. (3)core/config.py에InstallConfig추가 (agent_first,wait_ready_stage2_secs,wait_ready_stage3_secs,auto_resume,watchdog_max_respawns,watchdog_probe_debounce_count,watchdog_probe_debounce_secs) —__post_init__가 모든 숫자 범위를 clamp 하고 hand-edited TOML 의 잘못된 값을 raise 없이 안전한 기본값으로 coerce. (4)cli/pod_install_status.py+cli/pod_install_resume.py— argparse stub 을winpodx pod의 subcommand 라우터에 wiring, Phase 3 의 전체 flag surface (--json,--no-color,--logs,--non-interactivefor status;--non-interactive,--yes/-y,--forcefor resume) 노출, "Phase 3 implementation pending" 출력 후 0 종료. (5)docs/design/install_failure.schema.json— JSON Schema draft-07,additionalProperties: false, slug-shapefailed_step/error_class, capped string 길이, 50줄last_log_linescap. +95 새 tests acrosstest_install_config.py(17),test_install_state.py(22),test_agent_install_state.py(32 — redactor 의 round-trip invariant 를 검증하는 Hypothesis property test 포함: 임의의 입력 문자열에 대해 출력에 redaction 패턴 substring 이 결코 남지 않음),test_install_cli_stubs.py(23). 테스트 카운트: 937 passed, 1 skipped (이전 842/2). Phase 2 (install.bat 를 state machine 으로 재작성), Phase 3 (host-sidewait-ready5-stage +install-status/install-resume본격 구현 + GUI 통합), Phase 4 (agent_first = True로 default flip), Phase 5 (legacy 코드 경로 제거) 모두 deferred. -
Reverse-open Phase 1 foundations —
src/winpodx/reverse_open/모듈 스켈레톤 (paths, config, mime, seen_uuids).docs/design/REVERSE_OPEN_DESIGN.md(#48) 에 기술된 guest→host 파일 열기 경로의 4단계 롤아웃 중 첫 번째. 4개 foundation 모듈이 land 되며 기존 사용자에게는 동작 변경 0: (1)paths.py가 guest 의\\tsclient\...namespace UNC 경로를 설정된 share root 아래의 POSIX 경로로 변환하며, TOCTOU-safesafe_open_uncatomic context manager 포함 (보안 리뷰가 원래의 validate-then-open 2-함수 API 의 swap window — validate 호출과 open 호출 사이에 symlink 가 바꿔치기될 수 있는 — 을 catch 한 뒤 재작성됨; 새 형태는O_NOFOLLOW로 열고 fd 를 잡고 있는 동안 realpath 를 허용된 root 와 검증해서 attacker 가 race 할 window 가 없음); (2)config.py가ReverseOpenConfig스키마 정의 + dangerous-app denylist (cmd/powershell/wscript 등) 를 baked-in,cfg.reverse_open.enabled = False기본 — 이 PR 은 user-visible 동작 변경 0; (3)mime.py는 guest 가 가장 자주 돌려보낼 파일 타입을 커버하는 86-entry curated MIME→extension 매핑 + curated 테이블 밖은xdg.Mimeruntime fallback; (4)seen_uuids.py는 최근 처리된 request UUID 의 persistent ring buffer — replay 방어용으로 LRU eviction 포함 capped size + tmp+rename atomic write 라 mid-update crash 가 파일을 손상시키지 못함. 4개 새 테스트 모듈에 +154 tests,paths.translate_unc_to_posixtraversal invariant 를 다루는 hypothesis property test 포함 (랜덤 component 시퀀스 / 혼합 separator / encoded../ percent-encoded null byte 모두 허용된 root 안으로 resolve 되거나 raise). 테스트 카운트: 814 passed, 2 skipped (이전 660). Phase 2 (transport), Phase 3 (host dispatcher), Phase 4 (guest agent integration) 은 deferred. #48 참조. -
winpodx app run --extra-argsper-launch FreeRDP 인자 override + GUI Settings 의 Extra FreeRDP args 입력. 깨진 xfreerdp3 빌드 배포한 distro 들을 위한 진단/우회용 escape hatch. Per-launch--extra-args는cfg.rdp.extra_flags뒤에 append → 일회성 디버그 플래그가 글로벌 기본값을 이김; 양쪽 경로 모두 같은_filter_extra_flagsallowlist 통과해서 unsafe 인자 차단. GUI Settings 페이지에 "Extra FreeRDP args" 입력란이cfg.rdp.extra_flags에 바인딩 — placeholder + tooltip 으로 자주 쓰는 토글 안내. allowlist 자체도 codec/cache/RAIL 토글 pack 으로 확장 —+/-gfx-h264,+/-gfx-progressive,+/-gfx-thin-client,+/-gfx-small-cache,+/-nsc,+/-jpeg,+/-avc444,+/-wallpaper,+/-themes,+/-decorations,+/-grab-keyboard,+/-grab-mouse,+/-mouse-relative,+/-async-update,+/-async-channels,+/-auto-reconnect,+/-bitmap-cache,+/-offscreen-cache,+/-glyph-cache— 이전엔 allowlist 에 없어서 xfreerdp3 도달 전에 silently drop 됐음. 단기 핵심 use case:-gfx-h264가 cachyos 의 xfreerdp3 빌드 (WITH_VAAPI_H264_ENCODING=ON을 FreeRDP 자체가 experimental 로 표시) 에서 xiyeming 이 RemoteApp 의 RAIL post_connect 단계에서 silent fail 한 상황의 우회 — 이제 winpodx.toml 의extra_flags = "-gfx-h264"또는 GUI 에서 RemoteFX fallback 강제 가능,winpodx app run notepad-exe --extra-args="-gfx-h264"가 가설 확인하는 동안에도 사용 가능.
변경
winpodx pod wait-ready --logs출력에서 dockur 의 cosmetic "you are using the BTRFS filesystem for /storage" 경고 suppress. dockur 의proc.sh가 이 경고를df --output=fstype만 보고 모든 btrfs 호스트에 무조건 출력 — NoCoW (chattr +C) 적용 여부는 확인 안 함. v0.4.3 가 사용자별 bind-mount + 자동 chattr +C 마이그레이션 제공하므로, post-migration 호스트에서 이 경고는 false positive — 실제 fragmentation 신호 (COW (copy on write) is not disabled for disk image file ...) 는 이미 사라짐. 스트리밍 되는 로그 라인이cfg.pod.storage_path가 set 일 때(btrfs warning suppressed: NoCoW bind mount in use)로 재작성됨 — install.sh 출력이 더 이상 경고처럼 보이지 않음. 아직 named volume 쓰는 legacy 사용자 (storage_path = "") 는 원본 경고 그대로 보여서winpodx setup --migrate-storage안내가 유지됨.
[0.4.3] - 2026-05-06
btrfs Copy-on-Write fragmentation 을 정조준한 패치 릴리즈. 0.4.x 라인의 핵심 storage rework: 사용자별 bind mount + 자동 NoCoW 로 Windows VM 디스크 이미지의 매 write (pagefile / boot / swap 바이트 모두) 가 btrfs extent 를 새로 fork 하던 걸 차단. btrfs 호스트에서 이전엔 수분~수십분 걸려 300초 예산을 자주 timeout 시키던 pod recreate 가 ext4 처럼 30초에 끝남. @xiyeming 의 #121, #122 보고 (cachyos 환경의 #126 "Unable to open apps and desktop" 도 actual root cause 이 동일 — install.bat 가 btrfs CoW 로 인한 file ops 정체로 mid-stage 에서 죽고, agent 가 안 올라오니 host 의 모든 FreeRDP fallback 이 multi-session 으로 reset).
Highlights — 5개 PR 가 end-to-end 로 fix 를 넣음. 마이그레이션 설계가 처음엔 "chattr +C 를 podman graph root 전체에" (PR #124 — 머지 안 하고 close, 호스트의 모든 컨테이너/볼륨/이미지를 NoCoW 로 바꾸면 btrfs snapshot 쓰는 사용자가 깜짝 놀랄 수 있음), 그 다음 사용자별 bind mount 로 pivot 후 chattr 가 그 한 디렉토리에만 적용되도록 변경. 그 다음 4개 follow-up 패치는 스모크 테스트가 매번 새로운 silent failure 를 드러내면서 추가됨:
- PR #125 — bind mount +
chattr +C+ named-volume 마이그레이션 도구. Fresh install 은~/.local/share/winpodx/storage가 기본 (compose-up 전에 chattr 박혀있어서 dockur 의 첫 raw-disk write 가 NoCoW inherit). 기존 사용자는 named volume 그대로;winpodx setup --migrate-storage(또는winpodx migrate통한 자동 트리거) 가 Windows 재설치 없이 bind mount 로 이동. - PR #127 — compose-prefixed volume name resolution.
podman-compose up이 named volume 을 project name prefix 로 materialise (winpodx-data가 아니라winpodx_winpodx-data); 마이그레이션의 volume probe 가 prefixed 형식 먼저 시도 후 bare 로 fallback. - PR #128 — chattr +C 결과 surfacing + post-rsync NoCoW 검증. apply / already-off / failed 결과를
print()해서 사용자가 install.sh 도중 직접 확인; rsync 후 디렉토리 와 sample.img둘 다lsattr재확인 — chattr 가 0 반환했지만 flag 가 persist 안 된 silent-no-op 케이스 catch. - PR #129 — bind-mount target 이 이미 populated 인 상태에서 auto-migration 재실행 거부. install.sh 가 두 번째 실행되어 stale
cfg.pod.storage_path때문에_maybe_auto_migrate_storage가 64 GiB 를 already-migrated NoCoW 디스크 위에 fresh CoW 복사본으로 덮어쓰는 destructive 시나리오 방어. - PR #130 — fresh install 에서 chattr 가 절대 안 박히던 진짜 root cause.
plan_migration이 디렉토리mkdir전에detect_path_fs(target)호출. openSUSE Tumbleweed 에선findmnt --target <nonexistent_path>가rc=1+ 빈 stdout 반환 (docs 와 다르게) —detect_path_fs가"unknown"반환,chattr_will_runFalse 평가, chattr 분기 통째로 silent no-op, PR #128 의 surfacing print 도 분기 진입 안 해서 fire 안 함. 수정: findmnt 호출 전에 input path 에서 가장 가까운 존재하는 ancestor 까지 walk-up (최대 64 step;/는 항상 존재).
openSUSE Tumbleweed btrfs 에서 end-to-end 검증: clean wipe → v0.4.2 fresh install (named volume winpodx_winpodx-data 생성) → main 으로 upgrade → install.sh 가 Migrating storage... 와 OK: migrated 71 GiB 사이에 chattr +C applied to /home/.../winpodx/storage (NoCoW for new files) 출력; 이후 lsattr -d + lsattr data.img 둘 다 C flag 박힘, dockur 의 Warning: COW (copy on write) is not disabled for disk image file /storage/data.img 가 더이상 journal 에 안 나옴, named volume 제거됨, 다음 podman-compose up 이 bind mount 마운트하고 Windows 가 CoW warning 없이 부팅.
추가
- 사용자별 storage bind mount + btrfs 자동 NoCoW.
winpodx setup의 fresh install 이 이제 Windows VM 디스크 이미지를 legacywinpodx-datanamed volume 대신 사용자 소유 host 디렉터리 (~/.local/share/winpodx/storage기본) 에 bind mount. 그 경로가 btrfs 면 setup 이 compose-up 전 빈 디렉터리에chattr +C적용 — dockur 가 처음 Windows raw disk image 쓸 때 부모 디렉터리에서 NoCoW 상속해서 Copy-on-Write fragmentation 완전히 우회. flag 는 이 특정 디렉터리에만 영향; host 의 다른 podman 컨테이너/볼륨/이미지는 일체 안 건드림 (graph-root 전체에 적용하는 PR #124 설계는 의도적으로 폐기 — closed comment 참조). 기존 사용자는 named volume 그대로 유지 + 그 볼륨이 btrfs 면 마이그레이션 명령 안내 한 줄 표시. @xiyeming 의 #121, #122 보고. winpodx setup --migrate-storage. 기존winpodx-datanamed volume 을 per-user bind mount 경로로 이동 — Windows 재설치 없음. Pre-flight plan 이 source/target 경로, source 크기, target 파일시스템, chattr 실행 여부, target 빈 공간을 표시. 비용: NVMe 에서 ~5-10분 (rsync -aS~60 GiB). Windows 설치 보존 — Sysprep 재실행 없음, ISO 재다운로드 없음. Idempotent: pod stop → 빈 target 에 btrfschattr +C(복사되는 파일이 NoCoW 상속) → rsync →cfg.pod.storage_path저장 → compose 재생성 → 성공 시에만 old named volume 제거 → pod restart. copy 실패 시 source 볼륨 그대로 두고 target 만 wipe — 재시도 시 빈 상태에서 시작.--migrate-storage-target PATH로 destination override;--yes로 confirmation skip.- 업그레이드 시 자동 마이그레이션.
winpodx migrate(install.sh 가 매 업그레이드마다 호출) 가 같은 조건을 감지 —winpodx-datanamed volume + mountpoint 가 btrfs +cfg.pod.storage_path비어있음 — 시 자동 마이그레이션. 인터랙티브 실행은 confirmation prompt (default Yes); 비인터랙티브 실행 (install.sh post-upgrade 경로) 은 그냥 진행 — 이미 degraded 상태고 migrate 실행 자체가 fix 적용 목적이라. 실패 시 원본 named volume 보존되고 사용자에게winpodx setup --migrate-storage수동 재시도 안내. cfg.pod.storage_pathconfig 필드. 빈 문자열은 legacy named-volume 모드 유지; absolute path 면 그 경로로 host bind mount.Config.load가 필드 검증하고 잘못된 값은 빈 문자열로 fallback;_render_storage_blocks가 unsafe 값 (newline, quote, brace) 감지하면 named volume 으로 회귀 — hand-edit 한winpodx.toml이 compose YAML 깨뜨릴 수 없음.
수정
- compose-managed 설치에서 자동 마이그레이션이 silent skip 되던 버그.
storage_migration.named_volume_exists("podman", "winpodx-data")가podman-compose up으로 생성된 install 에서 항상 False — 실제 볼륨은winpodx_winpodx-data(compose 가name: "winpodx"프로젝트명으로 prefix).winpodx migrate의 auto-migrate 와winpodx setup의 Case-2 경고 모두 bare name 기준이라 모든 compose-managed btrfs install 을 "fresh-install" / "nothing to migrate" 로 잘못 분류 — 자동 마이그레이션이 몇 시간 동안 잠자고 있었음. kernalix7 가 openSUSE Tumbleweed 에서 catch (2026-05-06): named volume 이winpodx_winpodx-data에 있었고, auto-migrate hook 이 dormant 상태였음. 새resolve_named_volume(backend)가 prefixed 형식 먼저 probe 하고 bare 형식으로 fallback — compose-managed / hand-created 양쪽 다 정확히 resolve.MigrationPlan.source_volume이 resolved 이름을 끝까지 carry — migration 끝의volume rmstep 이 정확히 같은 볼륨을 target. detect_path_fs가findmnt호출 전에 존재하는 ancestor 까지 walk-up — 마침내 chattr +C silent skip 의 진짜 root cause 잡음. kernalix7 의 2026-05-06 세 번째 스모크 테스트 (clean wipe → v0.4.2 설치 → main upgrade) 가 드디어 "chattr +C 가 절대 안 박힘" 의 silent root cause 노출:plan_migration이 target 디렉토리 mkdir 되기 전에detect_path_fs(~/.local/share/winpodx/storage)호출. openSUSE Tumbleweed 의findmnt --target <nonexistent_path>가rc=1+ 빈 stdout 반환 (docs 는 nearest existing parent 로 fallback 한다고 하지만 실제론 그러지 않음) → helper 가"unknown"반환 →chattr_will_run = (target_fs == "btrfs")→ False 평가 →execute_migration의 chattr 분기 통째로 silent no-op → PR #128 의 post-rsync NoCoW 검증도 마찬가지로chattr_will_runFalse 라 nothing to flag → PR #128 의 surfacingprint()도 분기 진입 안 해서 절대 안 fire. 세 번 연속 마이그레이션이 CoW-fragmented 디스크 이미지를 만들면서 "OK: migrated N GiB" 로 success 보고, NoCoW 가 silent skip 됐다는 indicator 0. 수정: input path 에서 nearest existing ancestor 까지 최대 64 step walk-up (/에서 종료 — 항상 존재), 그 path 를 findmnt 에 넘김 → not-yet-mkdir 된 target 도 그것을 포함할 fs 로 정확히 resolve. fresh-install 마이그레이션 plan 의 btrfs target 에 대해chattr_will_run이 이제 True →chattr +Capply / 결과 inlineprint()/ post-rsynclsattr검증 셋 다 실제로 실행.- bind-mount target 이 이미 populated 인데도 auto-migration 이 재실행되며 64 GiB 를 silently overwrite 하던 버그. kernalix7 가 2026-05-06 두 번째 스모크 테스트에서 catch — PR #127 + #128 머지 후 install.sh 가 한 번 더 실행됐을 때
cfg.pod.storage_path가 비어있고 (근본 원인은 조사 중 — 가능성 하나: install.sh 의 git-update 단계에서 install dir 이 stale 한 채 남아PodConfigdataclass 가storage_path필드를 갖지 않은 상태였고,Config.load가 round-trip 중 값을 silently drop), 동시에podman-compose up이 named volumewinpodx_winpodx-data를 64 GiB raw disk 와 함께 재생성. auto-migration 이 두 번째rsync -aS64 GiB 를~/.local/share/winpodx/storage로 실행 — 이미 첫 번째 마이그레이션 결과물 (수동cp --reflink=never복구로chattr +C박힌data.img포함) 이 들어있던 곳을 silently overwrite. 두 번째 rsync 가 NoCoW 디스크를 CoW-fragmented 복사본으로 덮어써서 perf 효과 날림 + 첫 마이그레이션 이후 named volume 과 bind mount 사이에 갈라진 모든 변경사항도 손실. 새로운 방어:_maybe_auto_migrate_storage가 named-volume probe 이전에default_target_path()(~/.local/share/winpodx/storage) 부터 확인 — 존재하고 비어있지 않으면 hook 이 bail out 하면서 사용자에게storage_path를winpodx.toml에 복구하거나 populated dir 을 옆으로 옮긴 후 재시도하라고 안내. 디스크의 bind-mount 데이터가 source of truth — 한 번 마이그레이션 됐으면 재실행은 안전할 수 없음. chattr +C결과가 install.sh 에서 보이지 않고, post-migration 검증도 없던 버그. kernalix7 의 2026-05-06 openSUSE Tumbleweed 스모크 테스트가 auto-migration 성공 메시지 ("OK: migrated 64 GiB") 까진 정상이었는데, 직후lsattr -d로 확인하니 bind-mount 디렉토리에도 / 새로 rsync 된 64 GiBdata.img에도Cflag 가 안 박혀 있었음 — chattr 결과가log.warning으로만 흐르고 (install.sh foreground 출력에서 필터링됨), post-rsync sanity check 도 없어서 NoCoW 효과 0 인 채로 완료 메시지 출력.execute_migration이 이제 (a) chattr 결과 (disabled/already_off/failed) 를print()로도 출력 — 사용자가 inline 으로 NoCoW 적용 여부를 직접 확인 가능, (b) rsync 후is_cow_disabled를 target 디렉토리 + 그 안의 sample*.img파일에 대해 재실행 → 불일치 ("dir 엔 +C 인데 새 파일이 inherit 못함", "chattr 0 반환했는데 flag 가 persist 안 됨") 감지 시MigrationResult.detail에 warning + recovery 명령 (pod 정지 후cp --reflink=never로 해당 파일 재생성) 첨부.cp --reflink=never는 이미+C인 부모 디렉토리 안에서 새 파일로 복사 — 새 파일이 inception 부터 NoCoW + flag inherit. 64 GiB CoW 디스크 이미지를 Windows 재설치 없이 retroactively 고치는 유일하게 안전한 방법.
[0.4.2] - 2026-05-05
Patch release. 0.4.1 위에 작은 fix 두 개.
수정
wait-ready --logs가 dockur 컨테이너 내부 VNC URL 을 호스트 매핑 포트로 rewrite. dockur 가 컨테이너 안에서visit http://127.0.0.1:8006/출력 — 컨테이너 내부에선 8006 이 listen 포트 맞음. 호스트는 그 포트를cfg.pod.vnc_port(기본8007,core/pod/compose.py의127.0.0.1:{vnc_port}:8006스펙) 로 매핑하므로 사용자 터미널에 스트림된 URL 이 컨테이너 안에서만 reachable 한 포트를 가리키고 있었음._wait_ready의_drain이 이제 줄마다127.0.0.1:8006→127.0.0.1:{cfg.pod.vnc_port}치환 — 사용자가winpodx.toml에 설정한 실제 포트 사용 (커스텀 값도 그대로 반영). @xiyeming 보고. Closes #118.- Packaging 워크플로우가 create-release race 견딤.
debs-publish와rhel-publish가view || create패턴으로 release 보장하는데,v0.4.1태그 push 시 두 job 이 동시에 view-failed 분기 → 둘 다gh release create성공 (GitHub release-create POST 가 tag uniqueness 에 atomic 아님) → 같은 태그에 GitHub Release 두 개 생성 → 후속release.yml의softprops/action-gh-release가tag_name: already_exists로 fail 하면서 sdist/wheel attach 안 됨. create 를 idempotent 로: try create,already_exists에러 swallow, 그 후 view 로 reachable 확인. race 이긴 쪽이 생성, 진 쪽 create 는 no-op, 둘 다 asset upload 진행.
[0.4.1] - 2026-05-05
0.4.x 라인의 첫 stable 릴리스. 0.4.0rc1 (2026-05-05) 이 soak preview 였고, 0.4.1 은 동일 코드 경로 + smoke-test triage 중 land 된 polish. 기능적으론 0.4.0 의 안정성 + UX 집중의 연장선이며, 모두 fresh 사용자가 거치는 install / migrate 경로에 집중.
Highlights — agent never installs regression 완전 종결. kernalix7 가 ~3일 동안 openSUSE Tumbleweed 에서 fresh-install 실패 재현: setup.log 자체가 없음, C:\OEM\agent.ps1 복사 안 됨, qwinsta 에 disconnected User session 누적 — agent 안 떠서 메뉴 안 채워짐. 3개 독립 실패 모드가 같은 증상으로 수렴: (1) install.bat 첫 PowerShell 엔진 로드 시점에 Windows Defender 실시간 스캔이 C:\OEM\ 의 파일을 lock (PS Expand-Archive 가 rdprrap zip 에서 deadlock); (2) agent 안 떠 있을 때 host-side migrate apply chain 이 FreeRDP RemoteApp 으로 fallback 해서 Windows session-takeover 통해 install.bat autologon session kick; (3) apply chain 직후 transient agent-respawn window 가 inline app refresh 와 매번 race. 셋 다 fix, 어느 한 layer regress 해도 deadlock 안 돌아오게 다층 방어.
openSUSE Tumbleweed 에서 end-to-end smoke 통과: install.bat 깔끔 완료, agent transport 통한 Password sync OK (ERRINFO_LOGOFF_BY_USER cascade 없음), agent transport 통한 apply chain, discovery inline 완료 (Discovered N app(s) + retry-on-respawn-race) — install.sh exit 전 메뉴 채워짐. v0.3.0-RTM1 에서 업그레이드도 동일: image-pin 마이그레이션, agent 통해 apply chain 성공, interactive discovery prompt 가 메뉴 inline 채움.
변경
- Release 트리거가
*RTM*glob 에서 별도REL-마커 태그로 전환. "RTM" 접미사가 원래 release 마커였지만 (v0.3.0-RTM1) 프로젝트가 PEP 440 / SemVer-hyphen 버전 (0.4.0,0.4.0rc1) 으로 이동하면서 RTM-suffix 태그 ship 안 함. 마커를 버전 태그 안에 섞으면 (v0.4.0-REL같이) PEP 440 valid 아니라 버전 체계 오염 — 새 디자인은 둘을 분리: 버전 태그는 깨끗 (v0.4.0,v0.4.0rc1), 같은 commit 의 두 번째 태그 (REL-v0.4.0,REL-v0.4.0rc1) 가 release-trigger 마커.release.yml이REL-*매치하고 prefix 떼고 버전 추출 — public Release titlev0.4.0, CHANGELOG lookup## [0.4.0]정확 매치. Prerelease 감지는 PEP 440 + SemVer 둘 다. 사용 흐름:git tag v0.4.1 && git tag REL-v0.4.1 && git push origin v0.4.1 REL-v0.4.1. plain version tag 는 4개 packaging 워크플로우 트리거; REL 마커는 GitHub Release 페이지 전용 opt-in. 기존 v0.3.0-RTM1 release 들은 그대로. - Release notes 가 미공개 중간 섹션 자동 포함.
v0.4.0rc1release 페이지가 처음엔 @Mic92 의 모든 contribution (PR #62-#65) 를 놓침 —## [0.3.1]섹션에 credit 있었는데 v0.3.1 이 CHANGELOG 에만 있고 태그/release 안 됐음. 워크플로우가 이제 실제 git tag 가 있는 가장 최근 CHANGELOG 섹션 (= published release) 을 찾아서 현재 섹션부터 그 이전 published 섹션 직전까지 emit — 미공개 중간 섹션의 credit 도 자동으로 다음 release 페이지에 노출. v0.4.0rc1 페이지는gh release edit로 수동 backfill.
수정
- install.bat 가 first boot PS Expand-Archive 에서 hang 안 함 (
agent never installs진짜 root cause).Add-MpPreference -ExclusionPath C:\OEM,C:\winpodx -ExclusionProcess rdprrap-installer.exe가 install.bat 의 가장 첫 단계 — staging dir 안 어떤 파일도 실시간 스캔 안 됨.PS Expand-Archive를 Windows 내장tar -xf로 교체 — PowerShell engine 우회로 PS-script 분석 레이어가 추출 가로챌 수 없음 (다른 보안 도구 추가돼도 동일 deadlock 안 일어남)._probe_password_sync가WINPODX_REQUIRE_AGENT=1받아들여서 agent down 일 땐 probe skip — PR #104 가 빠뜨린 마지막 host-side FreeRDP 경로. v0.3.0 (마지막 정상 baseline) 과 diff: install.bat 코드 거의 동일, rdprrap zip blob hash 동일; 변한 건 OEM bundle 6 → 13+ 파일 + dockur image pin 이 더 엄격한 Defender 정책의 새 Windows 11 빌드 — 조합이 deadlock 트리거. OEM bundle 22 → 23. - Fresh install 에서 host-side FreeRDP fallback 이 install.bat autologon session 을 더 이상 kick 안 함.
_apply_runtime_fixes_to_existing_guest가 apply chain 호출 전 agent /health 응답 요구 — 응답 없으면 skip. fresh install 에선 OEM bundle 이 이미 같은 state 적용했으니 no-op.discover_apps가WINPODX_REQUIRE_AGENT=1보면 FreeRDP fallback 대신agent_unavailableraise.wait_for_windows_responsive가 agent /health 예산 60s → 180s. PR #82 (release/0.3.1) 의 gate 제거가 들어온 regression — 0.3.1 이전엔 chain 이 0.1.x → 0.1.9 boundary 에서만 돌아서 race 가 안 보였음. - install.bat 가 rdprrap TermService cycle 중간에 죽지 않음. dockur autologon 흐름에서 Sysprep first-logon User session 자체가 TermService 통해 관리됨 —
rdprrap-activate.ps1끝의 cycle 이 install.bat session kill. cycle 을 OEM 모드에서 SKIP, install.bat 가 cycle 을 마지막 action 으로 (launchers,HKCU\Run, agent spawn,setup_done.txt모두 commit 후). cycle 이 죽여도 setup 작업 다 디스크에 있고, autologon retry 가 새 session +termwrap.dll로드, agent 깔끔히 시작. OEM bundle 21 → 22. app refresh와 migrate 의 인터랙티브 discovery prompt 가 agent-respawn race 에서 retry._apply_vbs_launchers끝에agent-respawn.ps1detached spawn — ~3s 후 기존 agent kill, 새 agent 시작. 그 윈도우와 겹치면/exec가Remote end closed connection without response로 실패. 두 호출 지점 모두 3회 retry (10s spacing);install.shexit 전 메뉴 채워지고 migrate 인터랙티브 경로도 inline 완료.- install.sh 가 migrate 와 app refresh 사이에 agent settle 대기. 두 step 사이
curl /healthpoll — agent 가_apply_multi_session의 TermService cycle 후 돌아올 시간 (최대 60s) 확보. retry 와 합쳐 메뉴가 안정적으로 inline 채워짐. setup.log가 redirected PowerShell block 안에서Add-Content로 self-lock 안 함. stderr redirect 잡힌 핸들에Add-Content가 sharing violation → exception 이 같은 redirect 통해 로그됨, 루프. 안쪽Add-Content모두Write-Output으로 교체, cmd-level redirect 도2>>→>>"%SETUP_LOG%" 2>&1.- Info 화면이 fresh-install warm-up 중 "Overall: FAIL" 안 깜빡임.
probe_agent_health/probe_guest_exec/probe_guest_summary의 timeout 모양 에러는 이제WARN("agent warming up or busy")반환.run_all()이 dependency-aware skip —agent_health가 OK 아니면 downstream probe 들이status="skip". 단일 warm-up 신호, cascade FAIL 폭주 없음. - AgentTransport 선택이 host-side token 가용성 존중.
/health는 unauthenticated 이지만/exec는 token 필요 — token 파일 race 시dispatch()가 green/health보고 agent 선택 후 첫/exec에서 token-missing.AgentClient.auth_ready()가(bool, detail)반환,AgentTransport.health()가 이거 확인 — token 안 ready 면available=False. 기본 dispatch 가FreerdpTransport로 fallback. token 자체는 노출 안 됨. - OEM bind mount 가 bundle 이 user-writable 일 때 복사 안 함. PR #95 가 항상 user-owned path 로 복사하게 만든 게 majority case (curl install / source / Nix) 에서
cp: Permission denied일으킴.os.access(bundle_oem, R_OK | W_OK)분기 — 사용자 소유면 그대로 반환, 아니면 user-space 복사 + 명시적chmod. - Defense-in-depth hardening 일괄. Config TOML load 시 재검증;
_ensure_config()가 password + timestamp 자동 생성;PasswordFilter가 FreeRDP/p:도 redact; rotation 이generate_compose→cfg.save순서로 half-state 방지.install.batASCII-only + tar 절대경로.tests/test_oem_install_bat.py가 정적 invariant 검증.
추가
- README 에 Star History chart (light/dark
<picture>). 클릭하면 star-history.com 인터랙티브 차트.
Contributors
Thanks to @Mic92 (Nix flake packaging, default-image switch, FreeRDP argv[0] match, Wayland XWayland gate — PRs #62-#65) and @pgarciaq (SELinux OEM bind mount fix — PR #95).
[0.4.0] - 2026-05-03
설치 / 마이그레이션 경로의 안정성 + UX 에 집중한 주요 릴리스. dockur 의 :latest push cadence 가 더 이상 컨테이너 재생성을 트리거하지 않음 (이미지 SHA pinned). 앱 launch 와 agent autostart 에서 PowerShell 콘솔 깜빡임이 모두 제거됨. fresh install 이 Windows 준비될 때까지 정직하게 대기. multi-session 활성화가 apply-fixes / migrate 로 hands-free. SELinux-enforcing 시스템 (Fedora) 가 out of the box 동작. RTM-suffix pod (0.3.0-RTM1) 정상 마이그레이션. winpodx app refresh discovery race 가 3계층으로 차단. Contributing 정책 + 라이프사이클 문서 추가.
수정
- Discovery 에러 분류 + GUI 다이얼로그가 더 이상 session-disconnect 실패를 "Pod Not Running" 으로 잘못 표시 안 함. 이 fix 이전엔 분류기가
"no result file"또는"auth"포함 메시지를 무조건DiscoveryError(kind="pod_not_running")로 매핑. pod 가 멀쩡히 살아있는 session-side 에러도 같이 잡혀버림 -- 구체적으로ERRINFO_LOGOFF_BY_USER(0x0001000C) 와ERRINFO_RPC_INITIATED_DISCONNECT(0x00010001). 둘 다 outer 메시지가 "no result file written" 인데 (스크립트가 다 못 쓰고 끝나니까), 의미는 게스트가 FreeRDP RemoteApp 세션을 mid-call 종료 -- multi-session 활성화의 TermService cycle 직후 흔함. GUI 가 살아있는 pod 에 "Start Pod" 버튼 띄움 (kernalix7 2026-05-03 fresh install 직후 발생). 수정:_classify_channel_error()가 3개 상태로 명시적 구분 -- transport-layer 실패 (connection refused / connect_transport_failed / agent unavailable) →pod_not_running; session-layer disconnect (LOGOFF / RPC_INITIATED_DISCONNECT) → 새session_disconnectedkind; 그 외 →script_failed. GUI 핸들러가 새 kind 에 대해 "Discovery Session Disconnected" 다이얼로그 + Retry 버튼 (잘못된 "Pod Not Running" / "Start Pod" 안내 대신). winpodx pod start가 Fedora / SELinux-enforcing 환경에서 더 이상lsetxattr: operation not permitted로 실패 안 함. 3개 버그 체인: (1)bundle_dir()의 marker check (scripts/,config/,data/) 가any()사용 — 셋 중 하나만 있어도 winpodx 번들로 인식, RPM 제거 후 남은/usr/share/winpodx/config/skeleton (agent_token.txt가 RPM 트래킹 안 돼서 잔존) 이 curl-only 설치에서도 path resolution 가로챔. (2)_find_oem_dir()가 번들 path 직접 반환; RPM/wheel 설치 시엔/usr/share/winpodx/config/oem/(root-owned), compose template 가:Z로 SELinux relabel 마운트 → rootless Podman 이 root-owned 파일 relabel 못해서lsetxattr: operation not permitted실패. (3)stage_token_to_oem()이agent_token.txt를 번들 OEM 디렉터리에 작성 → RPM 설치에서 runtime token 이/usr/share/winpodx/아래로 (RPM untracked,rpm -e후 잔존) → 버그 1 로 피드백. 수정:_find_oem_dir()가 번들 OEM 트리를~/.config/winpodx/oem/(user-owned,:Z-relabel 가능) 으로 복사 후 그 path 반환; agent token 도 같은 indirection 통해 user 영역에 작성.bundle_dir()marker check 를any()→all()로 변경 → 부분 leftover 가 더 이상 매치 안 함. (Thanks @pgarciaq — PR #95, fixes #93.)- install.bat 가 끝에서 agent 를 직접 시작 -- HKCU\Run 미래 등록만 하지 않음. HKCU\Run 은 user logon 당 1회 발사. autologon User 세션은 install.bat (FirstLogonCommands) 가 실행될 때 이미 logon 완료된 상태 -- 그래서 우리가 HKCU\Run 에 등록한 건 다음 세션의 agent 만 셋업. install.bat 가 도는 현재 세션엔 agent 없음, 사용자 (또는 호스트 RDP probe) 가 새 세션 열기 전엔. install.sh wait-ready phase 3 가 매 fresh install 마다 /health 에서 timeout (kernalix7 가
OK Windows ready (17:57)+ agent-missing 경고 보고, 이후 migrate apply 체인이 FreeRDP rc=12 LOGOFF_BY_USER + rc=1 RPC_INITIATED_DISCONNECT 로 실패 -- agent 없는 Windows 에 명령 시도한 정확히 그 패턴). 수정: install.bat 끝에 PowerShellStart-Process블록이 현재 세션에 agent spawn (wscript.exe + hidden-launcher.vbs경유, wrapper 없으면 직접 PS fallback -- HKCU\Run 등록과 동일 setup.log 진단). install.bat 끝나는 시점에 agent /health up -> phase 3 가 몇 초 안에 성공 -> migrate apply 체인이 healthy agent 에 실행 -> FreeRDP-fallback cascade 없음. HKCU\Run 등록은 그대로 남음 -> 미래 user logon (호스트 재시작 후 RDP 재접속, multi-session 앱 launch) 에서도 agent 발사. OEM 번들 20 -> 21. check_rdp_port가 단순 TCP-accept 대신 실제 RDP 핸드셰이크 수행. dockur 의 QEMU slirp 가 호스트의 매핑된 RDP 포트로의 TCP 포워드를 QEMU 프로세스 시작 즉시 accept -- Windows 게스트의 TermService 가 올라오기 한참 전에. 이 fix 이전엔 helper 가socket.create_connection만 함 -- 그래서 fresh install 의 ISO 다운로드 중간 (또는 QEMU 부팅했지만 Windows 가 RDP 리스너까지 못 온 어떤 pod) 이면 컨테이너 시작 즉시 "RDP port open" 보고. install.shwait-ready가 phase 2 를 0초에 통과, phase 3 (PR #91 이후 /health miss 시 warning + True 반환) 도 통과, 그리고 migrate 의 apply 체인을 Windows 없는 상태에서 실행 -- FreeRDP rc=147 / "Connection reset by peer" 폭격으로 표면화. kernalix7 2026-05-02 21:53 fresh install 에서 [container] 로그가 Windows ISO 7% 다운로드 중인데 호스트는 이미 다음 단계로 진행한 것 보고. 수정:check_rdp_port가 이제 minimal X.224 Connection Request (TPKT-wrapped) 보내고 2바이트 읽음. 진짜 RDP 서버 = TPKT 응답 (first byte0x03, second byte0x00); QEMU-forwarding-with-no-guest = slirp TCP 스택의 SYN-ACK 받지만 recv 가 timeout / EOF 반환. ~1초 안에 구분. 새 helpercheck_tcp_port가 옛 TCP-accept-only 동작 노출 -- 이게 정당하게 필요한 한 곳 (recover_rdp_if_needed안의 VNC liveness 체크 -- VNC 는 RDP 안 쓰니까 X.224 probe 가 자연스럽게 fail) 위해.wait_for_windows_responsive가 더 이상 agent /health 안 떠도install.shphase 3 에서 deadlock 안 함. 이전엔 RDP 포트 열림 AND agent.ps1 /health 응답 둘 다 있어야 return True. agent 가 안 뜨는 어떤 경로 (HKCU\Run 등록 오류, autologon 중간, agent token 불일치, port-mapping blip, install.bat staging 실패) 든 helper 가 caller timeout 전체 (install.sh의winpodx pod wait-ready --timeout 3600= 3600초) spin. kernalix7 이 2026-05-02 fresh install 에서 VNC 로 데스크톱은 보이는데[3/3] Waiting for Windows activation...에서 30+분 멈춤 보고. 이제: stage 1 (RDP 포트) 는 필수 그대로; stage 2 (agent /health) 는 best-effort,min(timeout, 60s)로 cap. /health 응답 = True (agent 경로 live). /health 응답 X = True 반환 (Windows 자체는 응답 — host code 가 FreeRDP RemoteApp 으로transport.dispatch폴백) + warning 로그 (C:\winpodx\setup.log진단 안내). RDP 포트 자체 안 열린 경우만 False. 다른 코드 경로의 agent-first 선호는 동일, boot probe 에서만 더 이상 block 안 함.- install.bat staging 에 per-file 검증 + structured fallback 추가 — "Cannot find script file" wscript 다이얼로그가 fresh install 막는 일 차단. OEM v20 이전 install.bat 가 5개 launcher 파일을
C:\Users\Public\winpodx\launchers\로 복사할 때2>nul로 에러 무시 +HKCU\Run\WinpodxAgent/WinpodxMedia를reg add로 무조건 그 경로 가리키게 등록. copy 가 silent fail (Sysprep 중 네트워크 share blip, AV 간섭 등) 하면 registry 가 존재하지 않는 파일 가리킴; 다음 user logon 때 wscript.exe 가 "Cannot find script file" 다이얼로그 띄우고 세션 무기한 block (kernalix7 2026-05-02 ~19:58 fresh install 에서 hit).reg add자체도 취약: cmd 파싱 시/d "..."안의\"escaped quotes\"가 살아남지만 reg.exe 가 literal backslash-quote pair 로 저장, logon 시 CommandLineToArgvW 가 실제 quoted argument 와 다르게 평가. 3방향 수정: (1) per-file 복사 검증 — 각copy /Y다음에if exist체크 +C:\winpodx\setup.log에 결과 기록 (파일별launcher OK:/launcher FAILED:). (2) HKCU\Run 등록을 PowerShellSet-ItemProperty한 블록으로 이동 — 깨끗한 .NET 문자열 저장, cmd-quoting 지옥 없음. (3) 존재성 기반 fallback —hidden-launcher.vbs가 copy 안 살아남으면 (LAUNCHERS_OK 미설정), HKCU\Run 을 legacy 직접powershell.exe -WindowStyle Hidden -File ...형태로 등록. 그 fallback 은 짧은 PS 콘솔 (~50ms) flash 있지만 agent 는 시작됨; 없이는 사용자가 다이얼로그 막혀서 데스크톱 자체를 못 봄. 동일 setup.log 가 어느 경로 선택됐는지도 기록 → apply-fixes 가 나중에 probe 가능. OEM 번들 19 → 20. - VBS 파일 ASCII-only — 영어 외 Windows 에서 Windows Script Host 가 더 이상 파싱 실패 안 함.
config/oem/hidden-launcher.vbs와config/oem/launch_uwp.vbs의 선두 주석에 em-dash 문자 있었음. wscript.exe 가.vbs파일을 시스템 default codepage (한국어 Windows 면 CP-949) 로 읽음. em-dash UTF-8 바이트 시퀀스 (0xE2 0x80 0x94) 가 multi-byte 시퀀스로 디코드되며 statement 중간에서 끝남 -> HKCU\Run 발사 시점에 "Windows Script Host" 에러 다이얼로그 -> Sysprep 에서 install hang. PR #88 이.ps1에 대해 잡은 것과 동일한 root cause; 이 PR 이.vbs까지 ASCII-ify 해서 audit 완료. 전체 스크립트 파일 (PS 5개 + VBS 2개) 모두 strict ASCII. agent-respawn.ps1의 legacy fallback 제거. 옛 코드가Test-Path hidden-launcher.vbs체크 + else-branch 가Start-Process powershell.exe -WindowStyle Hidden -File agent.ps1(PR #58 가HKCU\Run\WinpodxAgent에서 고친 것과 동일한 broken 패턴) fallback. OEM v13 이후엔 wrapper 가 항상 staging (install.bat +_apply_vbs_launchers둘 다 push) 되므로 fallback 은 파일시스템 손상 / 수동 삭제 케이스에만 발사 — 그런데 발사하면 매 apply-fixes 사이클마다 console flash. wrapper 없으면exit 1로 교체: 다음 user logon 때 HKCU\Run 재발사 (wscript+hidden-launcher.vbs 경유) 로 agent 복구, flash 없음. flash 발생 경로 모두 차단.- **rdprrap 이 이미 patched 됐는데
- 첫 부팅 앱 discovery 가 일관적 — "지난 설치엔 메뉴 떴는데 이번 설치엔 UWP 빠짐" 같은 stochastic 동작 사라짐. install.sh 시점
winpodx app refresh가 들쭉날쭉했던 race 조건 3개가 독립적으로 발사돼서, 셋 중 하나만 걸려도 빈/부분 결과: (1) Sysprep 직후 AppX deployment 가 아직 진행 중이라Get-AppxPackage가 부분만 반환; (2) Start Menu indexer 가.lnk들 propagation 중; (3) install.sh 가 app refresh 직전에 migrate 돌리는데 (multi-session 활성화 TermService cycle 트리거 가능) agent 가 mid-respawn 상태에서 discovery/exec발사. 3-계층으로 race 자체 제거:- 게스트 readiness gate (
scripts/windows/discover_apps.ps1):AppXSvc.Status -eq 'Running'AND ProgramData Start Menu.lnkcount > 0 이 1초 간격 3샘플 연속 안정될 때까지 대기 (StartPending → Running blip 잡음), 최대 60초 budget. "stable, proceeding" / "budget exceeded" 로그를 stderr 로 출력해서 게스트 타이밍을 apply-fixes / refresh 출력에서 볼 수 있음. - 호스트 transport readiness (
core.discovery.discover_apps): 스크립트 호출 전 agent/health+ RDP 포트 응답을 최대 30초 polling. 이거 없으면 multi-session 활성화로 갓 죽인 agent 한테 discovery 발사 → channel-failure cascade 로 멀쩡한 pod 에서 실패. - 호스트 retry-on-empty: 첫 pass 가 suspiciously empty (< 5 total OR 0 UWP — stock Win11 에선 둘 다 불가능) 면 8초 대기 후 1회 재시도. 큰 결과 선택해서 retry 가 regression 안 됨.
- Default discovery timeout 120초 → 180초 로 bump (새 readiness gate 흡수). Retry-on-empty 는 1회로 bounded — 진짜 앱 적은 stripped 이미지가 무한 loop 안 함.
- 게스트 readiness gate (
- rdprrap 이 이미 patched 됐는데 marker 만 stale 인 상태에서
apply-fixes가 더 이상 TermService cycle (+ agent kill) 안 함. PR #81 이_apply_multi_session을 self-heal 하게 만들면서.activation_statusmarker 가enabled외 값이면 무조건rdprrap-activate.ps1spawn 했음. 마커 없는 pod (pre-OEM-v15) /not-activated/installer-failed에서는 정확하게 작동. 그런데 kernalix7 처럼 marker =installer-failed(OEM-time 부분 실패의 잔재) 지만ServiceDll은 OEM-time 에 이미termwrap.dll로 성공적으로 flip 돼서 multi-session 동작하는 pod 도 같이 트리거. 이 상태에서 활성화 재실행 = 불필요한 TermService cycle → agent 의 RDP 세션 죽음 → (HKCU\Run은 user logon 때만 발사라) 사용자가 앱 안 띄우면 agent 영구 dead. 반복 apply-fixes 호출 (install.sh --main업그레이드마다 migrate 의 apply 체인 재실행) 이 매번 agent 죽임. 수정: detached activator spawn 하기 전에HKLM\SYSTEM\CurrentControlSet\Services\TermService\Parameters\ServiceDll도 같이 probe. 이미termwrap매치면 marker 를enabled로 reconcile 하고 return — 이후 apply-fixes 는 fast path 로 들어가고 TermService cycle 안 함. HKCU\Run\WinpodxMedia가 더 이상 매 앱 launch 마다 검정 PS 콘솔 깜빡 안 함. OEM v19 이전 install.bat 은media_monitor.ps1(USB 자동 매핑 백그라운드 프로세스) 을powershell.exe -WindowStyle Hidden ...그대로 등록 —-WindowStyle Hidden은 conhost 가 자식한테 console 잠깐 할당한 후에야 적용돼서 ~50ms 검정 flash 가 새어 나옴. multi-session 켜진 상태에선 매 앱 launch 가 새 RDP 세션 만들고 HKCU\Run 처음부터 발사하므로, 사용자는 모든 launch 마다 flash 봄 — 보이는데 글자 안 읽힘, 전형적인 Hidden-flag race. 수정: install.bat 이 이제wscript.exe hidden-launcher.vbs powershell.exe ... media_monitor.ps1형태로 등록 (PR #58 가WinpodxAgent에 적용한 wscript+SW_HIDE 래퍼 동일). 마이그레이션:_apply_vbs_launchers가 wrapping 안 된 항목 발견 시HKCU\Run\WinpodxMedia재작성 — 기존 pod 도 다음winpodx pod apply-fixes(또는winpodx migrate) 에서 자동 적용. 다음 RDP 세션 / 앱 launch 부터 효과 — 현재 세션의 이미 실행 중인 media_monitor 는 살아있지만 재-spawn 안 하므로 추가 flash 없음. OEM 번들 18 → 19.cfg.pod.image가 SHA-pinned dockur image 로 default; migrate 가 기존 pod 도 정렬. 이전엔cfg.pod.image가docker.io/dockurr/windows:latest(또는 v0.3.0 이하 설치는ghcr.io/dockur/windows:latest) 로 default 였음. 매podman-compose up마다 tag 가 dockur 가 그 사이 push 한 최신으로 재해상도됨. resolved digest 가 바뀌면 (자주 — dockur 릴리스 주기가 거의 일별), podman-compose 가 spec mismatch 로 판단해서 컨테이너 재생성. kernalix7 이 2026-05-02 정확히 이 상황 만남: dockur 가 proc.sh substring failure (proc.sh: line 137: -1: substring expression < 0) 가 든:latestpush 한 직후, 일상적인install.sh --main업그레이드 가 멀쩡한 pod 위에 컨테이너 rebuild + 7.5GB ISO 재다운로드 + Sysprep 초기화 트리거. Pin:cfg.pod.imagedefault 가docker.io/dockurr/windows@sha256:20b398ab935465f97ec8ab06489f7a85a5ad58e74e036ce66cc3c9172e7dbea8(릴리스 시점에 Docker Hub registry 에서 조회 후core.config의DOCKUR_IMAGE_PIN으로 보관). Migrate 의 "already current" + cross-version 경로 모두 새_ensure_canonical_image_pin단계 호출 — 기존 pod 의cfg.pod.image+compose.yaml을 main fresh install 과 동일한 canonical pin 으로 재작성. 다음pod start에서 컨테이너 1회 재생성 (~30초, storage volume 보존 → ISO 재다운로드 없음, Sysprep 없음), 이후 dockur :latest 변동 영향 없음. Idempotent — 이미 pinned 된 config 에 migrate 재실행하면 rewrite 전에 short-circuit.winpodx setup --update-image명시적 dockur 버전 갱신. 기존setup서브커맨드에 새 플래그. 사용자의 container backend 로docker.io/dockurr/windows:latestpull → 로컬 image 의 repo-digest 해결 →cfg.pod.image에 저장 →compose.yaml재생성. 새 pin 을 출력해서 사용자가 무엇으로 잠그는지 확인 가능. 다음pod start시 migrate 경로와 동일한 recreate 비용 (~30초, volume 보존). fresh:latest를 pull 하는 유일한 경로 — 다른 모든 경로는 bundled / persisted pin 사용.
[0.3.1] - 2026-05-02
v0.3.0-RTM1 → main 마이그레이션 경로가 컨테이너 재생성 없이 multi-session 활성화 갭을 실제로 self-heal 하도록 만든 maintenance 릴리스. OEM-time 과 runtime rdprrap 활성화 경로도 단일 스크립트로 통합.
추가
- Nix flake.
nix run github:kernalix7/winpodx,nix profile install github:kernalix7/winpodx, 또는inputs.winpodx.url = "github:kernalix7/winpodx". Wrapper 가 FreeRDP, podman / podman-compose, iproute2, libnotify 를 번들로 포함해 기본 podman 백엔드는 추가 설정 없이 동작; docker 와 libvirt 는 opt-in 유지. devShell 에 동일한 런타임 툴 + ruff + mypy +src/를PYTHONPATH에 노출. README (en + ko) 에 Nix install 섹션 추가. (Thanks @Mic92 — PR #65.)
변경
paths.bundle_dir()— 번들 리소스 트리 단일 resolver. 이전엔 7개 호출 사이트가 각자__file__.parent.parent…walk + 일관성 없는 fallback 손수 굴림: discovery 스크립트 lookup, OEM 번들 버전, compose mount 용 OEM 디렉터리, VBS launcher 마이그레이션, debloat 스크립트 (CLI + GUI), data 에셋, rdprrap 버전 pin. 각자 따로 drift — discovery 가 이미 parent count off-by-one 으로 한 번 깨졌고, OEM 디렉터리 resolver 는 wheel install 을 놓쳤고, data-asset lookup 은 Nix 에서 아이콘을 못 찾음.winpodx.utils.paths의bundle_dir()단일 resolver 로 통합 —$WINPODX_BUNDLE_DIR환경변수 → 소스 체크아웃 →sys.prefix/share/winpodx→~/.local/bin/winpodx-app순서로 검색. 각 후보는scripts/,config/,data/중 하나를 포함해야 적격; 어느 후보도 통과 안 하면 소스 체크아웃 추측치로 폴백 (안정적 에러 메시지용). 영향받은 helper 의 테스트는HOME+sys.prefix+__file__저글링 대신bundle_dir직접 monkeypatch. (Thanks @Mic92 — PR #65.)
수정
winpodx pod apply-fixes가 multi-session 활성화 안 됐으면 자동 활성화. Multi-session 은 WinPodX 핵심 기능 — 없으면 multi-app 띄울 때마다 "Select a session to reconnect to" dialog 뜸. 이전엔 apply 체인의multi_session스텝이 상태 probe 만 (PR #77, mid-apply rdprrap-conf 가 agent 세션 죽이고 /exec 타임아웃되던 hang 방지용). PR #80 으로 활성화가 안전해짐 — rdprrap-activate.ps1 을 detached 로 spawn 해서 /exec 응답이 TermService cycle 전에 반환됨 — 그래서 apply 체인이 self-heal 가능:.activation_status가enabled면 no-op (추가 /exec round-trip 없음, disconnect 없음, churn 없음); 아니면 detached activator 큐. apply 체인 순서 재배치 —vbs_launchers(rdprrap-activate.ps1 + hidden-launcher.vbs staging) 가multi_session보다 먼저 실행. 활성화 필요한 경우 비용: TermService cycle 동안 RDP 세션 잠시 disconnect (~10초), 재접속하면 multi-session 활성. OEM-time 경로가 내는 1회성 비용을 pre-OEM-v17 pod 의 마이그레이션 시점으로 미룬 것일 뿐.winpodx pod multi-session on이 컨테이너 재생성 없이 기존 pod 에서 rdprrap 활성화. 활성화는rdprrap-installer install+net stop/start TermServicecycle 이 필요 — 패치된ServiceDll을 새 TermService 가 읽어야 하므로. 그런데 그 cycle 은 모든 활성 RDP 세션 (agent 자기 user 세션 포함) 을 죽여서, inline/exec로는 활성화 못 함 (응답 돌려보내기 전에 agent 가 죽음). 이전엔 OEM-time 활성화 실패한 v0.3.0-RTM1 pod 가 패치 적용하려면podman rm -f+ 재-Sysprep 이 유일한 길 — 정상 케이스에선 30초짜리 레지스트리 tweak 인데 게스트 디스크 수백 MB churn. 이제: 새rdprrap-activate.ps1스크립트 (idempotent — installer 바이너리 staging 안 되어 있으면C:\OEM\의 번들된 rdprrap-*.zip 추출 fallback, 3회 재시도, 재시작 후ServiceDllflip 검증, install.bat OEM v15+ 가 쓰는 동일한.activation_status마커 기록) 가C:\Users\Public\winpodx\launchers\에 staged 되고,winpodx pod multi-session on이 wscript+hidden-launcher.vbs 로 detached spawn (agent-respawn 패턴 동일)./exec호출은 "OK: activation queued" 즉시 반환; 사용자가 잠시 끊긴 후 재접속; agent 가 HKCU\Run 으로 자동 재시작; 이후winpodx pod multi-session status(이제 apply-fixes 의 status 표면과 동일한 marker probe) 로enabled확인.winpodx pod apply-fixes가 다른 VBS 런처들과 함께rdprrap-activate.ps1도 push 하므로, 기존 v0.3.0-RTM1 pod 도 다음 마이그레이션 때 재생성 없이 받음. (status는 더 이상rdprrap-conf.exeshell-out 안 함;disable은 여전히 inline — disable 은 레지스트리 패치 clear 뿐이라 TermService cycle 불필요.) install.bat 의 인라인 ~80 라인 installer-retry / TermService-cycle / ServiceDll-verify / marker 로직도 같은 스크립트로 통합 — install.bat 은 SHA 핀된 번들 추출만 하고rdprrap-activate.ps1에 위임 (OEM 시점엔 cmd.exe 가 로컬 콘솔 세션이라 TermService cycle 이 부모 안 죽이므로-Detached없이 동기 호출). Single source of truth: 활성화 동작 fix 가 OEM-time / runtime 경로 모두에 drift 없이 반영. OEM 번들 16 → 18.- 앱 launch 가 CLI parent 종료 후 silent 사망하던 문제 해결. Doomed FreeRDP 가 설명 없이 사라지는 경로 2가지: (1)
stderr=subprocess.PIPE가 parent 프로세스에 read-end 를 남겨서, CLI 종료 후 다음 stderr 쓰기에서 SIGPIPE → detached 클라이언트 사망. 이제 stderr 를$XDG_RUNTIME_DIR/<app>.stderr파일로 기록 — 세션이 parent 보다 오래 살고 tail 도 inspect 가능;RDPSession.stderr_tail은 그 파일의 마지막 2KB 를 lazy 하게 읽어서 기존 caller interface 유지. (2)$DISPLAY없는 순수 Wayland 세션에서xfreerdp(RAIL 동작하는 유일한 클라이언트 —sdl-freerdp는 RAIL 없음 (FreeRDP #9078),wlfreerdp는 deprecated 에 RAIL repaint 깨짐) 가 detach 후 "failed to open display" 로 사망.launch_app이 이제 그 조합을 거부하고 명확한 에러로 XWayland (compositor 내장 또는 niri / river 는xwayland-satellite) 를 안내. (Thanks @Mic92 — PR #64.) is_freerdp_pid()가 무관한 프로세스를 live RDP 세션으로 잘못 인식하던 거 해결. 이전엔/proc/<pid>/cmdline안 어디든b"freerdp"또는b"xfreerdp"부분 문자열만 있으면 매치 —~/freerdp-notes/run.sh같은 경로의 스크립트,--deselect=test_freerdp_pid인자 가진 pytest 호출, 또는 어쩌다 인자에 freerdp 언급한 도구까지 다 잡혀서, WinPodX 가 그것들을 본인이 spawn 한 FreeRDP 로 착각해 stale.cproc마커가 영원히 reap 안 됐음. 이제 cmdline 을 null-byte 로 파싱해서 argv[0] basename 만 검사 —find_freerdp()가 실제로 실행하는 바이너리들 (xfreerdp{,3},sdl-freerdp{,3},flatpak run com.freerdp.FreeRDP폴백) 과만 매치. 부분문자열 매치로 새던 케이스 2개에 대한 회귀 테스트 추가. 하위 PID-reuse 테스트는bash -c "exec -a … sleep 30"없이 다시 작성 — 멀티콜 coreutils 환경에서도 안 깨짐. (Thanks @Mic92 — PR #63.)
변경
- 기본 컨테이너 이미지
docker.io/dockurr/windows:latest로 전환 (이전ghcr.io/dockur/windows:latest). upstream 공식 compose /docker run레퍼런스와 정렬 (upstream README 와 예제compose.yml모두dockurr/windows사용). 동일 이미지 — digest 로 검증됨. 일부 사용자가 GitHub Container Registry 경로에서 token / 4xx 에러 만남; Docker Hub 가 canonical artifact 를 안정적으로 제공. 기존 설치는 자동 마이그레이션 안 됨:~/.config/winpodx/winpodx.toml이 resolved 값을 persist 해서 이미winpodx setup돌린 사용자는 옛 레퍼런스 유지. 새 기본값 적용하려면winpodx.toml에서image = "ghcr.io/..."라인 삭제 후winpodx setup재실행 (compose.yaml재생성), 또는~/.config/winpodx/compose.yaml직접 편집. (Thanks @Mic92 — PR #62.) - PowerShell 창 깜빡임 0 — 게스트 경로가 hidden VBS 런처와 agent 트랜스포트로 모두 통합. 3가지 수정 합쳐짐:
- Agent 자동시작이
hidden-launcher.vbs경유. HKCU\Run 이powershell.exe -WindowStyle Hidden -File C:\OEM\agent.ps1을 등록했는데, Hidden 플래그는 PowerShell 이 conhost 할당한 후에 적용되므로 사용자 로그인마다 ~50ms 짜리 PS 콘솔이 깜빡였음. 새 VBS wrapper 는 GUI 서브시스템 (자체 콘솔 없음) 이고WshShell.Run intWindowStyle=0이SW_HIDE를CreateProcess에 전달해서 spawn 된 PowerShell 이 windowless 로 시작됨. - UWP launch 가
IApplicationActivationManager경유. 기존/app:program:explorer.exe,cmd:shell:AppsFolder\<AUMID>가 UWP 프레임이 뜨기 전에 explorer.exe RemoteApp 윈도를 ~300ms 보여줬음 — Calculator / Settings / Terminal 에서 사용자가 보던 "PowerShell 같은 깜빡임" 이 그거. RemoteApp 이 이제wscript.exe launch_uwp.vbs <AUMID>호출 →IApplicationActivationManager::ActivateApplication직접 호출. UWP 프레임이 transition 없이 RemoteApp 윈도로 바로 등장; ~300ms 도 단축. - 잔여
run_in_windows호출자들이 agent 트랜스포트 경유.core.updates,core.daemon.sync_windows_time,cli.pod.multi-session,cli.main.debloat, GUI Tools 페이지 debloat 핸들러 — 이제 모두winpodx.core.windows_exec.run_via_transport경유. v0.3.0 agent 의/exec(CreateNoWindow=$true) 를 우선 시도하고/health응답 없을 때만 FreeRDP RemoteApp 폴백. 비밀번호 회전 (rule #6) 과winpodx pod sync-password복구 경로는 직접 credential 인증이 필요해서 의도적으로 FreeRDP 유지.
- Agent 자동시작이
OEM 번들이 13 으로 bump (새 VBS 파일들은 C:\Users\Public\winpodx\launchers\ 에 stage — Public 이 User 권한으로 쓸 수 있어서 agent 가 나중에 admin 없이 재작성 가능). 0.3.0-RTM1 기존 pod 마이그레이션 자동화: 새 _apply_vbs_launchers apply 스텝이 agent /exec 한 번으로 3개 파일 + HKCU\Run\WinpodxAgent 모두 갱신; apply_windows_runtime_fixes 가 multi_session 뒤에 체이닝. 트리거: winpodx pod apply-fixes 또는 업그레이드 후 winpodx migrate — 컨테이너 재생성 불필요. 자동시작 변경은 다음 user 세션 로그인 (또는 winpodx pod restart) 시 적용; UWP launch fix 는 host 의 다음 launch 즉시 반영.
추가
- 하이브리드 디스커버리 필터 — 필수앱 항상 표시, 시스템 shim 기본 hide. Windows 11 기본 install 에서 자동 디스커버리가 ~45개 entry 를 만드는데 두 종류 노이즈 같이 발생 — OS 필수앱 (File Explorer / Calculator / Settings) 은 Start Menu .lnk 로 enumerate 안 돼서 누락, 시스템 shim (
LicenseManagerShellExt,WindowsPackageManagerServer,DesktopPackageMetadata,microsoft-store-server…) 들은 grid 어지럽힘. 필터가 이제 큐레이션된 essentials allowlist (스캔이 놓친 필수앱은 stub 합성) 와 noise denylist (hidden = true자동 stamp 해서 GUI grid 가 거름) 를 같이 가짐. 사용자 override 가 우선 — 타일에 Hide / Show 토글하면 같은 TOML 에 기록돼서 다음 디스커버리 sweep 에서도 유지. discover_apps.ps1 가 essentials 3개를 실제 Windows 아이콘과 같이 명시적으로 emit (File Explorer 는C:\Windows\explorer.exe에서, Calculator + Settings 는 AppxManifest Square logo 에서 추출) — 사용자가 generic 한 letter avatar 가 아닌 진짜 Windows 아이콘 봄. - Win32 launch args. RDP RemoteApp builder 가
app.toml의 per-appargs문자열을 honor 해서 FreeRDPcmd:필드로 forward. 오래된 "explorer.exe RemoteApp 뜨면 아무것도 안 보임" 문제 해결 — File Explorer essential 이args = "shell:MyComputerFolder"같이 emit 돼서This PCview 가 정상 윈도로 열림 (user shell 점령 시도 안 함). 기존args = ""앱은 영향 없음. - 앱별 .desktop description. 디스커버리가 게스트에서 한 줄짜리 description 추출 (
.lnkComment 필드, exeProductName, UWP<VisualElements Description>) 해서 각 앱의.desktop파일Comment=키에 넣음. 이전엔 모든 entry 가Comment=Windows application via winpodx라는 똑같은 스탬프 쓰던 거 — 이제 메뉴/파일 매니저 tooltip 에서 실제 앱 description 보임. description 추출 안 되는 앱은 여전히 generic 스탬프. - Known-good UWP allowlist.
DisplayName이ms-resource:간접 참조라 PowerShell 비대화형 세션에서 풀리지 않는 UWP 패키지들 (Calculator, Terminal, Paint, Snipping Tool, Camera, Alarms, Maps, Sound Recorder, Notepad UWP, Sticky Notes, Get Help, Your Phone, To Do, Settings) 이 fallback 으로 dottedPackageFamilyName받아서 host 의 UWP-dot 체크에 junk 로 분류돼 빠지던 문제 — 이제 이 패키지들은 명시적 allowlist 로 통과시켜서 AAD/BrokerPlugin 같은 shim 만 거름. - GUI Apps 페이지 "Hidden (N)" 토글. Hidden entry 는 기본 접힘; toolbar 의 count chip 이 몇 개 거른지 표시. chip 클릭하면 hidden 포함해서 grid 펼침 — denylist 가 과도하게 거른 항목 promote 가능.
- README 히어로에 데모 스크린샷.
docs/images/demo.png(Windows 정보 / 작업 관리자 / PowerShell 각각 Linux 창으로 winpodx Apps grid 와 나란히) 가 이제 README 상단에 — 처음 방문자가 통합 모습 바로 봄.
[0.3.0] - 2026-04-30
메이저 릴리스 — 모듈형 core 재구조, HTTP guest agent, 통합 헬스체크 surface. FreeRDP RemoteApp 파이프라인을 기본 host→guest 채널에서 대체.
배경
v0.2.2 / v0.2.2.1 가 같은 기능들의 첫 시도였지만 실설치에서 깨짐 (PS창 폭주, "Another user is signed in" 다이얼로그, install timeout, compose 의 8765 포트 매핑 누락으로 /exec RST). 2026-04-29 main 을 v0.2.1 로 롤백하고 명시적 anti-goal 와 함께 agent + transport 처음부터 재설계 (docs/AGENT_V2_DESIGN.md 참고). v0.3.0 이 그 재설계 구현; 0.2.2.x 태그는 혼란 방지를 위해 삭제됨 — v0.2.1 에서 바로 v0.3.0-RTM1 로.
추가
- HTTP guest agent (rev4).
agent.ps1가 Windows 안127.0.0.1:8765에서 동작,+:8765로 바인드해서 QEMU user-mode NAT 통과. Bearer-authed/exec(base64 인코딩 PowerShell 페이로드) 가 FreeRDP RemoteApp 을 기본 host→guest 채널에서 대체;/health는 readiness probe 위해 unauthenticated 유지. child PS 는[Diagnostics.Process]+CreateNoWindow=$true+ 비동기ReadToEndAsync로 spawn — PS창 깜빡임 없음, pipe buffer deadlock 없음. 토큰은 OEM bind mount 로 전달 (호스트 mode0600, gitignored). - Transport ABC v1 (
core/transport/{base,agent,freerdp,dispatch}).dispatch()가 agent 우선,/health응답 없으면 FreeRDP 폴백. Password rotation 은 명시적으로 Transport 통하지 않음 (docs/TRANSPORT_ABC.md규칙 #6) — rotation 은 자체 credential 소유 +run_in_windows직접 호출 (stale-password 복구 시 bootstrap loop 회피). winpodx check헬스 프로브. 새 CLI 명령어가 멀티 소스 헬스 점검을 한 번에 실행하고 각 프로브를OK/WARN/FAIL/SKIP와 측정 시간으로 출력.--json로 머신 판독용 출력. exit code 는 어떤 프로브든FAIL일 때만1. 프로브:pod_running,rdp_port,agent_health— bring-up 상태guest_exec—Write-Output ok페이로드를/exec로 보내 rc=0 + stdout="ok" 검증. host→guest 채널이 실제로 round-trip 하는지 (단순히/health응답만이 아니라) 증명guest_summary—/exec한 번으로 Windows 버전 / uptime / 현재 사용자 / 활성 세션 수 / C: 여유 공간 가져옴oem_version,password_age,apps_discovered,disk_free— 호스트 측 상태
- GUI Info 페이지 Health 카드 자동 갱신. Info 페이지 최상단의 새 "Health" 섹션이 각 프로브를 색 배지 + 전체 verdict 로 렌더. 페이지가 보이는 동안 30초마다 자동 갱신, 페이지 떠나면 타이머 일시정지 (idle 시 guest poll 안 함).
- 사이드바 트랜스포트 표시기. 상단 pod chip 에 글자 점 2개 추가 —
A(guest agent) 와R(RDP 포트) — 도달 가능하면 녹색, 안 되면 빨강. tooltip 으로 "agent OK (version)" / "host→guest 명령어가 FreeRDP RemoteApp 으로 폴백됨" 표시 — 다음 launch 가 어느 채널로 갈지 한눈에 보임. 기존 15초 pod-status 타이머가 같이 갱신. install.shref 선택.--main은origin/main에서 설치 (개발용),--ref TAG는 특정 git ref / 릴리스 태그에서 설치. 플래그 없으면 최신 GitHub Release 사용. RTM-only 릴리스 게이트와 같이 추가 — RTM 사이의 rapid iteration 태그가 AUR / OBS / Debian publish 를 트리거해서 동작하는 install 을 덮어쓰지 않게 함.
수정
- discovery 스크립트 경로 한 단계 어긋남.
_ps_script_path가.parent를 4번 거슬러<root>/src/scripts/windows/discover_apps.ps1를 만들었는데 어떤 layout 에도 없는 경로. 5번 거슬러 실제<root>/scripts/windows/로 resolve 되게 수정 — 이제 GUI Refresh 가 pod 정상일 때 "Pod Not Running" dialog 를 띄우지 않음. - GUI 가
script_missing을pod_not_running으로 오분류._looks_like_pod_down가winpodx-app/...같은 install path 의 "pod" 부분 substring 에 매칭돼서 path 가 들어간 모든 DiscoveryError 가 잘못된 dialog 로 라우팅. RefreshWorker 가 이제 명시적인DiscoveryError.kind를 먼저 읽고, kind 없을 때만 substring 휴리스틱 폴백. - Agent
/exec가 child clean exit 후에도rc:null반환. PowerShellStart-Process -PassThru+WaitForExit(timeout)가 child 가 정상 종료해도$proc.ExitCode를$null로 둘 수 있는 알려진 동작. agent (rev4) 가 이제 source 에서 null → 0 강제, 호스트AgentClient도rc:null을 0 으로 처리해서 rev2 / rev3 가 baked-in 된 기존 pod 도 동작. - Agent 가
/exec마다 PowerShell 창을 깜빡임.Start-Process -NoNewWindow가 hidden parent (HKCU\Run 의-WindowStyle Hidden) 에서 fast-exit child 의 콘솔을 새로 띄우는 동작. agent.ps1 (rev4) 가 이제[Diagnostics.Process]+ProcessStartInfo로CreateNoWindow=$true와UseShellExecute=$false로 spawn; stdio 는 비동기ReadToEndAsync로 drain (pipe buffer deadlock 방지).WINPODX_OEM_VERSION 11 → 12— 다음 pod recreate 시 새 agent 가 install path 에 들어감.
[0.2.1] - 2026-04-28
마이너 버전 (0.2.0.x → 0.2.1) — UX 개선 묶음: install 이 부분 완료 상태로 끝나도 다음 실행 시 자동 재개, GUI 로그가 WinPodX 자체 로그를 실시간으로 표시, GUI 첫 실행 시 시스템 체크 안내.
추가
utils.pending재개 시스템. 새~/.config/winpodx/.pending_setup마커가 install.sh 가 못 끝낸 단계 (wait_ready/migrate/discovery) 추적. 다음 CLI 호출 (version/help/uninstall/config/info 외 모든 서브커맨드) 과 GUI 시작 시 마커 픽업해서 미완료 단계를 canonical 순서로 실행. 각 단계는 성공 시 마커에서 자체 제거; 빈 상태 되면 파일 삭제. 10개 단위 테스트가 순서, 멱등성, 부분 완료, "게스트 부팅 중 → 후속 단계 시도 안 함" 가드 커버.- GUI 첫 실행 Quick Start 다이얼로그. 최초 launch 시 5-bullet 스냅샷 표시 — backend / FreeRDP / pod 상태 / RDP listener / 디스커버리된 앱 수 — 백그라운드 resume 진행 여부도 안내. dismiss 시
~/.config/winpodx/.welcomed작성하여 재방문 사용자에게는 안 띄움. - GUI 로그 페이지가 winpodx 앱 로그 자동 tail. Tools/Terminal 페이지로 이동하면 기본으로
tail -F ~/.config/winpodx/winpodx.log스트림 시작 — 사용자가 내부 프로그램 로그 (apply / probe / refresh / pod 상태 전이) 를 기존 on-demand 컨테이너 로그 버튼과 함께 봄. 페이지 떠나면 streamer 자동 종료.
변경
- install.sh wait-ready timeout 1800s → 3600s. 예산을 1시간으로 늘려, 느린 하드웨어 신규 설치 (Windows ISO 다운로드 + Sysprep + OEM apply 첫 실행) 가 인라인으로 완료될 수 있게 함 (이전엔 timeout 후 resume 훅에 미룸). 1시간 초과 작업은 여전히 resume 훅이 picking up.
pod.max_sessions기본값 10 → 25,pod.ram_gb기본값 4 → 6. 10은 실제 사용 (Office + Teams + Edge + 사이드 앱 몇 개 동시) 에 빡빡함. 새 RAM 기본값이 25 sessions 에서 session-budget 경고 안 띄움 (2.0 + 25 × 0.1 ≈ 4.5 GB 필요). 아래 tier auto-detect 가 머신별로 추가 조정.
추가 (보충)
-
Setup 의 호스트 스펙 auto-tier. 새
utils.specs.detect_host_specs가/proc/meminfo+os.cpu_count()읽고recommend_tier가 3개 preset 중 하나 매핑:호스트 RAM 호스트 CPU 티어 VM CPU VM RAM ≥32 GB ≥12 thr 상 8 12 GB 16-32 GB 6-12 thr 중 4 6 GB <16 GB <6 thr 하 2 4 GB두 축 모두 임계값 통과해야 상위 티어 — 64 GB / 4-core 호스트는 CPU 가 병목이라 "하" 받음. 대화형 setup 은 추천값을 기본으로 표시, 비대화형은 즉시 적용. 10개 단위 테스트가 양축-통과, 단축-부족, 임계 경계 커버.
수정 (보충)
- 앱 실행할 때마다 "Select a session to reconnect to" 다이얼로그 발생 — zombie disconnected 세션 누적 원인.
install.bat와_apply_rdp_timeouts양쪽이MaxDisconnectionTime을0으로 설정. RDP 의미론에서0= timeout 없음 = disconnect 된 세션이 영원히 살아있음. 사용자가 FreeRDP 창 닫을 때마다Disc상태 세션 누적 → 다음 launch 시 Windows 가 그동안 쌓인 세션 리스트로 재연결 다이얼로그 띄움. rdprrap 멀티세션은 세션 동시 실행은 허용하지만 이 prompt 는 못 막음 — auto-logoff 만이 답. v0.2.1 에서30000(30초) 으로 변경 — disconnect 후 30초 뒤 자동 logoff, 사용자가 앱 닫고 다시 열어도 zombie 누적 안 됨.install.bat(신규 컨테이너) +_apply_rdp_timeouts(런타임 apply 로 기존 컨테이너 패치) 양쪽 수정. _apply_max_sessions가 틀린 레지스트리 키에 씀. 런타임 apply 가HKLM\...\Terminal Server\MaxInstanceCount에 썼지만 Windows 는 실제로HKLM\...\Terminal Server\WinStations\RDP-Tcp\MaxInstanceCount를 읽음. 결과: session-cap 도입 이후 모든 릴리스가 cfg 변경 시 silent no-op —install.bat의 OEM 시점 값만 authoritative 였음. v0.2.1 이 올바른 subkey 에 쓰고 (fSingleSessionPerUser는 Terminal Server root 에 있는 게 맞음, 그대로 유지), OEM 시점 install.bat 천장도 10 → 50 으로 상향해서 cfg 값이 [1, 50] clamp 안에서 install time 에 silent cap 안 되게.
수정
- GUI Refresh 두 번째 SEGV 경로 — Python ref / Qt deleteLater race. v0.2.0.10 이 QImage-워커스레드 크래시는 잡았지만 두 번째 SEGV 가 남아있었음:
_on_refresh_succeeded와_on_refresh_failed슬롯이 즉시self._refresh_worker = None실행. Python 의 ref drop 이 Qt 의 queuedworker.deleteLater()이벤트와 race — 둘 중 나중에 실행되는 쪽이 free 된QObject만나서 worker 스레드의~QObject()에서 크래시. 2026-04-28 코어덤프로 확인: 워커 스레드 2282062 의 top frame 이QObject::~QObject, 메인 스레드 2281803 은 슬롯의 PySide6callPythonMetaMethod디스패치 중. 수정:_refresh_worker/_refresh_threadPython ref drop 을_cleanup_refresh_worker로만 옮김,thread.finished에 바인딩되어 Qt 객체 둘 다 완전 해제된 후 실행. WorkerdeleteLater는 워커 스레드 자체 이벤트 루프에서 정상 처리 — Python GC 간섭 없음.
수정
- GUI Refresh 버튼 SEGV.
_DiscoveryWorker.run()(Qt 워커 스레드) 가persist_discovered→_validate_png_bytes→QImage.loadFromData호출. Wayland 의 Qt + libgallium / Mesa state 가 메인 스레드 외에서 QImage 만지면 race →Signal: 11 (SEGV)코어 덤프. v0.2.0.10 에서_validate_png_bytes가threading.current_thread() is not threading.main_thread()일 때 stdlib 청크 워커로 단축 회귀. 워커도 여전히 CRC + 크기 캡 + IEND terminator 강제하므로 off-main-thread 호출자는 약간 느리지만 크래시 없는 경로. - install.sh wait-ready 600s → 1800s. 신규 설치 (
uninstall --purge후 재설치) 는7.5GB Windows ISO 다운로드 + 추출 + Sysprep + OEM apply + 최종 재부팅 = 첫 실행 1530분. 600초 timeout 이 Windows VM 부팅 전에 발화 →[FAIL] Timeout waiting for Windows ready (09:56)로 끝남. 1800초 예산이 일반적 환경에서 신규 설치 커버; 후속 설치는 캐시된 ISO 재사용해서 2~5분. - GUI Refresh 가
.desktop엔트리 자동 설치 (winpodx app refreshCLI 와 parity). 기존엔 CLI 경로만 인라인 등록했고 GUI Refresh 는 discovered 트리만 갱신,~/.local/share/applications/는 안 건드림. v0.2.0.10 의_DiscoveryWorker가_sync_desktop_entries호출 —cli/app._register_desktop_entries의 워커-스레드-안전 형제 함수.
추가
- 첫 부팅 GUI 자동 디스커버리. Pod 가
running으로 전이 + 앱 리스트 비어있을 때, 메인 윈도가 2초 settle 후 Refresh 워커 자동 발화. install.sh 의 wait-ready 가 Sysprep 끝나기 전에 timeout 한 케이스 해결 — 사용자가 나중에 GUI 열면 pod 살아있는 거 확인 후 디스커버리가 알아서 발화. - GUI 실시간 로그 스트리밍. Tools/Terminal 페이지에 4개 버튼 추가:
Live (pod)와Live (app)가 컨테이너 또는~/.config/winpodx/winpodx.log에tail -F걸어 새 라인을 패널로 스트리밍;App log는 WinPodX 자체 앱 로그 마지막 200줄 표시;Stop tail은 활성 streamer 종료. 기존엔 pod 로그 100줄 one-shot 스냅샷만 있었음.
수정
- 두 번째 앱 실행 시 독립 윈도 대신 Windows "Select a session to reconnect to" 다이얼로그 발생. Windows 기본값이 사용자당 동시 FreeRDP RemoteApp 세션 거부 → 첫 앱 이후의 모든 launch 가 기존 세션에 묻히거나 reconnect 다이얼로그 띄움. v0.2.0.9 에서 self-heal apply 체인에
_apply_multi_session추가 — 게스트 안에서rdprrap-conf --enable호출해 termsrv.dll 패치 활성화 → launch 마다 독립 세션. 멱등 (이미 활성화돼있으면 no-op), 구 OEM 번들에 rdprrap-conf 없으면 best-effort skip. - 앱이 Windows 에서 삭제됐는데 DE 메뉴에
.desktop엔트리가 계속 남음. v0.2.0.8 이 refresh 자동 설치는 추가했지만 사라진 앱의 엔트리 제거는 안 했음. v0.2.0.9 에서 refresh 진짜 양방향 동기화:list_available_apps()에 없는 모든winpodx-*.desktop파일이 (해당 아이콘과 함께) 제거됨 → Windows 에서 Office 지우면 다음 refresh 때 launcher 에서도 Word/Excel/PowerPoint 사라짐.~/.local/share/winpodx/data/apps/의 사용자 작성 엔트리는 보존.
변경
- README 정보량 강화. 상단에
for-the-badge스타일 "Status: Beta" + "Latest release" 배지. 그 아래 표준 shields (license, Python, backend, language, tests, CI). 소셜 행 (stars, forks, watchers, unique visitors). 활동 행 (issues, PRs, last commit, code size). EN + KO 동기화.
수정
winpodx app refresh가 앱 발견은 하지만 데스크톱 메뉴에는 등록 안 함. refresh 경로는app.toml+ 아이콘을~/.local/share/winpodx/discovered/에 저장만 하고, 실제.desktop엔트리는 별도winpodx app install-all명령으로만 생성됐음 → 사용자가 "Discovered N app(s)" 메시지 본 후 DE 메뉴에 앱이 안 떠서 혼란. v0.2.0.8 부터 refresh 가 발견된 앱들의 .desktop 엔트리를 자동 설치 (best-effort, 실패는 warn 만 하고 refresh 자체는 계속) + 아이콘 캐시 갱신.- 앱 실행할 때마다 PowerShell 창 깜빡임.
ensure_ready의 self-heal apply 경로가 매 앱 실행마다 FreeRDP RemoteApp PowerShell payload 3개 발화.-WindowStyle Hidden으로 작아져도 여전히 매번 눈에 띄게 깜빡임. apply 자체는 레지스트리 멱등이라 warm pod 에서 재실행해도 가시적 효과 없음 — 순수 노이즈. v0.2.0.8 부터 self-heal 성공 후~/.config/winpodx/.applies_stamp에<winpodx_version>:<container_StartedAt>기록 → 이후 launch 는 단축 회귀, pod 재시작 (TermService / NIC 설정 재적용 필요) 또는 winpodx 업그레이드 시에만 다시 발화.
수정
- 빠른 컨테이너에서
pod wait-ready --logs가[container]라인 하나도 안 띄움. 두 가지 문제: (1) tail 을--tail 0으로 시작했는데 이건 "지금부터의 로그만 표시" 의미. 하지만 dockur 는 Windows ISO 다운로드 / 부팅 단계 메시지를 wait-ready 실행 전에 이미 출력 → 사용자에게 아무것도 안 보임. (2)stdout만 drain. dockur 는 진행 메시지를 stdout (다운로드 byte/s) 과 stderr (부팅 단계) 로 나눠 출력해서 절반이 사라짐. v0.2.0.7 에서--tail 100으로 최근 컨텍스트 즉시 표시 + stdout/stderr 둘 다 병렬 스레드로 drain.
수정
wait_for_windows_responsive가 부팅 중 게스트에서 1초도 안 되어 무너져pod wait-readyUX 가 통째로 망가짐. 헬퍼가 RDP TCP 포트 열림은 제대로 대기했지만, 그 다음 FreeRDP RemoteApp probe 를 단 한 번만 발화. 한 번 실패하면 (부팅 중 게스트는 항상 rc=147 connection-reset 반환) 즉시 False return → 호출자가 넘긴 600초 timeout 이 무시됨. v0.2.0.6 에서 probe 를 retry loop 로 변경: 5-20초짜리 probe 를 deadline 까지 반복 (FreeRDP 프로세스 CPU 점유 막기 위해 3초 간격). 이제pod wait-ready --timeout 600이 진짜 10분까지 기다림 — phase 3 의 elapsed time 이 증가하는 게 보임.
추가
winpodx pod wait-ready [--timeout SEC] [--logs]— Windows VM 첫 부팅 다단계 wait gate. 세 체크포인트를 elapsed time 과 함께 표시해서 사용자가 침묵 속 몇 분간 hang 대신 실제 진행 상황을 봄:[1/3] Container running(~5초)[2/3] RDP port open(보통 30-90초)[3/3] Windows ready (RemoteApp probes OK)(첫 부팅 시 보통 2-8분)--logs옵션 시 컨테이너 stdout 을 백그라운드 스레드로 tail 해서[container] ...라인으로 surfacing — Windows 가 실제로 뭐 하는지 (Sysprep, OEM apply 등) 보임. 블랙박스 → 가시성.
변경
install.sh가 진짜 single-shot 으로 바뀜 — 설치 정말 끝났을 때 exit, 컨테이너 시작했다고 거짓말 안 함. 새 흐름:setup→pod wait-ready --logs(최대 10분, progress + 컨테이너 로그) →migrate(게스트 ready 라 apply 깔끔히 통과) →app refresh(디스커버리 즉시 통과). 기존에는 Windows 가 아직 부팅 중인데Installation complete!표시 후, 사용자가 첫 앱 실행 시 또 기다리는 구조였음. CI / 비대화형 환경에서는WINPODX_NO_WAIT=1로 wait 우회,WINPODX_NO_DISCOVERY=1로 디스커버리 우회.- install.sh 의 중복
winpodx pod apply-fixes호출 제거 — v0.1.9.3 이후migrate의 "always-apply" 경로가 이미 apply 를 돌리므로 한 번 더 부르면 wait 만 두 배가 됨.
수정
- 신규
--purge설치마다 가짜 "cfg.password does not match Windows" 경고. v0.1.9.5 가 추가한_probe_password_sync(cfg/Windows 비밀번호 drift 사전 감지) 의 에러 분류기가 FreeRDP 에러 문자열에"no result file"또는"auth"가 들어있으면 drift 로 판정. 하지만 부팅 중 게스트 (모든 신규 설치가 거치는 상태) 는 FreeRDP 가 rc=147ERRCONNECT_CONNECT_TRANSPORT_FAILED(connection reset) 반환 → host wrapper 가"No result file written"으로 감쌈 → 분류기가"no result file"매칭 → 가짜 drift 경고 발화. v0.2.0.4 가 두 방향으로 수정:- probe 가
wait_for_windows_responsive(timeout=180)로 먼저 대기. 게스트 미준비면(probe deferred — guest still booting; will retry on next ensure_ready)메시지로 skip. - 분류기가 transport-level 실패 (
rc=131,rc=147,transport_failed,connection reset) 와 실제 auth 실패 (logon_failure,STATUS_LOGON_FAILURE등) 를 구분. 후자만 sync-password 경고 발화.
- probe 가
수정
- Discovery 가 apply path 와 동일한 부팅 race 에 노출. v0.2.0.1 이
_apply_*와pod apply-fixes만wait_for_windows_responsive로 게이팅하고,winpodx migrate의 "Run app discovery now?" 프롬프트와provisioner._auto_discover_if_empty(첫 부팅 시 ensure_ready 가 발화) 는 probe 없이 FreeRDP RemoteApp 채널 호출. 신규--purge설치 시 QEMU 안 Windows VM 이 여전히 부팅 중인데 discovery 가 떠서ERRCONNECT_CONNECT_TRANSPORT_FAILED [0x0002000D](rc=147, connection reset) 으로 무너지고 사용자는 빈 앱 메뉴로 끝남. v0.2.0.3 이 두 discovery 호출 지점 모두에 동일 probe 적용 — wait 후 scan 또는 "Re-run later with: winpodx app refresh" 안내로 skip. - 첫 부팅 timeout 90s → 180s. 실제 환경의 신규 설치는 느린 하드웨어에서 Windows + RDP + activation 핸드셰이크에 90초 초과 가능. 세 개의 apply / discovery probe 의 wait 예산을 180초로 상향 — one-shot install 이 첫 시도에 apply round 까지 완료할 수 있게 함.
수정
--purge신규 설치가 가짜 "0.1.7 -> X detected" 업그레이드 메시지 표시.winpodx setup이winpodx.toml만 저장하고installed_version.txt마커는 안 써서,install.sh가 자동으로 이어 호출하는winpodx migrate가 "config 있고 marker 없음" 상태 보고 pre-tracker fallback (baseline 0.1.7 가정) 발동. 실제 마커 도입 전 업그레이드에서는 맞는 동작이지만, 신규 설치에서는 모든 마이그레이션 스텝을 불필요하게 재실행하면서 "What's new in 0.1.8 / 0.1.9 / ..." 안내까지 띄움. v0.2.0.2 에서 setup 이 마커가 없을 때만 현재 버전을installed_version.txt에 기록하도록 수정 — 신규 설치는 현재 버전으로 보고되어 마이그레이션 스텝 발화 안 함, 실제 업그레이드 흐름은 그대로 동작.
수정
- 차가운 컨테이너에서 apply cascade 가 무너짐. v0.2.0 은
pod_status가RUNNING이 되는 즉시 세 개 idempotent runtime apply (max_sessions,rdp_timeouts,oem_runtime_fixes) 를 발화. dockur Linux 컨테이너는 몇 초 안에RUNNING도달하지만, QEMU 안 Windows VM 은 RDP 리스너가 FreeRDP RemoteApp activation 받기까지 30~90초 더 필요. 그 윈도우 안에서 모든 apply 는:- 신규 설치 시:
ERRCONNECT_CONNECT_TRANSPORT_FAILED [0x0002000D](rc=147, RDP 소켓은 열렸지만 서버 미초기화 — connection reset by peer) winpodx pod restart시:ERRCONNECT_ACTIVATION_TIMEOUT [0x0002001C](rc=131, FreeRDP 연결됐지만 activation 단계 미완료) 로 무너짐. 각 apply 가 60초 timeout 풀로 대기 → cascade 3분 → 사용자가 앱 실행 시 Launch Error 다이얼로그 또는winpodx setup→winpodx migrate도중 "3 of 3 applies failed" 패닉 메시지로 표면화.
- 신규 설치 시:
- 새 헬퍼
wait_for_windows_responsive(cfg, timeout=90):check_rdp_port폴링 후 20초 no-opWrite-Output 'ping'프로브로 FreeRDP RemoteApp 채널이 실제로 살아있는지 확인. 다음 경로의 precondition 으로 사용:ensure_ready()warm-pod 경로 — 게스트가 미응답이면 self-heal apply 블록 통째로 skip.winpodx pod apply-fixesCLI — 명시적 "Waiting for Windows guest to finish booting (up to 90s)…" 메시지로 hang 아님을 표시.winpodx migrateapply 단계 — 같은 wait + 채널 실패 스택트레이스 3개 대신 "게스트 부팅 중; 나중에 apply-fixes 실행하거나 그냥 앱 실행해도 됨" 명확한 메시지.
_self_heal_apply()(신규) — warm-pod ensure_ready apply 블록을WindowsExecErrorswallow 로 감싸서 transient 채널 실패가 cascade 안 되고 warning 만 로그 후 같은 호출 내 추가 시도 중단. 다음 ensure_ready 가 이어받음.
수정
oem_runtime_fixes가AllowComputerToTurnOffDevice파라미터 오류로 첫 적용 실패. v0.1.9.5 가 runtime apply 를 FreeRDP RemoteApp PowerShell 로 넘겼지만 payload 는 여전히Set-NetAdapterPowerManagement -AllowComputerToTurnOffDevice $false호출. 이 cmdlet 은 enum 문자열'Disabled'/'Enabled'를 요구하고, QEMU 안 가상 NIC (virtio) 는 이 파라미터 자체가 노출 안 되는 경우 잦음. v0.2.0 은try/catch로 감싸고 enum 형태로 전환, 미지원 어댑터는 건너뜀 — NIC 토폴로지 무관 apply 성공.- migrate 의 password-drift 프로브가 20초에 timeout. 차가운 pod 의 FreeRDP 첫 연결 (TLS + 인증 + RemoteApp 실행) 은 20초 자주 넘김. v0.2.0 은 프로브 예산 60초로 상향, cold-start 지연 때문에 실제 drift 가 가려지지 않게 함.
추가
- Refresh 진행 상황 스트리밍. 기존
winpodx app refresh는 게스트 enumerator 가 Registry App Paths / Start Menu / UWP 패키지 / choco·scoop shim 을 30~90초 동안 도는 동안 침묵. v0.2.0 은 스트리밍 진행 채널 추가 —windows_exec.run_in_windows가progress_callback받고, wrapper 가$Global:WinpodxProgressFile+Write-WinpodxProgress정의,discover_apps.ps1가 소스별로 한 줄씩 출력. 호스트 CLI 는 stderr 로... Scanning Registry App Paths...식으로 표시 (JSON 출력은 그대로 깨끗). winpodx pod multi-session {on|off|status}— 번들 rdprrap 다중 세션 RDP 패치 런타임 토글. FreeRDP RemoteApp 로 Windows 게스트 안에서rdprrap-conf.exe호출하므로 패치 enable/disable/inspect 위해 컨테이너 재생성 불필요.C:\OEM\rdprrap\rdprrap-conf.exe,C:\OEM\rdprrap-conf.exe,C:\Program Files\rdprrap\rdprrap-conf.exe순으로 탐색.- 디스커버리 junk 필터. Refresh 가 그동안 uninstaller (
unins000.exe, "Uninstall …"), 재배포 패키지 (vc_redist.x64.exe, "Microsoft Visual C++ …"), 헬퍼 (crashpad_handler.exe), inbox 접근성 도구 (narrator.exe,magnify.exe,osk.exe), 시스템 plumbing (ApplicationFrameHost.exe,RuntimeBroker.exe), DisplayName 미해결 UWP fallback (예:Microsoft.AAD.BrokerPlugin) 을 모두 노출했음. v0.2.0 은 호스트측 denylist 패턴 + 실행 파일 basename 매칭 + UWP fallback 감지로 모두 drop. 디버깅 시WINPODX_DISCOVERY_INCLUDE_ALL=1로 우회 가능. - GUI 앱 아이콘. 디스커버리한 앱이 launcher 의 grid 카드와 리스트 타일에서 실제 Windows 아이콘 (PNG / SVG) 으로 렌더링됨 — 기존 색상+첫글자 avatar 대신. 아이콘은 v0.1.8 부터
~/.local/share/winpodx/data/discovered/<slug>/icon.{png,svg}에 저장되어 있었고, GUI 가 이제QPixmap(PNG, smooth scaled) +QSvgRenderer(SVG, 모든 크기 crisp) 로 읽음. 아이콘 없는 앱은 letter avatar 로 fallback.
테스트
- 스트리밍 progress wrapper: Popen 기반 테스트가 progress-file 쓰기 인터리브된 3-poll lifecycle 시뮬레이션.
- Junk 필터: 11 가지 쓰레기 케이스 drop, 4 가지 실제 앱 보존, env-bypass 동작 검증.
수정
- 결과 파일의 BOM 으로 인한 거짓 "fail" 보고. v0.1.9.4 가 runtime apply 를 FreeRDP RemoteApp PowerShell 로 라우팅했는데, wrapper 가
Out-File -Encoding utf8사용 → Windows PowerShell 5.1 은 UTF-8 BOM 을 붙임 → 호스트가 기본 utf-8 코덱으로json.loads시 BOM 거부 → "result file unparseable: Unexpected UTF-8 BOM". 사실 rdp_timeouts 와 oem_runtime_fixes 의 레지스트리 변경은 실제 적용 성공했고, 파싱만 실패해서 사용자가 "안 됐다" 고 본 것.windows_exec.run_in_windows가 이제utf-8-sig로 읽어 BOM 자동 흡수. _apply_max_sessions가 자기 RDP 세션을 죽이던 문제. payload 가Restart-Service -Force TermService호출했는데, TermService 가 바로 그 FreeRDP RemoteApp 세션 호스팅 중. 재기동 → 세션 강제 종료 → wrapper 가 결과 파일 쓰기 전 죽음 → 호스트는ERRINFO_RPC_INITIATED_DISCONNECT [0x00010001]봄. 레지스트리 쓰기는 됐을 수도 있지만 채널 실패로 잘못 분류. v0.1.9.5 는 in-scriptRestart-Service제거; 레지스트리만 쓰면 충분, TermService 가 다음 자연 사이클 (다음 부팅 / 사용자 수동winpodx pod restart) 때 새 값 반영.
변경 (아키텍처)
- 모든 host→Windows 명령 경로를 깨진
podman exec ... powershell.exe에서windows_exec.run_in_windows로 마이그레이션. 6개 함수가 0.1.0 ~ 0.1.9.4 동안 silent no-op 이었음 —podman exec는 QEMU 호스팅하는 Linux 컨테이너만 도달, 그 안 Windows VM 에는 못 가서powershell.exe호출이 모두rc=127 executable file not found in $PATH로 실패하는데 헬퍼들이 warning 만 로그하고 return 했음. v0.1.9.5 는 모두 마이그레이션:provisioner._change_windows_password(비밀번호 회전 — 수년간 silent fail)pod.recover_rdp_if_needed(Bug B TermService 재기동 — 작동 안 했음; FreeRDP 도 죽은 RDP 리스너 인증 못하므로 컨테이너 재시작으로 대체)daemon.sync_windows_time(w32tm)core.updates._exec_toggle(Windows Update 활성화/비활성화/상태)cli/main._cmd_debloat와gui/main_window._on_debloat(debloat.ps1 —podman cp+podman exec둘 다 깨졌었음)core/discovery.discover_apps(Bug A 의 stdin pipe "수정" 도 같은 깨진 경로; 이제 FreeRDP RemoteApp 로 진짜 적용)
추가
winpodx pod sync-passwordCLI — 이전 릴리즈에서 누적된 비번 drift 복구. "마지막으로 작동한" 비번 (보통 초기 설치 시 또는compose.yml의PASSWORDenv var 값) 을 입력받아 FreeRDP 인증 → Windows 안에서net user실행 → 계정 비번을 현재 cfg.password 로 설정. 동기화 완료 후 비번 회전이 정상 작동.- migrate 자동 drift 감지.
winpodx migrate가 "already current" 경로에서 작은Write-Output 'sync-check'payload 를 FreeRDP 채널로 먼저 발사. auth/no-result-file 로 실패하면 "winpodx pod sync-password실행" 안내 메시지 출력 → 그 이후 3개 apply 가 혼란스러운 채널 에러로 실패하는 것 방지. - Lint 테스트
tests/test_no_broken_podman_exec.py— 향후src/winpodx/아래 (windows_exec.py자체 제외) 에podman exec ... powershell.exe패턴 재도입 시 CI 실패. Windows-측 명령은 단일 채널로 강제.
[0.1.9.4] - 2026-04-26
수정
-
Runtime apply 가 드디어 실제로 적용됨. kernalix7 이 2026-04-26 에 v0.1.9.1 / v0.1.9.2 / v0.1.9.3 의 runtime apply 가 silently 실패하고 있다고 보고:
podman exec winpodx-windows ...\powershell.exe가rc=127 executable file not found in $PATH반환. 근본 원인:podman exec는 QEMU 를 호스팅하는 Linux 컨테이너 안에서 명령을 실행하지, QEMU 안에서 도는 Windows VM 에선 안 돈다. Linux 컨테이너엔powershell.exe가 없음. 헬퍼들 (_apply_max_sessions,_apply_rdp_timeouts,_apply_oem_runtime_fixes,_change_windows_password) 이 모두 warning 만 로그하고 return 하는데, 공개apply_windows_runtime_fixes는 헬퍼가 raise 안 하니까 "ok" 로 보고. → 3개 릴리즈가 silent no-op 을 ship 했음. 3개 변경:- 신규
core/windows_exec.py—run_in_windows(cfg, ps_payload)가 FreeRDP RemoteApp 으로 PowerShell 을 띄우고 기존\\tsclient\home리다이렉션으로 스크립트 파이핑. wrapper 가{rc, stdout, stderr}JSON 을 같은 share 로 다시 씀. 호스트가 파싱해서WindowsExecResult반환. 채널 실패 (FreeRDP missing / auth fail / timeout / no result file) 는WindowsExecErrorraise; 스크립트 non-zero rc 는WindowsExecResult.rc로 표면화. _apply_max_sessions,_apply_rdp_timeouts,_apply_oem_runtime_fixes재작성 — 각각 PS payload 빌드 →run_in_windows호출 →rc != 0시RuntimeErrorraise 해서 실패가 진짜 전파됨.apply_windows_runtime_fixes정직한 보고 —try/except구조 동일하지만, Windows VM 내부 rc가 실제로 fakeok대신failed: rc=2 ...로 보고.
비용: 호출당 ~5–10 초 (RDP handshake + auth + script + disconnect) +
-WindowStyle Hidden으로 최소화한 PowerShell 창 깜박임. 대신 기존 pod 에서 작동 (재생성 불필요) + rc 체크가 진짜 의미 있음.주의사항:
cfg.rdp.password가 Windows 게스트 실제 비밀번호와 일치해야 함. 이전 릴리즈에서 password rotation 이 같은podman exec원인으로 silently 실패해 왔다면, 첫 호출이 auth error 로 실패. 사용자가winpodx app run desktop으로 Windows 들어가서net user User <config-비밀번호>로 동기화 필요. - 신규
테스트
tests/test_windows_exec.py에 9개 신규 테스트 — FreeRDP missing / 비번 missing / timeout / no-result-file (auth fail) / happy path / non-zero rc propagation / FreeRDP/app:program:cmd 형태 검증 / flatpak 바이너리 splitting / unparseable JSON.tests/test_provisioner.py재작성 —subprocess.run대신windows_exec.run_in_windowsmock. 신규 테스트가rc != 0에서RuntimeErrorraise + 채널 실패에서WindowsExecErrorraise 검증.
[0.1.9.3] - 2026-04-26
수정
- Patch 버전 migrate 가 Windows-측 apply 를 건너뛰던 "already current" 트랩. kernalix7 이 0.1.9.x 에서 0.1.9.2 로 업그레이드 후
winpodx 0.1.9.2: already current. Nothing to migrate.만 보고 실제 Windows 게스트엔 v0.1.9.1 RDP-timeout / v0.1.9.2 OEM v7-baseline runtime 수정이 들어가지 않음. 원인:_version_tuple(...)[:3]이0.1.9.1과0.1.9.2를 같은(0, 1, 9)튜플로 자르므로inst_cmp >= cur_cmp가 runtime apply 단계 앞에서 early-return 시킴. 이제 "already current" 경로에서도 idempotent runtime apply 가 항상 실행됨.
추가
winpodx pod apply-fixes독립 CLI 명령. Idempotent —_apply_max_sessions,_apply_rdp_timeouts,_apply_oem_runtime_fixes를 실행 중인 pod 에 호출하고 헬퍼별 OK/FAIL 테이블 출력. 성공 시 exit 0, pod 미실행/백엔드 미지원 시 2, 헬퍼 실패 시 3. 언제든 재실행 안전.- GUI Tools 페이지 "Apply Windows Fixes" 버튼. 동일한 runtime apply 를 Qt GUI 에서 트리거 — worker thread 에서 헬퍼 호출, 기존 toast/info-label 채널로 성공/실패 표시. CLI 안 쓰고 GUI 만으로 적용 가능.
- install.sh 가 매 설치 마지막에
winpodx pod apply-fixes자동 호출. migrate 위자드 다음 단계로 실행.|| true로 실패 무해 — pod 안 켜져 있으면 silent skip.curl | bash한 번이면 항상 최신 Windows-측 수정사항이 기존 게스트에 적용됨, migrate 의 버전 비교가 "진짜" 업그레이드를 봤는지와 무관. - 공개 API
provisioner.apply_windows_runtime_fixes(cfg)—{helper_name: "ok" | "failed: ..."}맵 반환. CLI / GUI / migrate 경로가 단일 진입점 공유.
[0.1.9.2] - 2026-04-26
수정
- v0.1.9 / v0.1.9.1 의 Windows-측 수정사항이 기존 게스트에 적용 안 되던 버그. kernalix7 보고: "마이그레이션 잘 되는거 맞아? 윈도에 적용 안되는거같은데" — 사실이었음. install.bat (OEM 스크립트) 은 dockur 무인 설치 첫 부팅 시에만 도므로, 0.1.6 / 0.1.7 / 0.1.8 / 0.1.9 / 0.1.9.1 사용자는 컨테이너 재생성 없이 NIC power-save off (OEM v7), TermService failure-recovery (OEM v7), RDP timeout 비활성 + KeepAlive (OEM v8) 를 받을 수 없었음. 추가로, v0.1.9.1 의
_apply_rdp_timeoutsruntime 헬퍼가provisioner.ensure_ready의check_rdp_portearly-return 뒤에 와이어돼 있어서 이미 정상 동작 중인 포드에는 절대 도달하지 못했음.provisioner.ensure_ready: 함수 상단에서pod_status한 번 probe 해서 idempotent runtime apply 들 (_apply_max_sessions,_apply_rdp_timeouts, 신규_apply_oem_runtime_fixes) 을 RDP early-return 앞에서 실행. cold-pod 경로에서는 pod 시작 후 재적용. 호출당 약 1.5s 오버헤드, 재실행은 모두 no-op.- 신규
provisioner._apply_oem_runtime_fixes(cfg): OEM v7 baseline (NICSet-NetAdapterPowerManagement -AllowComputerToTurnOffDevice $false,sc.exe failure TermServicerecovery 액션) 을 기존 게스트에podman exec powershell로 적용 —discover_apps.ps1가 쓰는 stdin-pipe transport 재사용. winpodx migrate: 0.1.9 boundary 를 넘는 업그레이드 감지 시 세 apply 헬퍼를 proactive 호출 (pod 상태 probe + stopped 시 interactive 시작 옵션). 헬퍼별 성공/실패 출력 — 컨테이너 재생성 없이 어떤 게 적용됐는지 사용자가 직접 확인 가능.
[0.1.9.1] - 2026-04-26
수정
- Apps "Refresh Apps" 버튼 누르면 GUI SEGV (pod 안 켜진 상태). kernalix7 보고:
_on_refresh_failed가 queued-signal 콜백 프레임 안에서QMessageBox(self)를 바로 생성했는데 PySide6 + Qt 6.x 가 dialog 의 폰트 상속 경로 (QApplication::font(parentWidget)→QMetaObject::className()) 에서 부모 metaObject 가 콜백 중에 조회되며 SEGV. 이제QTimer.singleShot(0, ...)으로 dialog 생성을 다음 이벤트 루프 틱으로 미뤄서 signal handler 프레임이 먼저 풀림. Info 페이지의 첫 fetch 도 같은 이유로__init__에서 빠져나간 뒤 실행. Info worker 클래스를 모듈 레벨로 hoisting (refresh 마다 재정의되던 것), 재진입 busy guard 추가, worker + QThread 모두 정상deleteLater. - 호스트 suspend / 장기 유휴 후에도 RDP 세션이 사용 중에 끊기던 문제. v0.1.9 Bug B 수정은 "RDP 도달 불가" 경로만 다뤘는데, Windows TermService 의 1시간
MaxIdleTime기본값 등으로 활성 세션 자체가 종료될 수 있었음. install.bat (OEM v7 → v8) 와 새_apply_rdp_timeoutsprovisioner 단계가MaxIdleTime=0,MaxDisconnectionTime=0,MaxConnectionTime=0,KeepAliveEnable=1+KeepAliveInterval=1을HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services와RDP-TcpWinStation 양쪽에 기록 + WinStation 에KeepAliveTimeout=1(TCP keep-alive 1분 간격). 기존 0.1.x 게스트도 다음ensure_ready에서 자동 적용 — 컨테이너 재생성 불필요.
[0.1.9] - 2026-04-25
변경
- Discovery-first 리팩터.
data/apps/아래 14개 번들 앱 프로필 (word-o365,excel-o365, ...,notepad,cmd, ...) 을 전부 제거. 이제 Linux 앱 메뉴는winpodx app refresh결과로만 채워지며, 첫 부팅 시 발견 트리가 비어 있을 때provisioner.ensure_ready가 자동 실행. 수동 재실행은 동일: CLI 의winpodx app refresh또는 GUI Apps 페이지의 "Refresh Apps" 버튼.AppInfo.source에서"bundled"enum 값 제거 —"discovered"와"user"만 남음. 0.1.x < 0.1.9 에서 업그레이드 시winpodx migrate가 기존~/.local/share/applications/winpodx-{14-bundled-slug}.desktop파일 정리 여부를 물음 (--non-interactive에서는 자동 스킵).
추가
- Info 페이지 (CLI + GUI). 새
core.info.gather_info(cfg)가 5섹션 스냅샷 반환 — System (winpodx 버전, OEM 번들 버전, rdprrap 버전, 배포판, 커널), Display, Dependencies, Pod (상태, 실행 시작 시각, RDP/VNC 도달성 probe, 활성 세션 수), Config (기존 budget 경고 포함).winpodx info가 5개 섹션 모두 출력하도록 재작성. Qt 메인 윈도우에 5번째 탭 "Info" 추가 — 섹션당 카드 + "Refresh Info" 버튼이QThread로gather_info재실행. 모든 probe 가 하드 타임아웃되어 아픈 pod 가 패널을 멈추지 않음.
수정
- Bug A: Windows 게스트 대상
winpodx app refresh. v0.1.8 에서podman cp host:discover_apps.ps1 container:C:/winpodx-discover.ps1가 실패 — dockur/windows 는 QEMU 안에서 실제 Windows 게스트를 돌리는 Linux 컨테이너이고, C: 드라이브는 가상 디스크 안에 있어podman cp로는 도달 불가. 이제 스크립트 본문을podman exec -i container powershell -NoProfile -ExecutionPolicy Bypass -Command -의 stdin 으로 파이핑하므로 staging 단계 자체가 사라짐. 컨테이너 런타임 stderr 에 "no such container", "is not running" 등이 보이면kind="pod_not_running"으로 재분류 — cli 는 exit code 2 + "runwinpodx pod start --wait" 힌트로 라우팅 유지. - Bug B: 호스트 suspend / 장기 유휴 후 RDP 도달 불가. 증상: VNC 포트 8007 은 살아있는데 RDP 포트 3390 만 응답 없음 — Windows TermService 가 멈추거나 가상 NIC 가 절전으로 빠짐. 새
core.pod.recover_rdp_if_needed(cfg)가 이 비대칭을 감지하고podman exec powershell Restart-Service -Force TermService; w32tm /resync /force실행 후 RDP 재 probe (최대 3회, 백오프).provisioner.ensure_ready의_ensure_pod_running직후에 와이어. OEM 번들 6 → 7 —install.bat에 예방 조치 추가:Set-NetAdapterPowerManagement -AllowComputerToTurnOffDevice $false와sc.exe failure TermService reset=86400 actions=restart/5000/restart/5000/restart/5000로 Windows 자체 복구.
[0.1.8] - 2026-04-25
추가
- Windows 앱 동적 발견. 새 CLI
winpodx app refresh서브커맨드와 Qt GUI Apps 페이지의 "Refresh Apps" 버튼이 Windows 게스트에 실제 설치된 앱을 열거하고 기본 번들 14개 프로필과 함께 등록합니다. 컨테이너 내부에서scripts/windows/discover_apps.ps1이 RegistryApp Paths(HKLM + HKCU), Start Menu.lnk재귀, UWP/MSIX (Get-AppxPackage+AppxManifest.xml), Chocolatey / Scoop shim 4개 소스를 스캔하고 실제 바이너리/패키지 로고에서 추출한 base64 아이콘을 포함한 JSON 배열을 반환합니다. Linux 호스트 (winpodx.core.discovery) 는podman cp로 스크립트를 복사하고podman exec powershell로 실행한 뒤, 결과를~/.local/share/winpodx/discovered/<slug>/아래 TOML + PNG/SVG 아이콘 파일로 저장합니다. 번들 / 사용자 직접 추가 / 발견 앱은 세 디렉토리로 분리 관리되며 로딩 시 "사용자 > 발견 > 번들" 우선순위로 병합됩니다 — 재발견 실행은 발견 트리만 건드립니다. - UWP RemoteApp 실행.
rdp.build_rdp_command가launch_uri+ 엄격 정규식 검증된 AUMID (<PackageFamilyName>!<AppId>) 를 받아 UWP 앱을/app:program:explorer.exe,cmd:shell:AppsFolder\<AUMID>로 매핑합니다./wm-classfallback 이winpodx-uwp-<aumid-slug>로 슬러그당 고유하게 지정되어 두 UWP 앱이 같은 힌트를 공유할 때도 Linux 태스크바 그루핑이 분리됩니다. - CI PowerShell Core smoke 테스트. 새
discover-apps-ps잡이 Ubuntu runner 에pwsh를 설치하고 모든 PR 에서discover_apps.ps1 -DryRun을 실행해core.discovery가 기대하는 JSON 배열 shape 을 stdout 이 파싱 가능한지 검증합니다. - 업그레이드 후 마이그레이션 위자드. 새 CLI
winpodx migrate가 사용자가 건너뛴 모든 버전의 릴리즈 노트를 순차적으로 보여주고, 원하면winpodx app refresh를 바로 실행해 Windows 앱 메뉴를 한 번에 채워 줍니다.install.sh는 업그레이드 감지 시 (~/.config/winpodx/winpodx.toml존재)winpodx migrate를 자동 호출합니다 — 건너뛰려면WINPODX_NO_MIGRATE=1설정. 자동화용 플래그:--no-refresh(discovery 만 스킵),--non-interactive(모든 프롬프트 비활성화). 위자드는~/.config/winpodx/installed_version.txt에 현재 버전을 기록하며, 이 파일이 없는 사전-0.1.8 설치는0.1.7에서 업그레이드하는 것으로 간주합니다. pod.max_sessions설정 노출. 기본값 10 유지,[1, 50]범위로 clamp.ensure_ready()가 매 provisioning 시 값을 읽어 게스트의HKLM:\...\Terminal Server\MaxInstanceCount와 비교하고, 다를 때만 레지스트리 재작성 +TermService재기동 — 활성 RemoteApp 세션이 매번 끊기지 않습니다. 적용 시fSingleSessionPerUser=0도 함께 재확정.winpodx.core.config의estimate_session_memory/check_session_budget헬퍼가winpodx config show,winpodx config set,winpodx info, 그리고 GUI Settings 페이지에서max_sessions가ram_gb예산을 초과할 때에만 경고를 표시합니다 — 기본 설정은 조용합니다.- 오프라인 / 에어갭 설치용
install.sh로컬 경로 플래그.--source PATH는 git clone 대신 로컬 디렉토리에서 winpodx 를 복사합니다 (pyproject.toml+src/winpodx/존재 검증).--image-tar PATH는podman load -i(또는docker load -i) 로 Windows 컨테이너 이미지를 사전 로드해 최초 부팅 시 레지스트리 접근이 필요 없게 합니다.--skip-deps는 배포판 의존성 설치 단계를 완전히 스킵하며 필수 도구가 이미 설치돼 있지 않으면 즉시 실패합니다. 각 플래그에 대응하는 환경 변수 (WINPODX_SOURCE,WINPODX_IMAGE_TAR,WINPODX_SKIP_DEPS) 도 제공 —curl | bash호출도 조합 가능.install.sh --help로 전체 사용법 확인.
변경
AppInfo에source: "bundled" | "discovered" | "user",args,wm_class_hint,launch_uri필드 추가. GUI 가 발견 엔트리를 뱃지로 구분할 수 있고 RDP 실행이 UWP 앱을 타겟팅할 수 있게 됩니다.desktop.entry._install_icon이 아이콘 파일 확장자에 따라hicolor/scalable/apps/(SVG) vshicolor/32x32/apps/(PNG) 로 분기 설치. 발견 앱의 추출된 PNG 아이콘이 번들 SVG 아이콘과 나란히 깔끔하게 설치됩니다.
[0.1.7] - 2026-04-23
변경
- 번들된 rdprrap 을 v0.1.3 로 갱신 (라이선스 컴플라이언스 릴리즈). 업스트림이 0.1.0, 0.1.1, 0.1.2 GitHub 릴리즈 자산을 모두 철회했습니다. 0.1.0 / 0.1.1 은 rdprrap 이 코드를 포팅해 온 세 업스트림(
stascorp/rdpwrapApache-2.0,llccd/TermWrapMIT,llccd/RDPWrapOffsetFinderMIT) 이 요구하는 소스 레벨 저작자 고지(attribution notices) 가 누락되어 있었습니다. 0.1.2 는NOTICE+vendor/licenses/를 추가해 법적 공백은 해소했지만, rdpwrap 파생 Rust 소스 16개 중 9개만 나열하고rdprrap-confAbout 다이얼로그의 copyright 라인이LICENSE와 불일치하는 위생 문제가 남아 있었습니다. 0.1.3 은NOTICE를 업스트림 바이너리별(RDPWInst / RDPConf / RDPCheck)로 재편해 16개 전부를 열거하고, About 다이얼로그 copyright 를LICENSE와 정렬했으며, 채택한 Contributor Covenant 텍스트에 CC BY 4.0 출처를 명시합니다. 0.1.1 의 레지스트리 readback 수정(OriginalServiceDll이termsrv.dlll로 저장되던 문제) 도 그대로 포함합니다. 새 번들 SHA256 은config/oem/rdprrap_version.txt에 고정되며, 기존 게스트도 컴플라이언스 번들로 재설치되도록 first-boot OEM 버전을 6 으로 올렸습니다.
문서
- 최상위
THIRD_PARTY_LICENSES.md추가. 번들된 rdprrap 바이너리와 런타임/선택 Python 의존성(PySide6 LGPL, libvirt-python LGPL, docker-py Apache-2.0, tomli MIT) 을 문서화합니다. debian/copyright가 번들된 rdprrap 파일을 별도 선언하도록 보강했고, ZIP 내부의NOTICE/vendor/licenses/텍스트가 업스트림 Apache-2.0 / MIT 저작자 고지 요건을 충족한다는 사실을 명시했습니다.
수정
install.sh가curl … | bash경로에서 정상 동작. 파이프로 실행되면 bash 가 stdin 에서 스크립트를 읽으므로BASH_SOURCE[0]가 unset 상태가 되고, 파일 상단의set -u가드와 결합되어 install.sh 205 줄에서BASH_SOURCE[0]: unbound variable로 리포 클론 전에 중단되었습니다. 로컬/원격 분기가 소스 경로를 빈 값으로 기본 처리하도록 변경되어, 로컬 소스 트리가 없을 때 자연스럽게 git clone 경로로 폴백합니다. CachyOS + Python 3.14 + fish shell 환경에서 리포트 (#3).
보안 / 컴플라이언스
- rdprrap 0.1.0 을 번들한 WinPodX 0.1.6 은 동일한 저작자 고지 누락 결함을 그대로 가지고 있었습니다. 0.1.6 GitHub 릴리즈 자산은 철회되었으며(태그는 보존), 0.1.7 이 Windows 게스트에 컴플라이언스 rdprrap 번들(0.1.3,
NOTICE+vendor/licenses/포함) 을 내려주는 첫 WinPodX 릴리즈입니다.
[0.1.6] - 2026-04-22
추가
- 멀티세션 RDP — 번들/완전 오프라인. rdprrap v0.1.0 zip (~1.6 MB,
config/oem/내부) 을 winpodx 패키지에 동봉하며, Windows 무인 설치 단계에서 자동 적용합니다. 번들은 게스트 최초 부팅 시C:\OEM\로 스테이징되고, 핀 파일의 sha256 과 일치 여부를 확인한 뒤 압축이 풀립니다. 설치 시점에 네트워크 접근은 필요하지 않습니다. 실패 시 조용히 단일 세션으로 폴백합니다. 게스트 측 관리 채널(설치 후 enable/disable/status)은 향후 릴리즈로 예정되어 있습니다.
[0.1.5] - 2026-04-21
추가
- AlmaLinux 9 / AlmaLinux 10 용 prebuilt RPM 추가 (RHEL 9/10, Rocky 9/10 에도 그대로 설치 가능). 모든 GitHub Release 에 자동 첨부.
- Arch Linux AUR 패키징 인프라 추가 (메인테이너 1회 세팅 후 활성화 — 자세한 절차는
packaging/aur/README.md).
변경
- 최소 Python 버전을 3.11 → 3.9 로 낮춤. 기본
python3가 3.9 인 배포판 (RHEL 9 / AlmaLinux 9 / Rocky 9) 에 별도 Python 모듈 없이 바로 설치 가능.
수정
- OBS RPM 자동 다운로드가 새로 퍼블리시된 에셋을 제대로 수거하도록 수정.
[0.1.4] - 2026-04-21
수정
.deb빌드가 "missing files" 로 실패하던 문제 해결.- 타겟 매트릭스 외의 마이너 아키텍처에서 발생하는 빌드 서비스 측 문제로 인해 OBS 퍼블리시가 실패로 찍히지 않도록 개선.
[0.1.3] - 2026-04-21
수정
- OBS 퍼블리시 단계가 빌드 대기 중 인증 에러 루프에 빠지지 않도록 수정.
.deb빌드가 테스트 스위트를 돌리지 않도록 수정 (테스트는 GitHub Actions 업스트림에서 실행).
[0.1.2] - 2026-04-21
수정
- 태그 푸시 이후 RPM /
.deb퍼블리시 워크플로우가 제대로 실행되어 Release 에 아티팩트가 첨부되도록 수정. - 업스트림
pyproject.toml버전이 최신 git 태그보다 앞서있어도 RPM 빌드가 실패하지 않도록 개선.
[0.1.1] - 2026-04-21
추가
- Release 별 prebuilt 패키지:
- RPM: openSUSE Tumbleweed, Leap 15.6, Leap 16.0, Slowroll, Fedora 42, Fedora 43.
.deb: Debian 12 / 13, Ubuntu 24.04 / 25.04 / 25.10.- 소스 dist + wheel.
- README "설치" 섹션에 배포판별 설치 방법 추가.
변경
- AppImage 패키징 제거: Python + Qt + FreeRDP + Podman 의존성 때문에 단일 파일 배포의 이점이 거의 없음.
수정
- 주간 업스트림 업데이트 체크가 권한 에러로 실패하지 않고 추적용 Issue 를 생성하도록 변경.
[0.1.0] - 2026-04-21
첫 공개 릴리즈.
추가
- Zero-config 자동 프로비저닝: 첫 앱 실행 시 설정 파일 생성, compose 파일 생성, 컨테이너 시작, 데스크탑 엔트리 등록이 자동으로 수행됨.
- 14개 번들 앱 정의: Word, Excel, PowerPoint, Outlook, OneNote, Access, 메모장, 탐색기, CMD, PowerShell, 그림판, 계산기, VS Code, Teams.
- 자동 서스펜드 / 리줌: 유휴 시 컨테이너 일시정지, 다음 앱 실행 시 자동 복구; 종료 시 정상 셧다운.
- 패스워드 자동 로테이션: 암호학적 난수 20자 패스워드, 7일마다 교체 (설정 가능), 실패 시 자동 롤백.
- 수동 패스워드 로테이션:
winpodx rotate-password. - Office 락 파일 정리:
winpodx cleanup이 홈 디렉터리의~$*.*락 파일 제거. - Windows 시간 동기화:
winpodx timesync로 호스트 sleep/wake 후 시계 재동기화. - Windows 디블로트:
winpodx debloat로 텔레메트리, 광고, Cortana, 검색 인덱싱 비활성화. - 전원 관리:
winpodx power --suspend/--resume로 컨테이너 수동 일시정지/복구. - 시스템 진단:
winpodx info로 디스플레이, 의존성, 설정 상태 확인. - 데스크탑 알림 (D-Bus /
notify-send) 앱 실행 시 자동 표시. - 스마트 DPI 스케일링: GNOME, KDE Plasma 5/6, Sway, Hyprland, Cinnamon, env var, xrdb 에서 스케일 자동 감지.
- Qt 시스템 트레이: pod 제어, 앱 런처, 유지보수 도구, 유휴 모니터, 자동 새로고침.
- 멀티 백엔드: Podman (기본), Docker, libvirt/KVM, manual RDP — 통일된 인터페이스.
- Podman/Docker 백엔드용 compose 파일 자동 생성 (
dockur/windows이미지 사용). - 앱별 작업표시줄 분리: 각 앱이 고유한 WM_CLASS /
StartupWMClass보유. - Windows 빌드 고정:
TargetReleaseVersion정책으로 기능 업데이트 차단, 보안 업데이트는 유지. - 업스트림 업데이트 모니터링:
dockur/windows신규 릴리즈를 매주 체크. - 동시 실행 보호: 쓰레딩 락으로 동시 앱 실행 시 크래시 방지.
- GUI 의 Windows Update 토글 (서비스 + 예약 작업 + hosts 파일 3중 차단).
- 사운드 + 프린터 리다이렉션 기본 활성화.
- USB 드라이브 공유 + hot-plug (재연결 없이 subfolder 로 표시).
- FreeRDP
urbdrc사용 가능 시 USB 장치 리다이렉션; 없으면 드라이브 공유로 graceful fallback. - Windows 측 USB 자동 드라이브 문자 매핑 (이벤트 기반, 폴링 없음).
- 데스크탑 통합:
.desktop엔트리, hicolor 아이콘, MIME 등록, 아이콘 캐시 리프레시. - 자격 증명 보호용 제한 권한 (
0600) TOML 설정 파일. - 프로세스 추적 + 좀비 리퍼 포함 FreeRDP 세션 관리.
winapps.conf임포트 (기존 winapps 설정 마이그레이션용).
보안
- RDP 를 127.0.0.1 에만 바인딩; 네트워크 노출 없음.
- TLS 전용 RDP 채널 (SecurityLayer=2); NLA 는 loopback 바인딩 환경에서만 비활성화.