Skip to content

Commit 72c4df9

Browse files
authored
Merge pull request #24 from N3koSempai/feat/backups
[release] Feat/backups
2 parents bf59de5 + bf253cc commit 72c4df9

26 files changed

Lines changed: 4055 additions & 8 deletions

backup-feature-proposal.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Propuesta: Respaldo (Backup) y Restauración de Apps Flatpak
2+
3+
Estado: propuesta aprobada en discusión, pendiente de implementación.
4+
5+
## Objetivo
6+
7+
Permitir al usuario respaldar selectivamente las apps Flatpak instaladas, eligiendo
8+
por app si se incluyen sus datos (`~/.var/app/<app-id>`) o no, y poder restaurarlas
9+
después — incluso si la app ya no está disponible en Flathub o no hay red.
10+
11+
## Ubicación en la app
12+
13+
Sección dedicada de navegación **"Respaldos"** (nueva ruta, ítem de sidebar propio),
14+
en lugar de un botón dentro de "Mis Apps" o una acción por card. Motivo: el respaldo
15+
tiene estado propio (historial de backups hechos, ubicación en disco, restauración)
16+
que crecería mal colgado de un modal en `MyApps.tsx`. Una página propia da desde el
17+
inicio el lugar natural para listar backups existentes y lanzar restauraciones, sin
18+
tener que migrar la UX más adelante.
19+
20+
## Por qué no basta con copiar `~/.var/app/<id>` de vuelta
21+
22+
`~/.var/app/<app-id>` solo contiene datos/config/cache de la app — **no** el binario
23+
ni sus dependencias. El ejecutable real vive gestionado por OSTree en
24+
`/var/lib/flatpak/app/<id>/...` y requiere que Flatpak tenga la app *registrada*
25+
(refs, deployment, permisos de sandboxing) para poder ejecutarla. No existe una
26+
"carpeta portable de la app" que se pueda simplemente copiar; siempre hace falta
27+
un paso de instalación gestionado por Flatpak para tener algo ejecutable.
28+
29+
## Por qué no confiar en que Flathub retenga versiones viejas
30+
31+
Flathub es un repo OSTree: cada actualización crea un commit nuevo en la rama
32+
`stable`, pero los commits viejos son podados sin garantía de retención documentada.
33+
Es técnicamente posible pedir `flatpak install --commit=<hash>` para fijar una
34+
versión exacta, pero si Flathub ya podó ese commit, la instalación falla. Esta vía
35+
queda como **fallback opcional** (ver más abajo), no como mecanismo principal.
36+
37+
## Diseño principal: bundle local vía `flatpak build-bundle`
38+
39+
En vez de depender de un tercero (Flathub) para recuperar una versión exacta,
40+
cada backup **congela la app tal como está instalada localmente** en el momento
41+
del respaldo, usando `flatpak build-bundle` sobre el repo OSTree local del usuario.
42+
Esto la hace autocontenida y no depende de red ni de qué tan generoso sea Flathub
43+
reteniendo commits viejos.
44+
45+
### Contenido de cada backup de app
46+
47+
1. **`<app-id>.flatpak`** — bundle exportado vía `build-bundle` (la app exacta,
48+
versión congelada, generado desde el repo OSTree local).
49+
2. **`manifest.json`** — metadata: `app_id`, nombre, versión, ref del runtime exacto
50+
(id + arquitectura + rama + commit), permisos (`flatpak override`, ver abajo),
51+
fecha del respaldo.
52+
3. **`data.tar.zst`** *(opcional, según selección del usuario)* — contenido de
53+
`~/.var/app/<app-id>` comprimido con zstd.
54+
4. **Runtime del bundle** *(opcional, seleccionable por el usuario, ver sección de
55+
deduplicación)* — bundle del runtime exportado con `build-bundle --runtime`,
56+
para permitir restauración 100% offline.
57+
58+
### Permisos (`flatpak override`)
59+
60+
Los permisos otorgados a una app (`flatpak override`) **no viven** dentro de
61+
`~/.var/app/<app-id>` — viven en `~/.local/share/flatpak/overrides/<id>` (o el
62+
equivalente de sistema). Un backup ingenuo que solo copie `~/.var/app` los pierde.
63+
El `manifest.json` debe capturarlos explícitamente para poder reaplicarlos con
64+
`flatpak override` al restaurar.
65+
66+
## Selección de formato de compresión
67+
68+
`tar` + `zstd` (`.tar.zst`) para los datos de usuario. Comprime casi tan bien como
69+
xz pero mucho más rápido — relevante para carpetas de datos grandes (juegos, IDEs).
70+
Es además el códec que ya usa Flatpak/OSTree internamente, y el que usa Warehouse
71+
(la referencia principal del ecosistema) en producción para su feature de
72+
snapshots. Se implementa con las crates nativas `tar` y `zstd` en Rust (sin
73+
depender de binarios externos del sistema).
74+
75+
## Runtime: opción seleccionable + deduplicación entre backups
76+
77+
Incluir el runtime en el bundle es **opcional y seleccionable por el usuario**,
78+
igual que la opción "con datos" — no todos los backups necesitan ser 100% offline,
79+
y el runtime puede pesar mucho más que la app misma.
80+
81+
Problema a evitar: si el usuario respalda 5 apps que comparten `org.gnome.Platform//43`,
82+
no tiene sentido exportar el mismo runtime 5 veces.
83+
84+
### Solución: biblioteca compartida de runtimes por carpeta de destino
85+
86+
- La carpeta de destino de backups tiene una subcarpeta compartida `runtimes/`
87+
(no por-app).
88+
- Antes de exportar un runtime para una app, se comprueba si ya existe
89+
`runtimes/<runtime-id>-<version>-<arch>.flatpak` en esa carpeta. Si existe, no
90+
se vuelve a exportar: el `manifest.json` de la app simplemente referencia ese
91+
archivo compartido por nombre.
92+
- Si no existe, se exporta una vez (`build-bundle --runtime`) y queda disponible
93+
para que cualquier otro backup futuro en la misma carpeta lo reutilice.
94+
- El `manifest.json` de cada app guarda el ref del runtime y un puntero relativo
95+
a `../runtimes/<archivo>.flatpak` (no una copia).
96+
97+
### Resolución al restaurar
98+
99+
1. Si el runtime exacto ya está instalado en el sistema destino → se usa ese, se
100+
ignora cualquier bundle.
101+
2. Si no está instalado pero el bundle existe en `runtimes/` (ya sea de esta carpeta
102+
de backups o el usuario lo tiene junto al backup) → se instala desde ahí, sin red.
103+
3. Si no está instalado y no hay bundle disponible → se intenta traer desde Flathub
104+
por red como último recurso, y se avisa claramente al usuario si eso falla.
105+
106+
## Plan B (descartado como mecanismo principal, mantenido como fallback informativo)
107+
108+
Guardar el commit hash de Flathub en el manifiesto e intentar
109+
`flatpak install --commit=<hash> flathub <app-id>` como alternativa si por algún
110+
motivo no se dispone del bundle local (p. ej. el usuario perdió el archivo del
111+
bundle pero conserva el manifiesto). Si el commit fue podado, se debe avisar
112+
explícitamente al usuario y ofrecer instalar la última versión disponible —
113+
nunca fallar en silencio ni sustituir la versión sin decirlo.
114+
115+
## Validación contra el estado del arte (Warehouse)
116+
117+
Investigación de la app GNOME **Warehouse** (`io.github.flattool.Warehouse`,
118+
referencia principal del ecosistema para este problema):
119+
120+
- Su feature "Snapshots" **solo** respalda `~/.var/app/<app-id>` (datos), asumiendo
121+
que la app siempre se puede reinstalar después desde Flathub. Nunca empaqueta el
122+
binario ni el runtime.
123+
- Usa `tar` (shell-out al binario del sistema) con zstd para el archivo de datos,
124+
más un JSON sidecar con metadata — confirma la elección de zstd como estándar de
125+
facto.
126+
- No resuelve el caso "la app ya no está en Flathub" ni "quiero restaurar sin red" —
127+
hueco real que nuestro diseño con `build-bundle` sí cubre.
128+
- Conclusión: nuestro diseño es más completo que el estado del arte existente para
129+
el caso de backup verdaderamente offline/a-prueba-de-desaparición-en-Flathub, a
130+
costa de bundles más pesados que un simple tarball de datos.
131+
132+
## Gotchas técnicos a respetar en la implementación
133+
134+
- `build-bundle` exporta un solo ref por invocación (la app **o** el runtime, no
135+
ambos) — hace falta una invocación separada por cada uno.
136+
- GPG signing es opcional en `build-bundle`/`install`; no hace falta montar
137+
infraestructura de firmas para que los bundles se instalen localmente.
138+
- Sin bundle de runtime y sin runtime ya instalado en destino, la instalación
139+
offline de un bundle de app falla con "runtime not found" — de ahí la
140+
importancia de registrar el ref exacto del runtime en el manifiesto incluso
141+
cuando no se incluya el bundle del runtime.

cargo-sources.json

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,19 @@
14291429
"dest": "cargo/vendor/field-offset-0.3.6",
14301430
"dest-filename": ".cargo-checksum.json"
14311431
},
1432+
{
1433+
"type": "archive",
1434+
"archive-type": "tar-gzip",
1435+
"url": "https://static.crates.io/crates/filetime/filetime-0.2.29.crate",
1436+
"sha256": "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759",
1437+
"dest": "cargo/vendor/filetime-0.2.29"
1438+
},
1439+
{
1440+
"type": "inline",
1441+
"contents": "{\"package\": \"5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759\", \"files\": {}}",
1442+
"dest": "cargo/vendor/filetime-0.2.29",
1443+
"dest-filename": ".cargo-checksum.json"
1444+
},
14321445
{
14331446
"type": "archive",
14341447
"archive-type": "tar-gzip",
@@ -2599,6 +2612,19 @@
25992612
"dest": "cargo/vendor/jni-sys-0.3.0",
26002613
"dest-filename": ".cargo-checksum.json"
26012614
},
2615+
{
2616+
"type": "archive",
2617+
"archive-type": "tar-gzip",
2618+
"url": "https://static.crates.io/crates/jobserver/jobserver-0.1.35.crate",
2619+
"sha256": "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3",
2620+
"dest": "cargo/vendor/jobserver-0.1.35"
2621+
},
2622+
{
2623+
"type": "inline",
2624+
"contents": "{\"package\": \"1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3\", \"files\": {}}",
2625+
"dest": "cargo/vendor/jobserver-0.1.35",
2626+
"dest-filename": ".cargo-checksum.json"
2627+
},
26022628
{
26032629
"type": "archive",
26042630
"archive-type": "tar-gzip",
@@ -5381,6 +5407,19 @@
53815407
"dest": "cargo/vendor/tao-macros-0.1.3",
53825408
"dest-filename": ".cargo-checksum.json"
53835409
},
5410+
{
5411+
"type": "archive",
5412+
"archive-type": "tar-gzip",
5413+
"url": "https://static.crates.io/crates/tar/tar-0.4.46.crate",
5414+
"sha256": "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840",
5415+
"dest": "cargo/vendor/tar-0.4.46"
5416+
},
5417+
{
5418+
"type": "inline",
5419+
"contents": "{\"package\": \"3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840\", \"files\": {}}",
5420+
"dest": "cargo/vendor/tar-0.4.46",
5421+
"dest-filename": ".cargo-checksum.json"
5422+
},
53845423
{
53855424
"type": "archive",
53865425
"archive-type": "tar-gzip",
@@ -7682,6 +7721,19 @@
76827721
"dest": "cargo/vendor/x11-dl-2.21.0",
76837722
"dest-filename": ".cargo-checksum.json"
76847723
},
7724+
{
7725+
"type": "archive",
7726+
"archive-type": "tar-gzip",
7727+
"url": "https://static.crates.io/crates/xattr/xattr-1.6.1.crate",
7728+
"sha256": "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156",
7729+
"dest": "cargo/vendor/xattr-1.6.1"
7730+
},
7731+
{
7732+
"type": "inline",
7733+
"contents": "{\"package\": \"32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156\", \"files\": {}}",
7734+
"dest": "cargo/vendor/xattr-1.6.1",
7735+
"dest-filename": ".cargo-checksum.json"
7736+
},
76857737
{
76867738
"type": "archive",
76877739
"archive-type": "tar-gzip",
@@ -7877,6 +7929,45 @@
78777929
"dest": "cargo/vendor/zmij-1.0.21",
78787930
"dest-filename": ".cargo-checksum.json"
78797931
},
7932+
{
7933+
"type": "archive",
7934+
"archive-type": "tar-gzip",
7935+
"url": "https://static.crates.io/crates/zstd/zstd-0.13.3.crate",
7936+
"sha256": "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a",
7937+
"dest": "cargo/vendor/zstd-0.13.3"
7938+
},
7939+
{
7940+
"type": "inline",
7941+
"contents": "{\"package\": \"e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a\", \"files\": {}}",
7942+
"dest": "cargo/vendor/zstd-0.13.3",
7943+
"dest-filename": ".cargo-checksum.json"
7944+
},
7945+
{
7946+
"type": "archive",
7947+
"archive-type": "tar-gzip",
7948+
"url": "https://static.crates.io/crates/zstd-safe/zstd-safe-7.2.4.crate",
7949+
"sha256": "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d",
7950+
"dest": "cargo/vendor/zstd-safe-7.2.4"
7951+
},
7952+
{
7953+
"type": "inline",
7954+
"contents": "{\"package\": \"8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d\", \"files\": {}}",
7955+
"dest": "cargo/vendor/zstd-safe-7.2.4",
7956+
"dest-filename": ".cargo-checksum.json"
7957+
},
7958+
{
7959+
"type": "archive",
7960+
"archive-type": "tar-gzip",
7961+
"url": "https://static.crates.io/crates/zstd-sys/zstd-sys-2.0.16+zstd.1.5.7.crate",
7962+
"sha256": "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748",
7963+
"dest": "cargo/vendor/zstd-sys-2.0.16+zstd.1.5.7"
7964+
},
7965+
{
7966+
"type": "inline",
7967+
"contents": "{\"package\": \"91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748\", \"files\": {}}",
7968+
"dest": "cargo/vendor/zstd-sys-2.0.16+zstd.1.5.7",
7969+
"dest-filename": ".cargo-checksum.json"
7970+
},
78807971
{
78817972
"type": "archive",
78827973
"archive-type": "tar-gzip",

0 commit comments

Comments
 (0)