OAuth Google Drive: use a plain folder path instead of a folder ID
A real OAuth-authorized account has its own "My Drive" with actual storage, so unlike the service-account path (which has none of its own and can only write into an explicitly shared-by-ID folder), there's no need to hunt down a numeric folder ID — a plain path segment works and rclone creates it automatically on first upload if it doesn't exist yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0cea565fd5
commit
e773b19508
5 changed files with 44 additions and 17 deletions
|
|
@ -112,6 +112,7 @@ def backups_list():
|
|||
"backup_gdrive_client_secret",
|
||||
"backup_gdrive_scope",
|
||||
"backup_gdrive_token",
|
||||
"backup_gdrive_folder_path",
|
||||
"backup_s3_provider",
|
||||
"backup_s3_access_key_id",
|
||||
"backup_s3_secret_access_key",
|
||||
|
|
@ -146,6 +147,7 @@ def backups_remote_settings():
|
|||
Setting.set("backup_gdrive_client_secret", request.form.get("backup_gdrive_client_secret", "").strip())
|
||||
Setting.set("backup_gdrive_scope", request.form.get("backup_gdrive_scope", "").strip())
|
||||
Setting.set("backup_gdrive_token", request.form.get("backup_gdrive_token", "").strip())
|
||||
Setting.set("backup_gdrive_folder_path", request.form.get("backup_gdrive_folder_path", "").strip())
|
||||
Setting.set("backup_s3_provider", request.form.get("backup_s3_provider", "AWS").strip() or "AWS")
|
||||
Setting.set("backup_s3_access_key_id", request.form.get("backup_s3_access_key_id", "").strip())
|
||||
Setting.set("backup_s3_secret_access_key", request.form.get("backup_s3_secret_access_key", "").strip())
|
||||
|
|
|
|||
|
|
@ -639,6 +639,7 @@ DEFAULT_SETTINGS = {
|
|||
"backup_gdrive_client_secret": "",
|
||||
"backup_gdrive_scope": "",
|
||||
"backup_gdrive_token": "",
|
||||
"backup_gdrive_folder_path": "",
|
||||
"backup_s3_provider": "AWS",
|
||||
"backup_s3_access_key_id": "",
|
||||
"backup_s3_secret_access_key": "",
|
||||
|
|
|
|||
|
|
@ -78,14 +78,14 @@
|
|||
</div>
|
||||
|
||||
<div id="fields-gdrive" class="backup-remote-fields">
|
||||
<label>ID папки Google Drive <span class="muted">(спільне поле для обох способів нижче — обов'язково для сервісного акаунта, необов'язково для OAuth: порожнє значення означає весь «Мій диск»)</span></label>
|
||||
<input type="text" name="backup_gdrive_folder_id" value="{{ remote_values.backup_gdrive_folder_id }}" placeholder="1AbCдEfGhIjKlMnOpQrStUvWxYz">
|
||||
|
||||
<p class="muted field-hint">Заповніть <b>або</b> «Сервісний акаунт», <b>або</b> «OAuth» нижче — не обидва одразу.</p>
|
||||
<p class="muted field-hint" style="margin-top:0;">Заповніть <b>або</b> «Сервісний акаунт», <b>або</b> «OAuth» нижче — не обидва одразу.</p>
|
||||
|
||||
<label>Сервісний акаунт: ключ Google (JSON)</label>
|
||||
<textarea name="backup_gdrive_service_account_json" rows="4" placeholder='{"type": "service_account", "client_email": "...", ...}'>{{ remote_values.backup_gdrive_service_account_json }}</textarea>
|
||||
|
||||
<label>ID папки Google Drive <span class="muted">(обов'язково для сервісного акаунта — у нього немає власного диска, тож без явно розшареної папки йому нема куди писати)</span></label>
|
||||
<input type="text" name="backup_gdrive_folder_id" value="{{ remote_values.backup_gdrive_folder_id }}" placeholder="1AbCдEfGhIjKlMnOpQrStUvWxYz">
|
||||
|
||||
<details style="margin-top:12px;">
|
||||
<summary style="cursor:pointer;font-weight:700;font-size:13px;">Сервісний акаунт — як налаштувати (крок за кроком)</summary>
|
||||
<ol class="muted" style="font-size:12.5px;line-height:1.8;padding-left:20px;margin-top:10px;">
|
||||
|
|
@ -116,13 +116,16 @@
|
|||
<label>token</label>
|
||||
<textarea name="backup_gdrive_token" rows="3" placeholder='{"access_token": "...", "refresh_token": "...", ...}'>{{ remote_values.backup_gdrive_token }}</textarea>
|
||||
|
||||
<label>Шлях до папки <span class="muted">(необов'язково — просто назва, без ID; порожньо = корінь «Мій диск». Якщо такої папки ще немає, rclone створить її сам)</span></label>
|
||||
<input type="text" name="backup_gdrive_folder_path" value="{{ remote_values.backup_gdrive_folder_path }}" placeholder="напр. Бекапи/BoberBBQ">
|
||||
|
||||
<details style="margin-top:12px;">
|
||||
<summary style="cursor:pointer;font-weight:700;font-size:13px;">OAuth — як налаштувати (крок за кроком)</summary>
|
||||
<ol class="muted" style="font-size:12.5px;line-height:1.8;padding-left:20px;margin-top:10px;">
|
||||
<li>Встановіть rclone на своєму комп'ютері (не на сервері) — <a href="https://rclone.org/downloads/" target="_blank">rclone.org/downloads</a>.</li>
|
||||
<li>Запустіть <code>rclone config</code>, оберіть «New remote» → тип <b>drive</b>, погодьтесь на автентифікацію через браузер (увійдіть у свій Google-акаунт).</li>
|
||||
<li>Коли готово, відкрийте створений конфіг (шлях покаже сама команда, зазвичай <code>~/.config/rclone/rclone.conf</code>) і скопіюйте значення <code>client_id</code>, <code>client_secret</code>, <code>scope</code>, <code>token</code> у поля вище.</li>
|
||||
<li>ID папки — необов'язково для OAuth (за замовчуванням доступний весь «Мій диск» акаунта, з яким ви увійшли); заповніть, якщо хочете обмежити конкретною папкою.</li>
|
||||
<li>«Шлях до папки» — звичайна назва (не ID); можна лишити порожнім, і бекапи ляжуть у корінь «Мій диск» того акаунта, з яким ви увійшли.</li>
|
||||
<li>Збережіть, натисніть «Перевірити підключення».</li>
|
||||
</ol>
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ def _remote_env_and_path() -> tuple[dict, str]:
|
|||
service_account_json = Setting.get("backup_gdrive_service_account_json", "").strip()
|
||||
client_id = Setting.get("backup_gdrive_client_id", "").strip()
|
||||
token = Setting.get("backup_gdrive_token", "").strip()
|
||||
folder_id = Setting.get("backup_gdrive_folder_id", "").strip()
|
||||
|
||||
env[prefix + "TYPE"] = "drive"
|
||||
if service_account_json:
|
||||
|
|
@ -85,14 +84,19 @@ def _remote_env_and_path() -> tuple[dict, str]:
|
|||
json.loads(service_account_json)
|
||||
except json.JSONDecodeError as e:
|
||||
raise BackupError(f"Невалідний JSON ключа сервісного акаунта: {e}") from e
|
||||
env[prefix + "SERVICE_ACCOUNT_CREDENTIALS"] = service_account_json
|
||||
folder_id = Setting.get("backup_gdrive_folder_id", "").strip()
|
||||
if not folder_id:
|
||||
# A service account has no Drive storage of its own — it
|
||||
# can only write into a folder that's been explicitly
|
||||
# shared with it, so this isn't optional the way it is for
|
||||
# a real user's OAuth-authorized "My Drive" below.
|
||||
# shared with it (root_folder_id is a backend option, not
|
||||
# part of the path, so rclone can't just "create" this the
|
||||
# way it can for a real OAuth-authorized account below).
|
||||
raise BackupError("Для сервісного акаунта обов'язково вкажіть ID папки Google Drive")
|
||||
elif client_id and token:
|
||||
env[prefix + "SERVICE_ACCOUNT_CREDENTIALS"] = service_account_json
|
||||
env[prefix + "ROOT_FOLDER_ID"] = folder_id
|
||||
return env, f"{_REMOTE}:"
|
||||
|
||||
if client_id and token:
|
||||
try:
|
||||
json.loads(token)
|
||||
except json.JSONDecodeError as e:
|
||||
|
|
@ -105,13 +109,16 @@ def _remote_env_and_path() -> tuple[dict, str]:
|
|||
if scope:
|
||||
env[prefix + "SCOPE"] = scope
|
||||
env[prefix + "TOKEN"] = token
|
||||
else:
|
||||
raise BackupError(
|
||||
"Вкажіть або ключ сервісного акаунта, або client_id + token (OAuth) для Google Drive"
|
||||
)
|
||||
if folder_id:
|
||||
env[prefix + "ROOT_FOLDER_ID"] = folder_id
|
||||
return env, f"{_REMOTE}:"
|
||||
# A real OAuth-authorized account has its own "My Drive" with
|
||||
# real storage, so — unlike the service-account path above —
|
||||
# there's no numeric folder id to hunt down: a plain path
|
||||
# segment is enough, and rclone creates it on first upload if
|
||||
# it doesn't exist yet.
|
||||
folder_path = Setting.get("backup_gdrive_folder_path", "").strip().strip("/")
|
||||
remote_path = f"{_REMOTE}:{folder_path}" if folder_path else f"{_REMOTE}:"
|
||||
return env, remote_path
|
||||
|
||||
raise BackupError("Вкажіть або ключ сервісного акаунта, або client_id + token (OAuth) для Google Drive")
|
||||
|
||||
if remote_type == "s3":
|
||||
access_key_id = Setting.get("backup_s3_access_key_id", "").strip()
|
||||
|
|
|
|||
|
|
@ -77,6 +77,20 @@ def test_gdrive_oauth_env_and_path(app):
|
|||
assert path == "backup:"
|
||||
|
||||
|
||||
def test_gdrive_oauth_folder_path_is_a_plain_path_not_an_id(app):
|
||||
with app.app_context():
|
||||
Setting.set("backup_remote_type", "gdrive")
|
||||
Setting.set("backup_gdrive_service_account_json", "")
|
||||
Setting.set("backup_gdrive_client_id", "CLIENT123")
|
||||
Setting.set("backup_gdrive_token", '{"access_token": "a"}')
|
||||
Setting.set("backup_gdrive_folder_path", "/Бекапи/BoberBBQ/")
|
||||
db.session.commit()
|
||||
|
||||
env, path = backup_remote._remote_env_and_path()
|
||||
assert "RCLONE_CONFIG_BACKUP_ROOT_FOLDER_ID" not in env
|
||||
assert path == "backup:Бекапи/BoberBBQ"
|
||||
|
||||
|
||||
def test_gdrive_neither_method_configured_raises(app):
|
||||
with app.app_context():
|
||||
Setting.set("backup_remote_type", "gdrive")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue