CluPilotCloud/app/Provisioning/Steps/Customer
nexxo 5b63fdb86c Deliver the storage a customer actually buys
A booked storage pack was a row in `subscription_addons` and nothing else:
priced, frozen, charged every month, and delivered nowhere. Nextcloud's
quota came from `instances.quota_gb`, which is the package alone; the disk
was sized for the package alone; and on the one occasion a disk did grow —
a plan upgrade — the guest never saw it, because nothing stretched the
partition or the filesystem over the new space.

One authority for the allowance. StorageAllowance adds the package and the
booked packs, and everything that needs the figure asks it: the quota step,
the downgrade check, the portal, the console, the repair command. It is
DERIVED rather than stored — a column would have to be rewritten by booking,
cancelling, granting, a grant expiring and a plan change, and the day one of
those paths forgot it would be silently wrong in whichever direction costs
somebody money. `quota_applied_gb` keeps its own meaning: what the guest was
last actually told, which is how a machine with the allowance enforced is
told from one where the figure has only ever been a row in our database.

Buying it delivers it. BookAddon asks ApplyStorageAllowance for a run on
booking AND on cancellation; the new `storage` pipeline grows the virtual
disk to the allowance plus the package's own overhead (read off the package
— 20/40/50/100 GB on the four catalogue packages — never a ratio invented
here), then GrowGuestFilesystem makes the guest see it, then the quota is
applied. No cold boot is involved: the data disk is scsi0 and Proxmox's
resize on a running guest is a qemu block_resize, so the capacity reaches
the guest while it runs. The new step rescans, growparts and grows the
filesystem with the tool its type actually needs — ext2/3/4, xfs, btrfs —
and fails loudly on one it does not know rather than handing it to resize2fs
and hoping. Idempotent end to end: NOCHANGE from growpart is not a failure,
and every tool here exits 0 when there is nothing left to grow.

The same step now runs in the plan-change pipeline, where an upgrade used to
stop one step short of the customer.

A downgrade blocked by data gets a way out. The block stays — it is correct
— but the check now measures against the target package PLUS the packs the
customer already owns, and reports the numbers behind the refusal: what is
stored, what would be allowed, how much has to go, and how many packs would
cover it instead. The portal offers both routes: book exactly those packs
(confirmed in a modal, R23), or delete data and have the fill level measured
on demand rather than waiting for the nightly sampler. That reading is taken
by DiskUsageProbe, which CollectInstanceTraffic now uses too, so there is one
notion of "how full is it" and not two.

A plan change keeps booked packs. They were paid for separately and have
nothing to do with which package the customer is on; the new package's disk
and quota are sized with them included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:13:10 +02:00
..
ApplyStorageQuota.php Deliver the storage a customer actually buys 2026-07-29 19:13:10 +02:00
CloneVirtualMachine.php fix(engine-b): clean re-clone on lost task ref; idempotent firewall rules 2026-07-25 13:04:48 +02:00
CompleteProvisioning.php Stop mailing the initial admin password, hold it in the panel until noted 2026-07-28 23:19:20 +02:00
CompleteRestart.php Restart a machine, enforce the quota that was sold, end a route that ended 2026-07-29 18:28:28 +02:00
ConfigureCloudInit.php fix(engine-b): apply plan CPU/RAM; idempotent backup + monitoring registration 2026-07-25 12:42:10 +02:00
ConfigureDnsAndTls.php Serve the custom domain, not just announce it 2026-07-29 16:44:52 +02:00
ConfigureNetwork.php fix(engine-b): address Codex round 5 (capacity release, activate-last, guest-ip poll, email unique) 2026-07-25 12:19:31 +02:00
ConfigureNextcloud.php Serve the custom domain, not just announce it 2026-07-29 16:44:52 +02:00
CreateCustomerAdmin.php fix(engine-b): real backup/monitoring registration + OC_PASS scoping 2026-07-25 12:33:45 +02:00
CustomerStep.php fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
DeployApplicationStack.php fix(engine-b): address Codex round 6 (crash-window idempotency) 2026-07-25 12:23:28 +02:00
GrowGuestFilesystem.php Deliver the storage a customer actually buys 2026-07-29 19:13:10 +02:00
RegisterBackup.php fix(engine-b): clone recovery waits for lock; register rows before breadcrumb 2026-07-25 12:44:22 +02:00
RegisterMonitoring.php Add the SSH identity to the vault, and give deployment config a console page 2026-07-29 00:52:44 +02:00
ReserveResources.php Take the order, park it, and say when it will be delivered 2026-07-29 18:50:46 +02:00
ResizeVirtualMachine.php Deliver the storage a customer actually buys 2026-07-29 19:13:10 +02:00
RunAcceptanceChecks.php Add the SSH identity to the vault, and give deployment config a console page 2026-07-29 00:52:44 +02:00
SettlePlanServices.php Apply a bought plan change instead of only pricing it 2026-07-29 17:18:55 +02:00
ShutDownVirtualMachine.php Restart a machine, enforce the quota that was sold, end a route that ended 2026-07-29 18:28:28 +02:00
StartVirtualMachine.php feat(engine-b): 15-step customer pipeline + DNS/Traefik services 2026-07-25 11:50:26 +02:00
ValidateOrder.php fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
WaitForGuestAgent.php fix(engine): don't reset started_at on poll so step deadlines accumulate 2026-07-25 12:15:26 +02:00