app store: uninstall removes containers and never data

There is now no uninstall option that deletes data, rather than a careful one that does. A user
uninstalling a sidecar is saying "stop running this", which is not the same sentence as "delete my photo
library", and for Immich or Jellyfin getting that wrong once is unrecoverable. No confirmation dialog
makes it a good default.

So: `docker compose down` without `-v`. Containers and networks go; the service directory and everything
under it stays exactly as it was.

The bind-mount convention already makes this hard to get wrong, which is worth noting because it means
the safety is structural rather than a rule someone has to keep following. Data lives on the host inside
the service directory, so `-v` — which only removes NAMED volumes — could not delete it even if a future
change added the flag back.

`mode: 'existing'` has no disposal question at all: we did not create that service, so uninstall removes
our sidecar and our rows and touches nothing else.

Reclaiming disk becomes its own feature later, with the sizes in front of the user — "Photos is using
340 GB, delete it?" — as a deliberate act rather than a checkbox inside an uninstall flow.

Removed two stale `down -v` references that survived the first pass, one in the schema comment and one
in the design doc's table. Leftovers like those are how a rule becomes permission again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 13:44:30 +00:00
co-authored by Claude Opus 5
parent ee38257856
commit 936b96a36e
2 changed files with 10 additions and 8 deletions
+2 -3
View File
@@ -143,10 +143,9 @@ Two independent flags, because they answer different questions:
That yields the three outcomes asked for: That yields the three outcomes asked for:
| Action | Effect | | Action | Effect |
| ------------------------ | ------------------------------------------------------------------------------------ | | ------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Disable** | Stop the sidecar. Container, config, schema and data all stay. Re-enable is instant. | | **Disable** | Stop the sidecar. Container, config, schema and data all stay. Re-enable is instant. |
| **Uninstall, keep data** | Stop, remove the process. Leave container volumes and rows. | | **Uninstall** | Stop the sidecar, `docker compose down` — containers and networks removed. **The service directory and everything in it stays.** |
| **Full uninstall** | Also `docker compose down -v` and drop the sidecar's tables. |
The middle one is the in-between; the user chooses disposal at uninstall time rather than us guessing. The middle one is the in-between; the user chooses disposal at uninstall time rather than us guessing.
@@ -23,8 +23,11 @@ import { pgTable, serial, text, boolean, timestamp, jsonb, uniqueIndex } from 'd
// SHOULD BE RUNNING. Disabling is the reversible middle ground the owner asked for — stop the process, // SHOULD BE RUNNING. Disabling is the reversible middle ground the owner asked for — stop the process,
// keep the container, the config, the tables and the data, and start again later at no cost. // keep the container, the config, the tables and the data, and start again later at no cost.
// //
// Uninstall then has a disposal choice rather than a fixed meaning: keep the data, drop the container, // Uninstall stops the sidecar and removes the containers. It does NOT remove data, and there is no
// or drop both. None of those are this table's business beyond recording that the row is gone. // option that does: the service directory and everything under it survives. A user uninstalling a
// sidecar is saying "stop running this", not "delete my photo library", and the two are unrecoverably
// different for Immich and Jellyfin. Reclaiming disk is a separate, deliberate feature with the sizes
// shown — not a checkbox in an uninstall flow.
export const sidecarInstalls = pgTable( export const sidecarInstalls = pgTable(
'sidecar_installs', 'sidecar_installs',
{ {
@@ -67,7 +70,7 @@ export const sidecarInstalls = pgTable(
* Absolute path to the rendered compose directory, for `mode: 'provisioned'` only. * Absolute path to the rendered compose directory, for `mode: 'provisioned'` only.
* *
* Stored rather than derived because it is the user's directory and he may move it — and because * Stored rather than derived because it is the user's directory and he may move it — and because
* uninstall must not guess at a path it is about to run `docker compose down -v` in. * uninstall must not guess at a path it is about to run `docker compose down` in.
*/ */
composeDir: text('compose_dir'), composeDir: text('compose_dir'),
installedAt: timestamp('installed_at', { withTimezone: true }), installedAt: timestamp('installed_at', { withTimezone: true }),