Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ Keyring and provides a native GTK control center for the details that matter.
- Independent upload/download file-data limits, guided connection tuning and
guarded upload-slot, cache-retention, metadata-refresh and restart controls.
- Conservative health monitoring with desktop notifications.
- Guided first setup, native account reauthorization, encrypted credentials and
signed rclone updates.
- Guided first setup, contextual same-account reauthorization, guarded account
switching with isolated cache namespaces, encrypted credentials and signed
rclone updates.

This is an on-demand filesystem, not a full offline mirror. Reads download data
when needed; writes remain protected in the local VFS cache until uploaded.
Expand Down Expand Up @@ -99,6 +100,24 @@ untouched while the replacement login is verified. A real Proton rate limit is
distinguished from rejected credentials and persists a local retry time so no
UI or manual service start can create another premature login attempt.

For an intentional migration to another Proton account, open **Preferences →
Account → Change Proton account …**. PDrive refuses while any transfer, queued
upload or Dirty cache file remains, tests the candidate login in isolation, and
mounts it only through a fresh anonymous cache namespace. If activation fails,
the previous encrypted configuration, authentication state and cache namespace
are restored automatically.

<table>
<tr>
<td><img src="docs/assets/pdrive-account-settings.png" alt="PDrive Preferences account section with the guarded Change Proton account action"></td>
<td><img src="docs/assets/pdrive-account-switch.png" alt="PDrive guarded Proton account-change dialog after a successful safety preflight"></td>
</tr>
<tr>
<td align="center"><sub>A deliberate account action, separate from contextual reauthorization</sub></td>
<td align="center"><sub>Full native credentials, explicit confirmation and cache-isolation promise</sub></td>
</tr>
</table>

Existing configuration, credentials, cache and state are preserved when the
installer is run again.

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7.4
0.7.5
110 changes: 110 additions & 0 deletions bin/pdrive-account-switch
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-3.0-or-later

set -euo pipefail
umask 077

readonly installed_backend="${PDRIVE_ACCOUNT_SWITCH_BACKEND:-${HOME}/.local/libexec/switch-rclone-proton-account}"
source_backend="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../libexec" && pwd)/switch-rclone-proton-account"
readonly source_backend

usage() {
printf '%s\n' \
'Usage: pdrive-account-switch' \
' pdrive-account-switch --switch' \
' pdrive-account-switch --help' \
'' \
'Guarded migration of the managed /pdrive mount to another Proton account.' \
'' \
'No option and --help show only this action-free guidance.' \
'--switch starts one interactive, transactional account change.' \
'' \
'Safety workflow:' \
' 1. refuses active uploads, downloads, queued writes or Dirty VFS data;' \
' 2. reads the candidate username, password and optional 2FA privately;' \
' 3. authenticates the candidate in an isolated encrypted configuration;' \
' 4. rechecks the preflight before stopping the current mount;' \
' 5. assigns a new anonymous cache namespace and activates atomically;' \
' 6. restores the previous configuration and namespace if validation fails.' \
'' \
'Previous cache namespaces and encrypted rollback data are retained.' \
'Credentials never enter argv, environment variables, logs or diagnostics.'
}

case "${1:-}" in
'')
(( $# == 0 )) || { usage >&2; exit 2; }
usage
exit 0
;;
--switch)
(( $# == 1 )) || { usage >&2; exit 2; }
;;
-h|--help)
(( $# == 1 )) || { usage >&2; exit 2; }
usage
exit 0
;;
*)
printf 'Unknown option: %s (help: pdrive-account-switch --help)\n' "$1" >&2
exit 2
;;
esac

backend="${installed_backend}"
if [[ ! -x "${backend}" && -x "${source_backend}" ]]; then
backend="${source_backend}"
fi
if [[ ! -x "${backend}" ]]; then
printf 'Account-switch backend is missing: %s\n' "${backend}" >&2
exit 69
fi
if [[ ! -t 0 || ! -t 1 ]]; then
printf 'Run this command in a normal interactive terminal.\n' >&2
exit 64
fi

printf '%s\n' \
'PDrive Proton account change' \
'============================' \
'' \
'This changes the remote account mounted at /pdrive. The current account' \
'configuration and cache stay separate and are retained for rollback.' \
'Pending or active work blocks the change without modifying anything.' \
''

"${backend}" --preflight
printf '\nType CHANGE ACCOUNT to confirm this intentional migration: '
IFS= read -r confirmation
if [[ "${confirmation}" != 'CHANGE ACCOUNT' ]]; then
printf 'Cancelled; nothing was changed.\n'
exit 0
fi

read -r -p 'New Proton username or email address: ' proton_username
read -r -s -p 'New Proton password: ' proton_password
printf '\n'
read -r -s -p 'Repeat password: ' proton_password_confirm
printf '\n'
if [[ -z "${proton_username}" || -z "${proton_password}" \
|| "${proton_password}" != "${proton_password_confirm}" ]]; then
unset proton_password proton_password_confirm
printf 'Username is empty or passwords do not match.\n' >&2
exit 65
fi
unset proton_password_confirm
read -r -s -p 'Fresh 6-digit 2FA code (empty when disabled): ' proton_2fa
printf '\n'
if [[ -n "${proton_2fa}" && ! "${proton_2fa}" =~ ^[0-9]{6}$ ]]; then
unset proton_password proton_2fa
printf 'The 2FA code must be empty or exactly six digits.\n' >&2
exit 65
fi

set +e
printf '%s\0' "${proton_username}" "${proton_password}" "${proton_2fa}" \
| "${backend}" --switch-from-stdin
switch_rc=$?
set -e
unset proton_username proton_password proton_2fa
exit "${switch_rc}"
20 changes: 19 additions & 1 deletion bin/pdrive-draft-recovery
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,26 @@ umask 077
readonly draft_config="${HOME}/.config/pdrive-draft-recovery.conf"
readonly recovery_config="${HOME}/.config/pdrive-recovery.conf"
readonly service_name='rclone-proton-drive.service'
readonly vfs_meta_root="${HOME}/.cache/rclone/vfsMeta"
readonly auto_helper="${HOME}/.local/libexec/pdrive-draft-recovery-auto"
source_account_cache_helper="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../libexec" && pwd)/pdrive-account-cache"
readonly source_account_cache_helper
readonly account_cache_helper="${PDRIVE_ACCOUNT_CACHE_HELPER:-${HOME}/.local/libexec/pdrive-account-cache}"

resolve_cache_dir() {
local helper="${account_cache_helper}"
if [[ ! -x "${helper}" && -x "${source_account_cache_helper}" ]]; then
helper="${source_account_cache_helper}"
fi
if [[ ! -x "${helper}" ]]; then
printf 'Account cache resolver is missing: %s\n' "${helper}" >&2
return 69
fi
"${helper}" --path
}

cache_dir="$(resolve_cache_dir)"
readonly cache_dir
readonly vfs_meta_root="${cache_dir}/vfsMeta"

usage() {
printf '%s\n' \
Expand Down
20 changes: 19 additions & 1 deletion bin/pdrive-recovery
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,25 @@ umask 077

readonly recovery_config="${HOME}/.config/pdrive-recovery.conf"
readonly service_name='rclone-proton-drive.service'
readonly vfs_meta_root="${HOME}/.cache/rclone/vfsMeta"
source_account_cache_helper="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../libexec" && pwd)/pdrive-account-cache"
readonly source_account_cache_helper
readonly account_cache_helper="${PDRIVE_ACCOUNT_CACHE_HELPER:-${HOME}/.local/libexec/pdrive-account-cache}"

resolve_cache_dir() {
local helper="${account_cache_helper}"
if [[ ! -x "${helper}" && -x "${source_account_cache_helper}" ]]; then
helper="${source_account_cache_helper}"
fi
if [[ ! -x "${helper}" ]]; then
printf 'Account cache resolver is missing: %s\n' "${helper}" >&2
return 69
fi
"${helper}" --path
}

cache_dir="$(resolve_cache_dir)"
readonly cache_dir
readonly vfs_meta_root="${cache_dir}/vfsMeta"

usage() {
printf '%s\n' \
Expand Down
20 changes: 19 additions & 1 deletion bin/pdrive-refresh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,25 @@ readonly rc_socket="${HOME}/.local/state/rclone/pdrive-rc.sock"
readonly rclone_bin="${HOME}/.local/bin/rclone"
readonly watch_bin="${HOME}/.local/bin/pdrive-watch"
readonly recovery_config="${HOME}/.config/pdrive-recovery.conf"
readonly vfs_meta_root="${HOME}/.cache/rclone/vfsMeta"
source_account_cache_helper="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../libexec" && pwd)/pdrive-account-cache"
readonly source_account_cache_helper
readonly account_cache_helper="${PDRIVE_ACCOUNT_CACHE_HELPER:-${HOME}/.local/libexec/pdrive-account-cache}"

resolve_cache_dir() {
local helper="${account_cache_helper}"
if [[ ! -x "${helper}" && -x "${source_account_cache_helper}" ]]; then
helper="${source_account_cache_helper}"
fi
if [[ ! -x "${helper}" ]]; then
printf 'Account cache resolver is missing: %s\n' "${helper}" >&2
return 69
fi
"${helper}" --path
}

cache_dir="$(resolve_cache_dir)"
readonly cache_dir
readonly vfs_meta_root="${cache_dir}/vfsMeta"

usage() {
printf '%s\n' \
Expand Down
2 changes: 1 addition & 1 deletion bin/pdrive-state
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ from typing import Any


SCHEMA_VERSION = 1
TOOL_VERSION = "0.7.4"
TOOL_VERSION = "0.7.5"
RECENT_TRANSFER_WINDOW_SECONDS = 24 * 60 * 60
RECENT_TRANSFER_LIMIT = 24
MOUNT_LOG_TAIL_BYTES = 512 * 1024
Expand Down
Loading