v0.5.8
[0.5.8] - 2026-05-24
Reliability + reach release: fixes the two big fresh-install failures users reported (#269 agent can't bind 8765, #287 install.bat never runs), ships a distro-agnostic fat AppImage, and rounds out the setup wizard so a standalone winpodx setup provisions end-to-end.
Highlights
Fresh-install reliability + a self-contained AppImage. Two fresh-install dead-ends fixed, slow connections + slow hosts no longer time out, and a single-file AppImage now bundles FreeRDP + Podman for immutable distros.
- #269 fixed -- the agent's
http://+:8765/urlacl reservation is now created for the World SID so the non-admin guest User can bind it; agent.ps1 retries + logs the urlacl state on failure (thanks @ismikes). - Fat AppImage (#227) --
winpodx-fat-x86_64.AppImagebundles Python + Qt + FreeRDP + Podman + podman-compose;winpodx setup-hosthandles the host-side kvm-group / subuid bits via a single pkexec prompt (requested by @leandromqrs). - Dynamic + generous timeouts (#126) --
pod wait-readyauto-extends from dockur's download ETA, compose-up is activity-based, and every fixed timeout was widened so slow links / hosts don't fail mid-op (reported by @xiyeming). winpodx pod recover-oem(#287) -- re-stagesC:\OEM+ runs install.bat when dockur's first-boot OEM copy fails (driver @ankranidiotis).- Setup wizard completion (#255) --
--customizewalks edition / language / region / keyboard / tuning, and a standalonewinpodx setupnow runs the full provision flow (wait-ready + discovery + reverse-open) instead of stopping at "container created". podman-composerequired up-front (#288) -- no more silentkeep-groupsfailure whenpodman composedelegates to docker-compose (reported by @magicdiablo).
Added
winpodx pod recover-oem-- manual OEM recovery for failed dockur first-boot copy (#287). When dockur's$OEM$ -> C:\OEMcopy silently fails (soinstall.batnever runs, the agent never installs, and port 8765 RSTs), this command tars/oemout of the container, serves it over a short-lived HTTP server on container port 8766, and prints the exact noVNC PowerShell to download + extract + runinstall.batfrom the full OEM tree. podman/docker only. Driver: @ankranidiotis (Linux Mint 22, Podman 4.9.3).pod wait-readydynamic deadline +winpodx setup-host(#126, #227).pod wait-readyparses dockur's wget ETA and extends its deadline (no upper bound -- a genuinely stalled download stops producing ETA lines and the wait expires naturally; +60min slack absorbs blips) so slow ISO downloads don't trip the 60min default.winpodx setup-host(alsopython -m winpodx.setup_wizard) detects + pkexec-fixes the host bits an AppImage can't do from user space:kvmgroup membership,/etc/subuid+/etc/subgidfor rootless podman, persistent kvm module load. Reported by @xiyeming.- Fat AppImage with bundled FreeRDP + Podman + pkexec wizard (#227). Initial lean AppImage in #302 (Python + winpodx + Qt only) layered up to a fat bundle in this release:
packaging/appimage/recipe downloads a python-build-standalone portable Python 3.11, installs the winpodx wheel +gui+reverse-openextras into it, then overlays a Fedora 41 binary bundle (xfreerdp / wlfreerdp / sdl-freerdp, podman, podman-compose, conmon, crun, netavark, slirp4netns, passt, pasta) with all transitive.sodeps that aren't on the host-critical exclude list (glibc / libX11 / libGL / libwayland / libxkbcommon stay on host -- bundling them would break desktop integration or hard-crash on glibc mismatch). Singlewinpodx-fat-x86_64.AppImage(~290 MB squashfs / ~920 MB AppDir) self-contained for everything user-space can carry. Newwinpodx setup-hostsubcommand (pluspython -m winpodx.setup_wizard) wraps the host-side bits that AppImages legitimately can't do from user space --kvmgroup membership,/etc/subuid+/etc/subgidfor rootless podman, persistent kvm module load -- in a singlepkexecpolkit prompt. Target audience: immutable distros (Fedora Silverblue / Kinoite / Aeon, Steam Deck) where layering system packages is heavy or unavailable, plus locked-down environments wherecurl install.sh | bashisn't an option. CI bypasses python-appimage entirely (its bundled appimagetool runs without honouring our PATH wrapper and silently fails on FUSE-less GitHub runners); the new pipeline builds the AppDir layout from scratch, layers the binary overlay via a Fedora 41 docker container, and packs with a preinstalled extract-and-wrapappimagetoolat/usr/local/bin. The bundled third-party license texts (FreeRDP / Podman / podman-compose / ...) are vendored inpackaging/appimage/licenses/so they travel inside the AppImage independent of CI'sdnfnodocs policy, and the build fail-closes if a required dir is absent. README + INSTALL.md (en + ko) updated. Driver: @leandromqrs. - Setup wizard completion --
--customizeprompts + full standalone provision (#255). The wizard now prompts for Windows edition, UI language, regional format, keyboard layout, and tuning profile (previously CLI-skipped, GUI-only). A standalonewinpodx setupruns the same post-create flow install.sh drives -- wait-ready, apply-fixes, app discovery, reverse-open -- so it finishes like a complete install. New--create-onlyflag keeps install.sh's create-then-orchestrate path intact. - CLI + GUI first-run setup prompt (#255 PR 1). When
cfg.pod.initializedis False (or no config exists), the firstwinpodx <cmd>invocation surfaces a three-way prompt:[Y]es(auto -- host-detected defaults, no prompts),[C]ustom(wizard -- pick every knob),[n]o(skip). Skip-list bypasses introspection / config / uninstall / gui / tray commands and non-TTY stdin. GUI shows the same modal on first launch. After a successful setup,initializedflips to True and the prompt stops firing. winpodx setup --customizeflag (#255 PR 1). Opts into the wizard mode (existing interactive prompts; full multi-step wizard with debloat / tuning / anti-detection knobs lands in PR 7). Defaultwinpodx setupis now non-interactive (host-detected defaults).install.sh --manualflag (#255 PR 2). Skipswinpodx setup+pod wait-ready+ app discovery + reverse-open setup. Binary + desktop entry + icon land normally; provisioning is deferred to the first-run prompt that fires on the nextwinpodxinvocation (CLI Y/C/n or GUI modal). Useful when you want to pick custom knobs (edition / language / debloat / tuning / anti-detection) without sitting through the auto path first. Equivalent:WINPODX_MANUAL=1env var.docs/INSTALL.md+ Korean mirror gain a "Manual install" section.winpodx doctorsubcommand (#255 PR 6). Read-only diagnostic. Walks ~9 checks (install source detected, freerdp present, /dev/kvm present, backend on PATH, config + binary state, container health, pending setup marker, autostart entry integrity,cfg.pod.initializedflag) and prints per-check[OK]/[WARN]/[FAIL]findings with the suggested next command. Read-only -- never mutates state; users copy the suggestions. Exit 1 when any FAIL finding; useful in CI / install scripts as a quick health gate. Designed to be cheap (< 2s on a healthy install): every subprocess probe has a short timeout, no network calls.winpodx setupregisters the GUI launcher.desktop(#255 G7). Standalonewinpodx setupinvocations (pip install, dev checkout, or any path where install.sh / packaging didn't drop the GUI entry) now copydata/winpodx.desktopto~/.local/share/applications/winpodx.desktop, mirroring what install.sh does for curl installs. Skips when/usr/share/applications/winpodx.desktopalready exists -- package installs own that path and a user-level copy would shadow the package version on upgrade.install_gui_launcher_desktop()lives next toinstall_winpodx_icon()inwinpodx.desktop.icons.winpodx uninstallconsolidated to a single bash script (#255 follow-up). The pre-this-PR #255 PR 3 landed a ~470-line Python implementation inwinpodx.cli.uninstallalongside the existing ~300-lineuninstall.sh. Two parallel implementations drifted (10+ cleanup steps diverged: MIME cache, gtk-update-icon-cache, guest-registry scrub, final pkill sweep, volume glob, storage bind-mount wipe, GUI launcher .desktop removal, ...), repeatedly producing bin/winpodx-symlink-survives-purge bugs (#278 / #279). Consolidation:uninstall.shis now the canonical implementation.winpodx uninstallis a ~50-line Python wrapper that locatesuninstall.sh(in/usr/share/winpodx/,~/.local/bin/winpodx-app/,~/.local/share/winpodx/,sys.prefix/share/winpodx/, or the dev checkout) andos.execvp's it -- so uninstall keeps working even when the Python install is half-broken. The sameuninstall.shnow ships in every channel (debian/winpodx.install, rpm %files, AUR PKGBUILD package(), pyproject shared-data for pip wheels, curl install bundle dir) at one well-known path:/usr/share/winpodx/uninstall.sh. Install-source detection (dpkg / rpm / pacman) moved into bash; if a package manager owns the binary, the script prompts to runsudo apt remove/sudo dnf remove/sudo pacman -Rnsfirst, then re-enters the same script via the package's postrm hook with--from-postrmto do user-side cleanup -- the only ordering that keeps dpkg/rpm db consistent with disk state.postrm-common.shsimplified: delegates to/usr/share/winpodx/uninstall.sh --from-postrm --yes [--purge]instead of calling the Python CLI. Flags normalised:--yes(was--confirm-- kept as silent alias),--purge,--from-postrm(internal, set by postrm hooks). The--no-package-promptflag introduced by PR 3 is removed (its behaviour folds into--from-postrm). Tests: deletedtests/test_uninstall_consolidation.py(Python helpers no longer exist); addedtests/test_uninstall_wrapper.py(10 tests: candidate-path coverage, execvp argv forwarding, SystemExit on script-not-found) andtests/uninstall_smoke.sh(sandbox $HOME smoke that asserts every expected path is gone post-run, shellcheck-clean).uninstall.shdetects pip / source installs (#255 G6). Previously fell through tounknown-- pip users saw no hint. Now identifies site-packages or dev-checkout paths and printspip uninstall winpodxafter user-side cleanup as the canonical removal command for that install kind. Detection is heuristic only (the script doesn't know the venv prefix to drivepip uninstalldirectly), so the hint is informational, not auto-exec'd.- Packaging post-install / post-remove hooks across debian / rpm / aur (#255 PR 4). Every package install path now ends with a banner pointing at
winpodx setup(or just opening winpodx -- first-run prompt covers it). Every package remove path runs a tiny cleanup that pkill's tray / GUI / helper processes + stops the reverse-open listener viawinpodx host-open stop-listener, so users aren't left with orphan processes afterapt remove/dnf remove/yay -Rns. debian'sapt purgeadditionally runswinpodx uninstall --purge --yes --no-package-promptper user with a winpodx config dir, matchingapt purgesemantics. rpm and aur lack a purge concept; their post-remove hooks tell users how to do the full wipe (winpodx uninstall --purge --yes). Shared logic lives inpackaging/scripts/postrm-common.sh, installed to/usr/share/winpodx/packaging/postrm-common.shby every distro. New files:debian/postinst,debian/postrm,debian/winpodx.install,packaging/aur/winpodx.install. Modified:packaging/rpm/winpodx.spec(%post + %postun added),packaging/aur/PKGBUILD(install=winpodx.install + scripts install),.github/workflows/aur-publish.yml(push winpodx.install alongside PKGBUILD). winpodx --versioninstall-source suffix. Output now readswinpodx 0.5.8 (installed via apt)/(curl install)/(pip install)/(install source not detected)so users see provenance at a glance.winpodx info [System]install-source line. Same detection surfaced in the info command's System block alongside winpodx / OEM bundle / rdprrap / distro / kernel lines.winpodx.utils.install_source-- new helper. Detects install provenance viadpkg -S/rpm -qf/pacman -Qo(3 s timeout each), with~/.local/bin/winpodx-appcurl-install heuristic +site-packagessource-checkout fallback. ReturnsInstallSource(kind, label, package_name, removal_command)for downstream uninstall / version / info / doctor consumers.- Extended Windows-on-KVM tuning profile (#245).
autoandsafenow apply 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) plusvirtio-rng-pcibacked by/dev/urandom. Significant scheduling / VM-exit win on multi-vCPU guests; entropy pool fills quickly so first-boot CryptoAPI / TLS handshakes don't stall.autoadditionally exposes nested-virt CPU features (+vmxIntel /+svmAMD) andhv-evmcs(Intel) when/sys/module/kvm_intel/parameters/nested(orkvm_amd) readsY-- enabling Hyper-V / WSL2 / Docker Desktop inside the Windows guest. No-op when the host kernel hasn't opted into nested KVM.safeexcludes nested-virt +hv-evmcs(those need explicit host-side opt-in). New CLI override:winpodx pod start --tuning {auto,safe,off,manual}for one-shot per-run profile selection (does not persist to winpodx.toml). GUI Settings gains a Tuning Profile dropdown plus a liveformat_tuning_summary()panel showing what the resolved profile applies on this host. NewTuningCapability.nested_kvmprobe +TuningProfile.apply_hv_enlightenments/apply_virtio_rng/apply_evmcs/apply_nested_virtflags inwinpodx.utils.specs. performancetuning profile + Settings card relocation (#245 follow-up). New profile that behaves likeautobut bypasses thededicated_hostgate -- CPU pinning + no-balloon flip on regardless of current host idle CPU / free RAM. Use when the box is mostly dedicated to winpodx and you want minimum guest latency at the cost of other host workloads. Hard-gated knobs (+invtsc,io_uring) still respect capability detection so the profile can't force a CPU flag QEMU would reject. CLI:winpodx pod start --tuning performance. GUI: Settings now has a dedicated "Performance Tuning" card -- dropdown at top,format_tuning_summary()detection panel below in the same card frame.- Settings UI polish + design tokens. Split the old "Container / VM" card (10 form rows, taller than the 7-row RDP card next to it) into a "Hardware" card (Backend / Edition / CPU / RAM / Idle / Max Sessions = 6 rows) plus a full-width "Localization" card (Language / Region / Keyboard / Timezone = 4 rows) so the top row reads as a height-balanced two-column layout. Added a design-token layer to
winpodx.gui.theme--SPACE_XS/S/M/L/XL/XXL,RADIUS_XS/S/M/L/XL/XXL, andFONT_CAPTION/BODY/SUBHEAD/HEADER/TITLE/HERO-- so the patchwork of magic numbers (24, 22, 14, 12, 11, ...) across cards, headers, summary panels, and form labels can converge on a single named scale. Settings page + tuning card converted to the new tokens; remaining pages will migrate in a follow-up.
Changed
winpodx setupdefault flipped to non-interactive auto (#255 PR 1). Previously interactive with host-detected defaults + a handful of prompts. Now defaults to fully auto (= existing--non-interactivebehaviour). Usewinpodx setup --customizefor the wizard. The--non-interactiveflag stays as a deprecated alias for install.sh and other scripted callers.- Timeouts widened across the board + activity-based compose-up (#298, #126). Slow hosts / slow links no longer fail ops that are still making progress:
compose upis now activity-based (kills only after 5min of silent output, not a 120s wall clock) with a 4h hard cap;wait_for_windows_responsive90 -> 300s default / 180 -> 600s at call sites; agent/health+ FreeRDP RDP-port probes 2 -> 5s;transport.exec/run_in_windowsguest calls bumped 2-3x (sync-password 30 -> 90s, multi-session 20/45 -> 60/120s, etc.);compose down60 -> 180s; daemon suspend/resume/inspect 30/10 -> 90/30s. Tight TCP poll-loop probes (check_rdp_portinsidepod_status) intentionally left short -- the budget there is the poll interval. - CPU sub-flags moved from
ARGUMENTSto dedicatedCPU_FLAGS+VMXenv vars; hv- delegated to dockur.* Three problems collapse into one refactor: (1) PR #289's-msg timestamp=onworkaround for dockurproc.sh:137bash slice on empty post-strip ARGUMENTS, (2) PR #281's hv-* enlightenments duplicating dockur's ownhv_passthrough(defaultHV=Y) causing QEMU 10'sAmbiguous CPU model string. Don't mix both "-hv-evmcs" and "hv-evmcs=on"warning, (3) PR #281's explicit+vmx/+svmnested-virt injection duplicating dockur'sVMXenv handling. winpodx now uses dockur's documented env-var interface:CPU_FLAGS: "arch_capabilities=off,+invtsc"on x86 (the two sub-flags dockur doesn't add itself),VMX: "Y"when nested-virt is wanted (dockur picks the right+vmx/+svmper host CPU). ARGUMENTS carries only the virtio-rng device pair now. proc.sh:137 strip code path is never triggered (no-cpu host,in ARGUMENTS) so the-msg timestamp=onmarker is removed. The ambiguous-string warning goes away (we don't addhv-evmcs-- dockur owns the entire hv-* set). And winpodx is decoupled from QEMU sub-flag deprecation: when QEMU 11/12 changes hv-* syntax, dockur handles it, we don't.
Fixed
- Agent 8765 bind failure -- urlacl reserved for the wrong owner (#269). install.bat ran to completion (RDP listener up, HKCU\Run registered, agent spawned) but the agent FATAL'd on every boot:
HttpListener.Start() failed: ... 'http://+:8765/' ... conflicts with an existing registration on the machine. The agent runs as the non-admin autologon User, so binding the strong-wildcard prefix needs a urlacl reservation it's permitted to use. install.bat's oldnetsh http add urlacl ... user=Everyone listen=yes >nul 2>&1either created a wrong-owner reservation or hitERROR_ALREADY_EXISTS(183) -- both masked by the>nulredirect. Now: delete all overlapping 8765 reservations first, thennetsh http add urlacl url=http://+:8765/ sddl=D:(A;;GX;;;WD)(World SID directly, locale-proof), with every result logged to setup.log + anetsh http show urlacldump after. agent.ps1 retries the bind 5x (absorbs the reservation-landing race) and dumps the live urlacl state to agent.log on persistent failure. OEM bundle 24 -> 25. Diagnosed end-to-end with @ismikes (Kubuntu 26.04) -- three earlier test branches ruled out the install.bat changes before his agent.log screenshots pinpointed the urlacl conflict. - dockur
proc.sh:137ARGUMENTS-strip crash, agent never installs (#287, #269). dockur (via the qemus/qemu base image) parses the composeARGUMENTS:env, strips out-cpu host,<sub-flags>for its own CPU_FLAGS pipeline, then runsARGUMENTS="${args::-1}"on the leftover. When the user's tuning profile produces nothing else (tuning_profile = offfor example), the leftover is an empty string and the bash slice fails withproc.sh: line 137: -1: substring expression < 0. proc.sh exits on that line;/oemis never copied into the Windows guest;install.batnever runs; the winpodx agent service is never installed; port 8765 accepts TCP but RSTs every handshake;winpodx pod wait-readytimes out at 60 min. Reported by @ankranidiotis on v0.5.7 (Linux Mint 22) and @ismikes (Kubuntu 26.04) -- both fresh installs with default tuning. Workaround in_qemu_arguments_for_host(): when no extra QEMU args would be emitted, append-msg timestamp=on(timestamps QEMU log lines, otherwise a no-op) so proc.sh's strip leaves at least one space-separated token in ARGUMENTS and the bash slice sees a non-empty string. Theauto/safe/performancetuning profiles already emit hv-* / virtio-rng extras (#245) and aren't affected; the workaround triggers foroffand any future profile that produces a pure-cpu host,...line. podman-composerequired up-front instead of trustingpodman compose(#288). On Fedora-family systems with the docker-compose CLI plugin installed,podman composedelegates to docker-compose, which doesn't understand thegroup_add: [keep-groups]magic value winpodx uses for rootless/dev/kvmpassthrough -- the container fails to start withUnable to find group keep-groups. install.sh's old presence check accepted that delegation (podman compose versionsucceeds via it). Now install.sh requires the standalonepodman-composebinary, andbackend.podman._compose_cmdraises a clear per-distro install hint instead of silently delegating. Reported by @magicdiablo (Nobara).-no-hpetremoved -- QEMU 10 (dockur v5.15+) rejects it as invalid option. PR #281's hv-enlightenments work appended-no-hpetto the QEMU ARGUMENTS when the auto / safe / performance profiles selected the Hyper-V machine timer. dockur v5.15 ships QEMU 10.x via the qemus/qemu v7.30 base image; QEMU 10 dropped the-no-hpetflag entirely (replacement is-machine ...,hpet=off, which would override dockur's machine type). Container start now bombs withqemu-system-x86_64: -no-hpet: invalid option. Dropping the flag has no functional impact -- thehv-stimer+hv-stimer-directenlightenments already steer Windows off HPET as the clock source. Reported by @ismikes on Kubuntu 26.04 against main HEAD after PR #289 merged.- Structured
[agent-install]markers in install.bat (#269 diagnostics). Each agent-install step (hkcu-run-register,spawn,post-spawn-probe,urlacl) is fenced with[agent-install] step=<name> status=<enter|exit>lines, the HKCU\Run write + Start-Process are wrapped in try/catch that log the exception, and a 5s post-spawn probe checks the 8765 listener + tails agent.log -- so a failed agent install localises itself in setup.log without another round-trip. - RPM spec
License:tag widened toMIT AND Apache-2.0(#301).packaging/rpm/winpodx.specdeclaredLicense: MITbutconfig/oem/rdprrap-*-windows-x64.zipbundles stascorp/rdpwrap (Apache-2.0). Fedora packaging guidelines require the SPDX expression to enumerate every license that applies to the redistributed binary content.debian/copyrightandTHIRD_PARTY_LICENSES.mdalready broke this out correctly; the RPM spec now matches.