feat(disk): KEEPSAKE_ENABLED trades the archive for ~3x the usable media
The upload gate refuses any photo that would leave too little room to build the keepsake, and the archive needs both halves at once — so it demands `2.2 x media + 10 GB reserve + 1.5 GB headroom`. Every gigabyte of photos therefore costs about 3.2 GB of disk budget, and usable media works out at `(disk - 11.5 GB) / 3.2`. On a 30 GB volume that is only ~5.8 GB of photos. Measured, not theorised: a 945-photo wedding (8.74 GB of real files) stopped after ~780 photos with 24.98 GB free — the gate wanted 25.91 GB on a disk that was 79% empty. Uploads ended for want of an archive nobody had asked for yet. `KEEPSAKE_ENABLED=false` drops the archive term, leaving only `DISK_RESERVE_BYTES` — the one question still live without an archive, which is whether Postgres can still write. That turns the same 30 GB volume into ~20 GB of usable media. Verified against the exact disk state that ended the run: the photo that returned 413 with the keepsake armed is accepted with it off. The headroom term goes with it, deliberately. It exists only to keep this gate strictly ahead of the EXPORT PREFLIGHT, and with no keepsake there is no preflight to stay ahead of. Boot-time immutable, like COMMENTS_ENABLED: flipping it mid-event would move the gate under uploads already accepted against the old one. Releasing the gallery and minting download tickets are refused while it is off, and the host dashboard hides the release control rather than offering a button that answers 403 — a new `keepsake_enabled` field on `GET /host/event` carries that. The gate and the host's low-disk banner now share `upload_gate_required_free`. They were already two expressions of one threshold, and the banner exists to fire BEFORE the gate closes; applying the switch to only one of them would warn the host about a limit that can no longer fire, or stay silent past one that can. Tests pin the lead in both modes. The cost is real and worth stating plainly: there is no downloadable gallery at the end, so guests keep only what they save from the feed. Prefer a bigger disk where you can — ~45 GB holds this library with the keepsake intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,8 @@
|
||||
export_released: boolean;
|
||||
disk_free_bytes: number | null;
|
||||
keepsake_required_bytes: number;
|
||||
/** False when KEEPSAKE_ENABLED=false — no downloadable gallery for this event. */
|
||||
keepsake_enabled: boolean;
|
||||
disk_low: boolean;
|
||||
}
|
||||
|
||||
@@ -736,6 +738,11 @@
|
||||
>
|
||||
{event.uploads_locked ? 'Uploads wieder öffnen' : 'Uploads sperren'}
|
||||
</button>
|
||||
<!-- Hidden entirely when the keepsake is switched off (KEEPSAKE_ENABLED=false):
|
||||
there is no archive to build, the backend refuses the call with 403, and a
|
||||
button that always errors reads as a broken app rather than a disabled
|
||||
feature. The note below says so in words instead. -->
|
||||
{#if event.keepsake_enabled}
|
||||
<button
|
||||
onclick={() =>
|
||||
(confirmAction = {
|
||||
@@ -756,6 +763,12 @@
|
||||
>
|
||||
{event.export_released ? 'Galerie bereits freigegeben' : 'Galerie freigeben'}
|
||||
</button>
|
||||
{:else}
|
||||
<p class="self-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Der Galerie-Download ist für dieses Event deaktiviert. Gäste speichern Fotos
|
||||
direkt aus der Galerie.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Live keepsake status: after release the ZIP/HTML still take time to build, and
|
||||
|
||||
Reference in New Issue
Block a user