mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
docs: restructure and update documentation for 2.5.0
Split the README into a short front page and task-oriented pages under docs/: installation, user guide, CLI reference, configuration and troubleshooting, with an index in docs/README.md. Rewrite RECOVERY.md around Continue This Backup Here and Restore Folders, and DEVELOPMENT.md around running, debugging and releasing from source. Bring ARCHITECTURE.md up to date with the read path, push events, the thread map, conflict copies and the machine profile, and move the feature design notes there. Collect planned work in ROADMAP.md. Link the threat model from SECURITY.md and the translation rules from CONTRIBUTING.md. Add scripts/publish-wiki.sh, which mirrors the user pages to the GitHub wiki with rewritten links and only pushes with --push.
This commit is contained in:
1 parent
4b90abf3b5
commit
af869944cf
16 files changed
+1960
-873
No files matched your search
+119
-213
@@ -1,262 +1,168 @@
|
||||
# Recovery: Getting a Replacement Machine Back onto Your Device
|
||||
# Recovering on a new computer
|
||||
|
||||
You lose the laptop. You install `pdfs` on a new one. What does it take to get the
|
||||
synced folders back?
|
||||
Your laptop is lost, stolen or reinstalled. This page explains what survives, and how to get
|
||||
your synced folders back on the replacement machine.
|
||||
|
||||
This document is two things: **§2 is the runbook that works with the client as it
|
||||
stands today**, gotchas included, and **§3 is the plan to reduce that runbook to
|
||||
one command.** §1 is the part neither can fix — what actually died with the
|
||||
machine.
|
||||
- [1. What survives](#1-what-survives)
|
||||
- [2. Restore your synced folders](#2-restore-your-synced-folders)
|
||||
- [3. How the restore works](#3-how-the-restore-works)
|
||||
- [4. Known gaps](#4-known-gaps)
|
||||
|
||||
---
|
||||
## 1. What survives
|
||||
|
||||
## 1. What survives, and what does not
|
||||
### On Proton Drive
|
||||
|
||||
### Survives (it is on Drive, or derivable)
|
||||
|
||||
| Thing | Where it lives |
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| Every file that finished uploading | Proton Drive |
|
||||
| The Device registration itself | Proton Drive (`enumerate_devices`) |
|
||||
| The device's folder tree — one folder per synced folder | Proton Drive, under the device root |
|
||||
| Your account, keys, sharing | Proton |
|
||||
| The computer's registration and its backup | **Computers** in the app and the web |
|
||||
| One folder per synced folder, under that computer | Proton Drive |
|
||||
| The machine profile: which folders were synced, to which local path, in which mode; pins; ignore patterns | `.proton-drive-linux/profile.json` under the computer's backup, end-to-end encrypted like any other file |
|
||||
| Your account, keys and shares | Proton |
|
||||
|
||||
### Does not survive
|
||||
The service rewrites the profile a short while after you add, remove or switch a synced folder,
|
||||
or change a pin.
|
||||
|
||||
| Thing | Why | Recoverable? |
|
||||
### Lost with the machine
|
||||
|
||||
| What | Why | Recoverable? |
|
||||
|---|---|---|
|
||||
| Undrained `staging/` + `recovery/` blobs | Writes the kernel accepted but that never reached Drive. They existed only on that disk. | **No.** |
|
||||
| Local-only files in a mirror folder that had not uploaded yet | Same. | **No.** |
|
||||
| `sync_folder` rows — which local path maps to which remote folder, and in which mode | Local SQLite (`cache.db`) | Rebuildable by hand (§2), not automatically (§3) |
|
||||
| `sync_entry` baseline | Local SQLite | Not needed. An empty baseline reconciles correctly — see §2.4. |
|
||||
| Pins | Local SQLite | Rebuildable by hand |
|
||||
| `config.json` — mountpoint, cache budget | Local config dir | Rebuildable by hand |
|
||||
| `cache.db` node/FTS/photo caches | Local SQLite | Pure cache; rebuilds itself |
|
||||
| Changes still in `staging/` or `recovery/` | Writes that were accepted locally but never reached Proton Drive | **No** |
|
||||
| New files in a synced folder that had not uploaded yet | Same | **No** |
|
||||
| `config.json`: mountpoint, cache limit, bandwidth limits, appearance | Local config directory | Set again in **Preferences** |
|
||||
| The local database and content cache | Local disk | Not needed; rebuilt from Proton Drive |
|
||||
|
||||
The first two rows are the only genuinely unrecoverable ones, and today nothing
|
||||
warns you they are non-empty before you walk away from a machine. That is tracked
|
||||
separately from this document as a pending-writes visibility gap.
|
||||
Only the first two rows are real losses. Before you retire a working machine, check that
|
||||
`pdfs sync queue` prints "Nothing queued" (or that **Sync → Overview** shows nothing waiting).
|
||||
|
||||
### Before anything else: revoke
|
||||
### Before anything else: revoke the old session
|
||||
|
||||
The lost machine holds a decrypted content cache, a plaintext index of every node
|
||||
name in your Drive (`cache.db`), and a live session in its keyring. Whether that
|
||||
matters depends on whether its disk was encrypted — but the session does not.
|
||||
The lost machine holds a decrypted content cache, a plain-text index of your file names and a
|
||||
live session in its keyring. Whether its disk was encrypted decides how much of that is exposed,
|
||||
but the session is valid either way.
|
||||
|
||||
**Revoke the session from your Proton account settings before restoring.** Nothing
|
||||
in this client can do that for you; the daemon on the lost machine holds
|
||||
credentials it will happily keep refreshing.
|
||||
**Sign the lost machine out from your Proton account's security settings before you restore.**
|
||||
The client cannot do that for you.
|
||||
|
||||
If you have also lost your password, Proton's recovery phrase is what determines
|
||||
whether the data is reachable at all. That is upstream of this client entirely: if
|
||||
zero-knowledge encryption worked as advertised, nobody at Proton can help.
|
||||
If you have also lost your password, account recovery is Proton's matter, not this client's.
|
||||
Proton Drive is end-to-end encrypted: without the password or a recovery method, the data cannot
|
||||
be decrypted.
|
||||
|
||||
---
|
||||
## 2. Restore your synced folders
|
||||
|
||||
## 2. The runbook that works today
|
||||
### Step 1: Install and sign in
|
||||
|
||||
### 2.1 Install, log in
|
||||
Install the client as described in [INSTALL.md](INSTALL.md) and sign in with `pdfs-app`. Signing
|
||||
in from the app also starts the service.
|
||||
|
||||
```bash
|
||||
pdfs login # stores the session in the keyring
|
||||
systemctl --user start proton-drive # or let autostart do it
|
||||
```
|
||||
### Step 2: Continue the old computer's backup
|
||||
|
||||
### 2.2 Re-adopt the device — check this before anything else
|
||||
Tell this machine which computer it replaces, so new changes go into the existing backup instead
|
||||
of a second, empty one.
|
||||
|
||||
`Core::ensure_device` recovers an existing device by **matching a Linux device
|
||||
whose name equals this machine's hostname**, read from
|
||||
`/proc/sys/kernel/hostname`. So:
|
||||
- **In the app:** open **Computers**, open the menu of the old computer and choose
|
||||
**Continue This Backup Here…**.
|
||||
- **From a terminal:**
|
||||
|
||||
```bash
|
||||
hostnamectl hostname # what this machine will register as
|
||||
pdfs devices list # what the account already has
|
||||
```
|
||||
```bash
|
||||
pdfs devices list # find the old computer's UID
|
||||
pdfs devices adopt UID
|
||||
```
|
||||
|
||||
- **Hostnames match** → the new machine silently re-adopts the old device and its
|
||||
root folder. This is the happy path, and it is why the rest of the runbook works.
|
||||
- **Hostnames differ** → `pdfs` registers a **second device**. Nothing errors.
|
||||
Your old device and all its folders are still there, but this machine is not
|
||||
attached to them, and every step below will quietly create empty folders under
|
||||
the new device instead.
|
||||
Adoption takes effect at once and is stored in `config.json` as `device_uid`. It survives a
|
||||
change of hostname. `pdfs devices adopt --clear` returns to the default, which is to match a
|
||||
computer whose name equals this machine's hostname.
|
||||
|
||||
> **The single most important step in this document:** if the hostnames differ,
|
||||
> set the new machine's hostname to match the old device's name *before* adding
|
||||
> any sync folders.
|
||||
> ```bash
|
||||
> sudo hostnamectl set-hostname OLD-DEVICE-NAME
|
||||
> ```
|
||||
> There is no supported way to adopt a device by uid yet — see §3.1.
|
||||
> [!NOTE]
|
||||
> Without adoption, the client only finds the old computer if this machine has the same
|
||||
> hostname. Otherwise it registers a new computer, and nothing points at the old backup.
|
||||
|
||||
### 2.3 Find out which folders the device had
|
||||
### Step 3: Restore the folders
|
||||
|
||||
```bash
|
||||
pdfs devices list # confirm the device and note its name
|
||||
```
|
||||
- **In the app:** open **Computers**, open the menu of **This computer** and choose
|
||||
**Restore Folders…**. Confirm or change the local path of each folder.
|
||||
- **From a terminal:**
|
||||
|
||||
The device's synced folders are the folders directly under its root in Drive.
|
||||
Browse them in the GUI's Computers page, or in the Proton Drive web app under
|
||||
that device. **Write down their exact names** — you need them in the next step.
|
||||
```console
|
||||
$ pdfs sync restore
|
||||
Documents [mirror] -> /home/you/Documents (Enter to accept, 'n' to skip, or a path)
|
||||
Projects [ondemand] -> /home/you/Projects (Enter to accept, 'n' to skip, or a path)
|
||||
```
|
||||
|
||||
### 2.4 Re-attach each folder
|
||||
Press Enter to accept a path, type `n` to skip the folder, or type another path. `--yes`
|
||||
accepts every proposal without asking.
|
||||
|
||||
For each folder name you noted, create the local directory and add it:
|
||||
For each folder you accept, the service creates the local directory, attaches it to the folder in
|
||||
the backup, restores its mode (**Mirror** or **Online only**) and downloads it. Follow the
|
||||
progress on **Sync → Overview** or with `pdfs sync list`.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/Documents
|
||||
pdfs sync add ~/Documents
|
||||
```
|
||||
> [!TIP]
|
||||
> Restore into empty or missing directories. If a directory already holds files, for example
|
||||
> from another backup, the client cannot tell which side is newer. It keeps both: your local file
|
||||
> is renamed to a `(sync-conflict …)` copy and the version from Proton Drive is downloaded. See
|
||||
> [Conflicts](USER_GUIDE.md#conflicts).
|
||||
|
||||
`add_sync_folder` looks for a folder under the device root named after the local
|
||||
directory's **basename**, and reuses it if found rather than creating a second
|
||||
one. So `~/Documents` re-attaches to the existing `Documents`. With an empty
|
||||
baseline and an empty local directory, the first reconcile classifies every remote
|
||||
path as `(None, Some(remote))` with no baseline → **download**, and the folder
|
||||
refills from Drive.
|
||||
### Step 4: Restore the rest by hand
|
||||
|
||||
Three things to be careful about:
|
||||
- **Available offline files.** The profile records them, but the restore does not apply them yet.
|
||||
Mark them again in the app, or with `pdfs pin PATH`.
|
||||
- **Preferences.** Set the cache limit, bandwidth limits, mountpoint and appearance again.
|
||||
|
||||
- **The basename must match exactly.** Restoring to `~/docs` when the device
|
||||
folder is `Documents` does not re-attach — it creates a *new*, empty `docs`
|
||||
folder in Drive and syncs nothing. The failure is silent; you just see an empty
|
||||
directory. Check with `pdfs sync list` and the Computers page.
|
||||
- **Add the folder empty, or accept conflict copies.** If the local directory
|
||||
already has files (restored from a backup, say), there is no baseline to say
|
||||
which side is newer, so both sides read as changed and the reconcile takes the
|
||||
conflict arm: your local file is renamed to a `(sync-conflict <ts>)` copy and
|
||||
the remote version is downloaded. Nothing is lost, but it is noisy. Restoring
|
||||
into an empty directory avoids it.
|
||||
- **A wholly empty local tree is now safe.** Before the audit fixes, adding a
|
||||
folder whose local side was empty against a populated remote could, on a later
|
||||
pass, be read as "the user deleted everything" and trash the lot. `sync.rs`'s
|
||||
`guard_local_wipe` now refuses any pass where every baseline path has vanished
|
||||
locally, which is exactly the restore shape.
|
||||
### Step 5: Clean up
|
||||
|
||||
### 2.5 Restore modes, pins, and settings by hand
|
||||
|
||||
```bash
|
||||
pdfs sync list # note the ids
|
||||
pdfs sync mode <id> ondemand # for folders that were on-demand
|
||||
```
|
||||
|
||||
Nothing records which folders were `ondemand` before, so this is from memory. Pins
|
||||
and the cache budget/mountpoint likewise: set them again from the Settings page.
|
||||
|
||||
### 2.6 Clean up
|
||||
|
||||
If a duplicate device got created before you noticed the hostname mismatch:
|
||||
If this machine registered a second computer before you adopted the old one, remove it:
|
||||
|
||||
```bash
|
||||
pdfs devices list
|
||||
pdfs devices rm <uid-of-the-stray-device>
|
||||
pdfs devices rm UID
|
||||
```
|
||||
|
||||
Check what is under it in the web app first. Deleting a device you actually
|
||||
attached folders to will take its folders with it.
|
||||
> [!WARNING]
|
||||
> Removing a computer deletes its backup from Proton Drive. Check what is under it in
|
||||
> **Computers → Browse Files** first.
|
||||
|
||||
---
|
||||
### Restoring another computer's folders
|
||||
|
||||
## 3. The plan: make this one command
|
||||
To bring folders from a computer that still exists, for example a desktop whose documents you want
|
||||
on a laptop, use **Restore to This Computer…** on that computer's row, or:
|
||||
|
||||
The pieces all exist — device enumeration, folder-name reuse, an empty-baseline
|
||||
reconcile that downloads correctly. What is missing is that the restore is
|
||||
**implicit, name-based, and undiscoverable**. Each phase below removes one of
|
||||
those properties. They are independent and land in this order by value.
|
||||
```bash
|
||||
pdfs sync restore --device UID
|
||||
```
|
||||
|
||||
### P1 — Adopt a device explicitly, by uid
|
||||
The folders stay part of the other computer's backup and sync in both directions. This machine
|
||||
does not take over that computer's backup.
|
||||
|
||||
**Problem:** device identity is inferred from the hostname. A new laptop with a
|
||||
new name silently forks a second device, and the failure looks like "my files
|
||||
didn't come back" rather than an error.
|
||||
## 3. How the restore works
|
||||
|
||||
- Add `Request::AdoptDevice { uid }`: validate the uid against
|
||||
`enumerate_devices`, write the `StoredDevice` row, and refuse if any
|
||||
`sync_folder` rows already reference a different device.
|
||||
- `pdfs devices adopt <uid>`, and an "Adopt this device" action on rows in the
|
||||
GUI's Computers page that are not the current machine.
|
||||
- Make the fork *visible* rather than silent: when `ensure_device` is about to
|
||||
create a device and the account already has other Linux devices, log a warning
|
||||
naming them, and surface it in `pdfs status`.
|
||||
This section is for the curious and for anyone debugging a restore.
|
||||
|
||||
Keep hostname matching as the default. It is right for the common case; it just
|
||||
must not be the *only* way.
|
||||
- **Which computer the machine is.** At start, the service uses `device_uid` from `config.json`
|
||||
when it is set. When it is not, it looks for a Linux computer in the account whose name equals
|
||||
the hostname, and registers a new one when there is none. An adopted UID that no longer exists
|
||||
in the account is reported in the log instead of being replaced silently.
|
||||
- **What can be restored.** `pdfs sync restore` lists the folders directly under the computer's
|
||||
backup, except `.proton-drive-linux`. Folders already synced on this machine are skipped.
|
||||
- **Proposed paths.** When the profile names a local path whose parent directory exists on this
|
||||
machine, that path is proposed. Otherwise the proposal is `~/<folder name>`. The service never
|
||||
writes to a path you have not confirmed.
|
||||
- **Attaching by UID.** The restore binds each local path to the remote folder's UID, not to its
|
||||
name. Restoring `Documents` into `~/docs` works.
|
||||
- **Downloading.** A restored folder starts with an empty sync baseline. Against an empty local
|
||||
directory, every remote file counts as new and is downloaded. Files present on both sides with
|
||||
no baseline become conflict copies, as described above.
|
||||
- **The wipe guard.** A sync pass in which every previously synced file has vanished locally is
|
||||
refused instead of being mirrored to Proton Drive as a mass deletion. A folder that was emptied
|
||||
by accident therefore does not empty the backup.
|
||||
- **The profile format.** `profile.json` carries a `version` field. A client refuses a profile
|
||||
written by a newer, incompatible client instead of applying part of it.
|
||||
|
||||
### P2 — Discover and re-attach the device's folders
|
||||
## 4. Known gaps
|
||||
|
||||
**Problem:** §2.3 and §2.4 are "remember what you had, then retype it exactly."
|
||||
|
||||
- Add `Request::ListDeviceFolders { device_uid }` — enumerate the device root's
|
||||
child folders and report name, node uid, child count, and total size. This is
|
||||
the "what did this machine sync?" answer that today requires the web app.
|
||||
- Add `Request::AttachSyncFolder { remote_uid, local_path, mode }` — the
|
||||
re-attach counterpart to `AddSyncFolder`. It binds a local path to a **specific
|
||||
remote uid** rather than resolving by basename, which removes the silent
|
||||
`~/docs` vs `Documents` failure entirely.
|
||||
- CLI: `pdfs sync restore [--device <uid>] [--into <dir>]` — list the device's
|
||||
folders, propose `<dir>/<name>` for each (default `$HOME`), and attach the ones
|
||||
the user confirms.
|
||||
- GUI: a "Restore folders from this device" flow on the Computers page, with a
|
||||
per-folder local-path picker defaulting to `~/<name>`.
|
||||
|
||||
Refuse to attach into a non-empty directory without an explicit
|
||||
`--allow-nonempty`, so the conflict-copy noise of §2.4 is opt-in rather than a
|
||||
surprise.
|
||||
|
||||
### P3 — Back the profile up to Drive
|
||||
|
||||
**Problem:** modes, pins, mountpoint, and cache budget exist only on the dead
|
||||
machine, so even a perfect P2 restores folders in the wrong mode with no pins.
|
||||
|
||||
Write a small JSON document into a hidden folder in the user's own Drive — say
|
||||
`.proton-drive-linux/profile.json` — containing, per device:
|
||||
|
||||
- sync folders: remote uid, the local path last used, and mode
|
||||
- pinned node uids
|
||||
- mountpoint and cache budget
|
||||
|
||||
It rides on Drive's own encryption, so this introduces no new crypto and no new
|
||||
place for plaintext to sit. Rewrite it whenever any of those change, debounced.
|
||||
|
||||
On a fresh install, after login: *"Found settings from `<device>`, last updated
|
||||
`<date>`. Restore?"* Node uids are stable across machines, so pins restore
|
||||
exactly. Local paths need remapping when the username or home differs — show the
|
||||
old path, default the new one to `$HOME/<basename>`.
|
||||
|
||||
Store the last-used local path only as a *hint*. Restoring a profile must never
|
||||
write to a path the user has not confirmed in this session.
|
||||
|
||||
### P4 — Make the unrecoverable visible
|
||||
|
||||
Nothing above helps with §1's undrained writes, because by then it is too late.
|
||||
The mitigation is to make the pending queue impossible to miss *before* the
|
||||
machine is lost:
|
||||
|
||||
- pending count and bytes in `pdfs status`, using the existing `pending` map and
|
||||
`TransferRegistry`
|
||||
- a tray icon state and a GUI banner while the queue is non-empty
|
||||
- a warning on logout or shutdown while it is non-empty
|
||||
|
||||
### P5 — Verification
|
||||
|
||||
None of the above is trustworthy without exercising the actual shape of the
|
||||
disaster. The end-to-end test is: register a device, sync a folder, upload
|
||||
content, **delete the local state directory and cache entirely** (simulating the
|
||||
lost machine), restart, restore, and assert the tree comes back byte-identical.
|
||||
|
||||
That is a two-account or at least two-state-dir integration test against a live
|
||||
account, not a unit test. Until the mock-backend problem described in
|
||||
`audit-plan.md` is solved, it stays manual — but it should be run, and its result
|
||||
recorded here, before P1–P3 are called done.
|
||||
|
||||
---
|
||||
|
||||
## 4. Current status
|
||||
|
||||
| Phase | State |
|
||||
| Gap | Effect |
|
||||
|---|---|
|
||||
| Manual runbook (§2) | Works, with the hostname and basename caveats |
|
||||
| P1 explicit adoption | Not started |
|
||||
| P2 discover + attach | Not started |
|
||||
| P3 profile on Drive | Not started |
|
||||
| P4 pending visibility | Not started |
|
||||
| P5 restore drill | Never run |
|
||||
| Pins are backed up but not restored | Mark files available offline again by hand |
|
||||
| No warning at shutdown or sign-out while changes are queued | Check `pdfs sync queue` before you retire a machine |
|
||||
| The full disaster drill (delete all local state, restore, compare byte for byte) has not been run against a live account | The individual steps are tested; the whole sequence is not |
|
||||
|
||||
Planned work is tracked in [ROADMAP.md](ROADMAP.md).
|
||||
Reference in new issue
Block a user