Skip to content

Windows VM: existing data.img deleted on next start if VM was never cleanly stopped; launcher permission check fails on setgid shared folder #11603

Open

Description

@mladenmir

System details

Omarchy 4.0.3-1, kernel 7.2.3-arch1-3, Docker 29.7.2 + KVM

What's wrong?

Summary

While using Omarchy's built-in Windows VM, I hit two separate but related defects:

  1. Data loss (dockurr/windows lifecycle, as used by Omarchy): if the VM container is never stopped cleanly after installation, dockurr/windows never writes its install-completion marker. On the next start it treats the existing, non-empty data.img as an unfinished installation, deletes it, and starts a fresh unattended Windows install. There is no prompt and no backup.
  2. Launcher permission check (omarchy-windows-vm): after the container's first start, the shared folder carries the setgid bit. The launcher's chmod 0700 keeps that bit, its exact mode == 700 check fails, and the launcher exits silently.

In my case these two combined: issue 2 blocked every start after a host reboot, and the first start after I worked around it triggered issue 1.

The root cause of issue 1 is in dockurr/windows code; I am filing here because the user-facing integration and lifecycle are Omarchy's, and Omarchy could add safeguards. It may need to be forwarded upstream.

Everything below comes from a real incident, logs and file timestamps, and read-only inspection of the exact code that was running (scripts copied from the running container). I did not intentionally reproduce the data loss.

Environment

  • Omarchy 4.0.3-1 (package), omarchy-windows-vm from that package
  • Docker 29.7.2, docker-compose 5.5.1, KVM
  • Image as written by omarchy-windows-vm install: dockurr/windows (no tag/digest pin → resolved to latest)
    • org.opencontainers.image.version: 6.05
    • org.opencontainers.image.revision: efe47da76d49c9d77c0a26799c70315fa4d91055
    • image ID / repo digest: sha256:0cff9eb0e7aee9953e55bc682852ca4fdca233145a58ae1ec94f0b0c01a2ed30
  • Installer defaults: VERSION=11, 4G RAM, 2 cores, 64G disk; storage ~/.windows, shared ~/Windows
  • Host filesystem: Btrfs

Sequence of events

  1. omarchy-windows-vm install created and started the container. Windows 11 was downloaded and installed unattended. The firmware log shows Windows Boot Manager started from the virtual hard disk twice (about 7 and 10 minutes after first boot).
  2. The container kept running and was never stopped. Its on-disk state afterwards still showed running, with no finish time.
  3. Roughly 20 minutes later the host froze for an unknown reason and had to be powered off. I am not attributing the freeze to Omarchy or dockurr/windows.
  4. After reboot, the storage directory contained data.img (read-only check: Windows GPT layout with EFI/MSR/NTFS/recovery partitions, NTFS system volume not dirty) and the old setup.img, but no windows.boot.
  5. Launching "Windows" from the menu failed silently; Docker was never even started (issue 2).
  6. I cleared the setgid bit on ~/Windows and ran the privileged start action omarchy-windows-vm __priv up, which runs the same mount/permission preflight. The preflight passed.
  7. On that start the container logged:
    Creating overlay image...
    Adding win11x64.xml for automatic installation...
    Creating a 64 GB growable disk image in raw format...
    BdsDxe: skipped Boot0003 "UEFI QEMU QEMU HARDDISK" ...
    BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM ..."
    
    The new data.img has a file birth time 1.9 s after the container's StartedAt, matching that log line to the millisecond. The previous image was gone, and all windows.*, .rom and .vars state files had been recreated (only the ISO was kept). A fresh unattended installation started.
  8. After this start ~/Windows was mode 2777 again.

The lost VM was a fresh test installation with no important data, but the same mechanism would destroy a VM that has real data.

Issue 1: an existing disk is deleted when the completion marker was never written

Code is from dockurr/windows 6.05 (/run/*.sh in the running container).

Install decision (install.sh, needsInstall):

if [ -n "$previousBase" ] && [[ "${STORAGE,,}/${previousBase,,}" != "${iso,,}" ]]; then
  ...
  return 0
fi
if [ -n "$previousBase" ] && hasSystemImage; then
  return 1
fi
hasData && hasBootMarker && return 1
return 0
  • hasBootMarker checks $STORAGE/windows.boot.
  • hasData is true when the first 100 KiB of the disk are non-zero.
  • So a non-empty disk without the marker means "install".

Deletion (install.sh): startInstall unconditionally calls cleanupStorage, which runs:

find "$STORAGE" -maxdepth 1 -type f -iname 'data.*' -not -iname '*.iso' -delete

In this incident windows.base matched the current install source (win11x64.iso), so the backup/discard branches of cleanupInstall did not apply, and the deletion came from cleanupStorage. For context:

  • discardPrevious also deletes data.* when the install source changed, or when windows.base is missing, and hasCompletedInstall is false.
  • backupPrevious is used only when hasCompletedInstall is true.

Marker creation (power.sh):

  • markWindowsBooted() is the only place that creates windows.boot.
  • It is called only from finish(), which runs when the container is shut down (the SIGTERM/SIGHUP/SIGABRT/SIGQUIT trap → gracefulShutdown → finish) or when QEMU exits.
  • Even then it writes the marker only if ready() confirms that the last firmware boot entry was Windows Boot Manager from the hard disk, and the install ISO still exists.
  • No path writes the marker while the VM is running.

Consequence: a VM that was installed but never cleanly stopped has no marker. Any interruption that bypasses finish() (host crash, power loss, SIGKILL, OOM kill) makes the next start delete the installed disk and reinstall, with no confirmation and no backup.

Steps to reproduce

Derived from the code; not executed. Use a disposable VM only.

  1. omarchy-windows-vm install and wait until Windows is fully installed.
  2. Do not stop the VM. Interrupt it uncleanly, e.g. docker kill --signal KILL omarchy-windows (or power off the host).
  3. Confirm the storage dir has a non-empty data.img and no windows.boot.
  4. Start again (omarchy-windows-vm __priv up, or launch once issue 2 is worked around).

Expected per code: "Creating a 64 GB growable disk image…", the previous data.img is deleted, and a fresh install starts.

Possible workaround

From the code; not verified by me: after the first installation, stop the VM once with omarchy-windows-vm stop, then confirm that windows.boot exists in the storage directory before relying on the VM.

Suggested safeguards

  • dockurr/windows:
    • never auto-delete a non-empty data.* just because the marker is missing — abort, or rename it to a backup name first;
    • write the marker at runtime as soon as ready() confirms a boot from the installed disk.
  • Omarchy:
    • before up, refuse to start (or back up the disk) when data.* is non-empty but windows.boot is missing;
    • after install, guide the user through one clean stop;
    • document the risk;
    • consider pinning the image digest;
    • forward the lifecycle part upstream.

Issue 2: launcher permission check fails on a setgid shared folder

omarchy-windows-vm, prepare_caller_mounts (runs as root on every up / up_wait):

chmod 0700 -- "/proc/$BASHPID/fd/$storage_fd" "/proc/$BASHPID/fd/$shared_fd" || { ...; return 1; }
storage_mode=$(stat -Lc '%a' "/proc/$BASHPID/fd/$storage_fd" 2>/dev/null) || storage_mode=""
shared_mode=$(stat -Lc '%a' "/proc/$BASHPID/fd/$shared_fd" 2>/dev/null) || shared_mode=""
if [[ $storage_mode != 700 || $shared_mode != 700 ]]; then
  ...
  return 1
fi

mounted_leaf_matches also requires $mode == 700.

  • dockurr/windows side: samba.sh (addShare) runs chmod 2777 "$dir" on an empty, writable shared directory, so after a container start ~/Windows is 2777.
  • Why chmod 0700 is not enough: GNU chmod with an octal mode of four or fewer digits preserves the setuid/setgid bits on directories. 2777 becomes 2700, stat %a returns 2700, and the function returns 1 without any message. Verified on the affected system: chmod 0700 leaves 2700, chmod 00700 yields 700.
  • Result: after the container's first start, every later launch / up_wait fails while the shared folder is empty. From the menu (Terminal=false) the user gets no diagnostic beyond the generic "Failed to start Windows VM" notification.

Suggested fixes

  • Use chmod 00700 (or chmod u=rwx,go=,g-s) where the directory mode is normalized, and/or validate only the access bits ((mode & 0777) == 0700) instead of the exact %a string.
  • Print the specific failing path, the actual mode and the expected mode to stderr, and surface it in the notification.
  • Note that dockurr/windows' 2777 also leaves the shared folder world-writable while the VM runs.

Scope and uncertainty

  • The cause of the host freeze is unknown and not attributed to Omarchy or dockurr/windows.
  • The data loss was not intentionally reproduced. The mechanism comes from the exact code that was running plus the incident timeline; the deletion itself is not logged by dockurr/windows, but the new disk's creation time matches its log line.
  • The lost VM was a recent test install with no important data.

I can provide the sanitized timeline and full code/log excerpts if helpful.

Activity

  1. hanzxtf commented on Sep 30, 2026

    @hanzxtf

    +1; just had this same issue.

    installed win11 and had it fully configured with office 2024 and then next day i check, there is no windows.

  2. omarchybot commented on Oct 4, 2026

    @omarchybot
    Collaborator

    Following the merge of #12323, The shared-folder permission refusal is fixed. The reported dockurr reinstall/data-loss incident is separate and remains unresolved; keep the issue open for it.

    Scope checked by GPT-6 in Codex and Codex Medium against the discussion and landed change; review independence is not guaranteed. No tests were rerun for this cleanup.

    Omabot on behalf of DHH

  3. omarchybot commented on Oct 11, 2026

    @omarchybot
    Collaborator

    I read the data-loss half of this against current dockur/windows master (after v6.06) and Omarchy's quattro. The mechanism you describe holds in source, with one refinement. I did not reproduce it: that needs a full Windows install under nested KVM, so this is proven by reading the code, not by running it.

    • needsInstall() in src/install.sh keeps an existing disk only when both hasData and hasBootMarker hold, or when a windows.system image is recorded. A non-empty data.img with no windows.boot and an unchanged install source goes to startInstall(), which calls cleanupStorage(). That deletes every data.* in storage with no size check, prompt or backup.
    • windows.boot is written only by markWindowsBooted() in src/power.sh, and its only caller is finish(). The refinement: finish() runs on a graceful container stop and when QEMU exits by itself (src/entry.sh), so it is not limited to a clean stop. What skips it is anything that kills the container's entrypoint first: host power loss, a hard freeze, docker kill -s KILL, the OOM killer. Nothing writes the marker while the VM is running.
    • On Omarchy's side, omarchy-windows-vm install starts the container detached and leaves it running (bin/omarchy-windows-vm, install_windows), and __priv_up / __priv_up_wait check mounts but not disk or marker state. A freshly installed VM therefore has no marker until something stops it. In practice that is usually the first launch without --keep-alive, which runs docker compose down when RDP closes. A host crash before that point loses the install.

    The setgid half is fixed on quattro by #12323, as noted above. The privileged preflight now runs chmod u=rwx,go=,a-s before it requires mode 700.

    No fix is drafted, because every Omarchy-side safeguard is a choice about behaviour rather than a correction. Seen from outside, a non-empty data.img with no windows.boot looks the same whether the install was interrupted half way (and should restart) or finished but was never stopped (and should be kept). Refusing to start, backing up the disk first, or stopping the VM once after install each trades one of those cases against the other. Pinning the image digest would not change this mechanism. The underlying fix belongs upstream in dockur/windows: write the marker at runtime once the firmware shows a boot from the disk, or never delete a non-empty disk without a backup. No upstream issue for it turned up in a search. Until then, the workaround from the report holds: after the first install, run omarchy-windows-vm stop once and check that windows.boot exists in ~/.windows.

    Checked by Opus 5.5 in Claude Code, with Codex Medium as a second opinion. Codex independently traced the same deletion path and contributed the finish()-on-QEMU-exit refinement above. It also agreed that no guard can tell the two states apart. This now waits on the maintainer: whether to add a safeguard in omarchy-windows-vm, and whether to report it upstream.

  4. jandrusk commented on Oct 13, 2026

    @jandrusk

    The setgid / == 700 launcher failure is the same cluster as #14445, #14196, #11879 — fixes in flight at #12264, #12046, #9783.

    The data.img wipe on unclean stop looks distinct and serious (silent reinstall). Can you confirm:

    1. Exact steps (install completes → kill container / host reboot without omarchy-windows-vm stop → next start deletes data.img)?
    2. Whether anything under ~/.local/share/omarchy/windows-vm (or the compose data path) still holds an install-complete marker after that path?

    If we can reproduce the wipe, that should be P1 separate from the setgid fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions