Skip to content

Restore the partition table if cfdisk Resize shrinks an existing OS - #122

Open
rastermanden wants to merge 3 commits into
omacom:quattrofrom
rastermanden:fix/cfdisk-resize-restores-table
Open

Restore the partition table if cfdisk Resize shrinks an existing OS#122
rastermanden wants to merge 3 commits into
omacom:quattrofrom
rastermanden:fix/cfdisk-resize-restores-table

Conversation

@rastermanden

@rastermanden rastermanden commented Aug 24, 2026

Copy link
Copy Markdown

Free-space installer's cfdisk Resize shrinks the GPT only — corrupts NTFS/ext4 (data loss)

Repo: omacom-io/omarchy-iso (installer lives here, not in basecamp/omarchy)

Related

What's wrong

Quattro's free-space install is the dual-boot conversion path. When the disk has no unallocated gap, the configurator offers Open partition tool, which launches cfdisk:

https://github.com/omacom-io/omarchy-iso/blob/quattro/configs/airootfs/root/configurator

The copy tells people to "Create unallocated free space" and "leave the target area as Free space." It does not say that Resize is partition-table-only. cfdisk rewrites the GPT end sector and does not shrink NTFS, ext4, btrfs, or LUKS.

If someone resizes an existing Windows C: or Linux /home:

  1. The partition table is now smaller.
  2. The filesystem still believes it owns the old size.
  3. Installing Omarchy into that "free space" writes over the tail of the other OS.
  4. First reboot into Windows/Linux is data loss. #7903: STOP CODE: UNMOUNTABLE_BOOT_VOLUME. Install log showed Partition 3 resized and never ran ntfsresize.

This is not a support mix-up. It is a validated data-loss bug on the path Omarchy just advertised.

Expected

Do not implement in-installer filesystem shrink (ntfsresize / resize2fs / btrfs shrink). That is the slow path and fights the documented model.

  1. Warn, in the partition tool, that Resize on an existing OS partition corrupts it.
  2. Point people at Windows Disk Management / the running OS to make the gap, then leave it unallocated.
  3. If they Resize anyway: detect that an existing partition shrank, restore the previous GPT (nothing has been written into the gap yet), and refuse to continue the install.

Deleting an unused partition to make free space must still work.

Patch

Branch on a local checkout of omacom-io/omarchy-iso: fix/cfdisk-resize-restores-table

  • open_partition_tool() snapshots the table with sfdisk -d before cfdisk, restores if any existing partition (matched by start sector) shrank by more than 1MiB.
  • Warning copy on the partition-tool screen and the "not enough free space" screen.
  • The guard refuses rather than guesses. A missing snapshot does not open cfdisk unguarded: with no baseline to compare against and no dump to write back, a Resize is neither visible nor recoverable, so the tool is declined and the user is pointed at Windows Disk Management. A table that will not read back after cfdisk, and a shrink whose restore fails, abort with their own screens. parted is judged by its exit status, not by whether it printed anything — an unreadable disk arriving as empty output otherwise reads exactly like a disk with no partitions.
  • .automated_script.sh honours the refusal: it clears the previous run's inputs before starting (the abort tells people to re-run it, and a leftover configuration describes a layout the guard just rejected) and requires both a clean configurator exit and a configuration file before handing off to the installer. The cleanup covers the same set omarchy-cidata-load clears, because the loader's own cleanup sits behind its drive-found check: an autoinstall attempt that aborts and is retried with the drive pulled would otherwise carry authorized_keys and tailscale_authkey into the interactive install, and the orchestrator uses both whenever the files exist.
  • Tests, no root and no real disk: test/unit/partition-shrink-guard-test.sh covers restore-on-shrink and the edits that must be left alone (delete, create, grow, no-op), plus every failure branch — no snapshot, an empty snapshot, a table that will not read back, and a failed restore with the shrink still on the table. test/unit/installer-gate-test.sh runs the launcher's real handoff region against a sandboxed /root and asserts no install starts without a configurator that exited clean and a configuration this run produced, that a pulled cidata drive leaves no remote-access credentials behind, and that the launcher's cleanup list still matches the loader's.

./test/all passes.

Why this is an adoption issue

Single-drive dual-boot is how Windows/Mac people try Omarchy without wiping the machine. If first contact eats /home or Windows, they do not become users — they become anti-advocates. The rest of the dual-boot cluster (#7263, #7515, #7867, ISO #111) is the same funnel; this one is the footgun that destroys data before the install even finishes.

Free-space install offers cfdisk when the disk has no unallocated gap. The screen already says to leave Free space, but cfdisk's Resize looks like the way to make that gap. It only rewrites the GPT. The filesystem — NTFS, ext4, btrfs, LUKS — still claims the old size, and the next boot into the other OS is a corrupt /home or UNMOUNTABLE_BOOT_VOLUME.

That is the conversion-path trap: Quattro advertises single-drive dual-boot, the manual says shrink from Windows Disk Management first, and the installer then hands people a tool that cannot do that job. Reports: omacom/omarchy#7262, omacom/omarchy#7903.

The partition tool now says Resize is destructive, snapshots the table before cfdisk, and writes the snapshot back if any existing partition shrank. Deleting an unused partition or creating nothing still stands. Filesystem-aware shrink (ntfsresize and friends) is out of scope; the documented path is still "make the gap from the other OS."

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a safety guard to prevent cfdisk partition-table-only shrinking from corrupting existing filesystems during free-space installs.

Changes:

  • Snapshot, detect, and restore partition tables when existing partitions shrink.
  • Add warnings and refusal flows around cfdisk.
  • Add image-based regression tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Summary
test/unit/partition-shrink-guard-test.sh Tests shrink restoration and delete, create, grow, and no-op behavior.
configs/airootfs/usr/share/omarchy-iso/disk-partitioning.sh Implements partition comparison and restoration; post-cfdisk table-read failures currently fail open.
configs/airootfs/root/configurator Adds warnings and invokes the guard; snapshot or baseline failures currently allow cfdisk to launch.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread configs/airootfs/root/configurator Outdated
Comment on lines +910 to +912
dump=$(mktemp)
before=$(partition_starts_and_sizes "$disk")
save_partition_table "$disk" "$dump" || { rm -f "$dump"; dump=""; }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 96927a6. The baseline scan and the snapshot are now prerequisites rather than best-effort: if either fails, open_partition_tool refuses to launch cfdisk and sends the user to Windows Disk Management instead. save_partition_table also checks sfdisk's exit status now, not just that the dump is non-empty, so a partial dump is never accepted as a snapshot.

Covered by test/unit/partition-shrink-guard-test.sh ("save_partition_table rejects a disk it cannot read", "a failed dump is not accepted as a snapshot").

Comment on lines +171 to +173
after=$(partition_starts_and_sizes "$disk")
shrunk=$(shrunk_partition_lines "$before" "$after")
[[ -n $shrunk ]] || return 1

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 96927a6. partition_starts_and_sizes now captures parted's output into a variable first, so the status it returns is parted's rather than awk's, and it returns 1 when parted fails. restore_shrunk_partitions gained status 3 for "cannot tell", which the configurator aborts on — a missing snapshot returns 3 as well, so an unverifiable table can no longer collapse into the status-1 "table is fine" path.

Covered by "partition_starts_and_sizes reports an unreadable disk" and "a table that will not read back is 'cannot tell', not 'fine'".

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

The PR prevents unsafe cfdisk partition shrinking from flowing into a free-space installation and closes the previously reported retry-state leaks.

  • Snapshots the partition table before opening cfdisk, detects existing partitions that shrink, and restores the prior table.
  • Refuses partition editing when the table cannot be safely captured or verified.
  • Requires a successful configurator run and newly produced configuration before starting installation.
  • Clears stale configuration and remote-access inputs before interactive or unattended setup.
  • Adds unit coverage for shrink restoration, failure handling, installer handoff gates, and retry cleanup.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
configs/airootfs/root/.automated_script.sh Clears all known staged inputs and enforces successful, current configuration before dashboard handoff, resolving the prior gate and credential-retention findings.
configs/airootfs/root/configurator Adds fail-closed partition-table snapshot, shrink restoration, and abort handling around the interactive partition tool.
configs/airootfs/usr/share/omarchy-iso/disk-partitioning.sh Adds helpers to capture partition geometry, detect meaningful shrink operations, snapshot GPT state, and restore it.
test/unit/installer-gate-test.sh Exercises configurator failure, stale configuration, credential cleanup, missing output, and valid interactive and cidata handoffs.
test/unit/partition-shrink-guard-test.sh Covers shrink restoration, permitted table edits, unreadable snapshots, unverifiable tables, and failed restores.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Start installer session] --> B[Clear prior inputs and credentials]
  B --> C{Valid cidata input?}
  C -->|Yes| D[Load current configuration]
  C -->|No| E[Run configurator]
  E --> F{Open cfdisk?}
  F -->|No| G[Continue configuration]
  F -->|Yes| H[Read and snapshot partition table]
  H -->|Failure| I[Refuse partition tool]
  H -->|Success| J[Run cfdisk]
  J --> K{Existing partition shrank?}
  K -->|No| G
  K -->|Yes| L[Restore previous table]
  L -->|Failure| M[Abort installation]
  L -->|Success| I
  D --> N{Configuration exists?}
  G --> N
  N -->|No| M
  N -->|Yes| O[Start installer dashboard]
Loading

Reviews (3): Last reviewed commit: "Clear the previous attempt's SSH and Tai..." | Re-trigger Greptile

say --foreground 1 "cfdisk Resize only changes the partition table. It does not shrink the filesystem."
say "The previous table could not be restored automatically. Do not continue this install."
abort "Aborted: shrinking an existing partition would corrupt the other OS."
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed restore does not stop install

When sfdisk fails to restore a shrunk partition table, this branch calls abort, but .automated_script.sh ignores the configurator's nonzero exit and unconditionally launches the installer dashboard. The install therefore continues after the safety guard reports that the existing filesystem remains behind a shortened partition boundary, producing a later failure or allowing retained configuration from an earlier attempt to operate against the damaged table.

Knowledge Base Used:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 96927a6, with one correction: .automated_script.sh sets set -euo pipefail at the top, and the else body inherits it (only the if condition is exempt), so a failing configurator did already terminate the script. The install was not actually reachable that way.

The guard is worth stating explicitly regardless, so the call is now ./configurator || exit 1, and a [[ -f user_configuration.json ]] gate covers both branches before the dashboard handoff. The launcher also clears the previous run's inputs first, since the abort message tells people to re-run it and a leftover configuration describes a partition layout the guard just rejected.

test/unit/installer-gate-test.sh runs the real handoff region against a sandboxed /root and asserts no install starts without a clean configurator exit and a configuration this run produced.

Comment thread configs/airootfs/root/configurator Outdated
Review found the guard only covered the case where it worked. Every way of
not knowing still returned "the table is fine", which is the one answer that
lets the install proceed over a filesystem that now sits past its partition
end.

A missing snapshot no longer opens cfdisk unguarded — without a baseline to
compare against and a dump to write back, a Resize is neither visible nor
recoverable, so the tool is refused and the user is sent to Windows Disk
Management. A table that will not read back after cfdisk, and a shrink whose
restore fails, now abort with their own screens instead of falling through.
parted is judged by its exit status rather than by whether it printed
anything: an unreadable disk arriving as empty output reads exactly like a
disk with no partitions.

The launcher has to honour that refusal for any of it to mean anything. It
now clears the previous run's outputs before starting — the abort tells
people to re-run it, and a leftover configuration describes a layout the
guard just rejected — and requires both a configurator that exited clean and
a configuration file before it hands off to the installer.

test/unit/installer-gate-test.sh runs the real handoff region against a
sandboxed /root, and the shrink guard tests now cover the failure branches:
no snapshot, an empty snapshot, a table that will not read back, and a
restore that fails with the shrink still on the table.
Comment thread configs/airootfs/root/.automated_script.sh Outdated
Review caught that the retry cleanup was scoped to the wizard's own outputs.
omarchy-cidata-load clears the full set before it copies, but that cleanup
sits behind the drive-found check and never runs when no cidata drive is
present. An autoinstall attempt that aborts and is retried with the drive
pulled therefore falls through to the wizard with authorized_keys and
tailscale_authkey still in /root, and the orchestrator treats both as
present-means-use-it — the new machine would authorize the imaging rig's SSH
key and enroll with its Tailscale key.

The launcher now clears the same set the loader does. The test compares the
two lists rather than trusting two hand-maintained copies, so a new optional
input on the loader side fails instead of quietly reopening the leak.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants