👔
Docker版RcloneでGoogleDriveの認証情報を取得・rcloneコマンド実行 (headless版)
Docker版RcloneでGoogleDriveの認証情報を取得・rcloneコマンド実行のheadless版
TL;DR
- Seleniumをheadless起動で認証操作
背景
- 何が何でもGUIを使いたくない
- 自動認証したい
※圧倒的にGUI手動認証のほうが楽なのでお勧めできない。
手順
詳細後述
auto_rclone.py
import os
import shutil
from argparse import ArgumentParser, Namespace
from signal import SIGTERM
from tempfile import TemporaryDirectory
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from undetected_chromedriver import Chrome
class AutoRclone:
def __init__(
self,
user_data_dir: str,
driver_executable_path: str,
browser_executable_path: str,
) -> None:
options: Options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
self.chrome: Chrome = Chrome(
options=options,
user_data_dir=user_data_dir,
driver_executable_path=driver_executable_path,
browser_executable_path=browser_executable_path,
)
def google_drive(self, url: str, email: str, password: str) -> bool:
print("get", url)
self.chrome.get(url)
print("email")
email_element: WebElement = WebDriverWait(self.chrome, 15).until(
EC.element_to_be_clickable((By.NAME, "identifier"))
)
email_element.send_keys(email)
self.chrome.find_element(By.ID, "identifierNext").click()
print("password")
password_element: WebElement = WebDriverWait(self.chrome, 15).until(
EC.element_to_be_clickable((By.NAME, "Passwd"))
)
password_element.send_keys(password)
self.chrome.find_element(By.ID, "passwordNext").click()
print("approve")
approve_element: WebElement = WebDriverWait(self.chrome, 15).until(
EC.element_to_be_clickable((By.ID, "submit_approve_access"))
)
approve_element.click()
print("result")
result_element: WebElement = WebDriverWait(self.chrome, 15).until(
EC.presence_of_element_located((By.TAG_NAME, "pre"))
)
return "All done. Please go back to rclone." in result_element.text
def quit(self) -> None:
print("quit")
os.kill(self.chrome.browser_pid, SIGTERM)
def auto_rclone(args: Namespace) -> None:
with TemporaryDirectory(ignore_cleanup_errors=True) as dname:
rclone: AutoRclone = AutoRclone(
dname,
args.driver,
args.chrome,
)
try:
if args.google_drive:
print(
"auth google drive:",
rclone.google_drive(
args.url,
os.environ["GOOGLE_EMAIL"],
os.environ["GOOGLE_PASSWORD"],
),
)
else:
print("nothing to do")
finally:
rclone.quit()
shutil.rmtree(dname) if os.path.exists(dname) else None
if __name__ == "__main__":
parser: ArgumentParser = ArgumentParser()
parser.add_argument("url", help="target url")
parser.add_argument("driver", help="driver path")
parser.add_argument("chrome", help="chrome path")
parser.add_argument(
"-gd", "--google_drive", action="store_true", help="auth google drive"
)
auto_rclone(parser.parse_args())
.env
GOOGLE_EMAIL=アカウント@gmail.com
GOOGLE_PASSWORD=パスワード
command
docker run -itd --name rclonepython -v ./auto_rclone.py:/root/auto_rclone.py --env-file=./.env python:3.12.1-bookworm
command
docker exec -it --workdir=/root/ rclonepython bash -c "wget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip && \
unzip chromedriver_linux64.zip && \
wget https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/114.0.5735.90/linux64/chrome-linux64.zip && \
unzip chrome-linux64.zip && \
apt update && \
apt install -y \
rclone \
libnss3 \
libdbus-1-3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libasound2 && \
pip install undetected-chromedriver==3.5.4 && \
rclone config"
以下ログが出るまで進める。
command
****/**/** **:**:** NOTICE: If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth?state=***
****/**/** **:**:** NOTICE: Log in and authorize rclone for access
****/**/** **:**:** NOTICE: Waiting for code...
別ターミナルを開き、上記URLを用いて以下実行
command
docker exec -it rclonepython python /root/auto_rclone.py http://127.0.0.1:53682/auth?state=*** /root/chromedriver /root/chrome-linux64/chrome -gd
以下のようなログが出れば認証成功
could not detect version_main.therefore, we are assuming it is chrome 108 or higher
get http://127.0.0.1:53682/auth?state=***
email
password
approve
result
auth google drive: True
quit
認証待ち側のターミナルが進められるようになる。
****/**/** **:**:** NOTICE: Got code
Configure this as a Shared Drive (Team Drive)?
y) Yes
n) No (default)
y/n>
Configuration complete.
Options:
- type: drive
- scope: drive
- token: {"access_token":"アクセストークン","token_type":"Bearer","refresh_token":"リフレッシュトークン","expiry":"有効期限"}
- team_drive:
Keep this "gd" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d>
Current remotes:
Name Type
==== ====
gdrive drive
e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q>
スクリプト説明
- ブラウザ自動化を検知されるとパスワード入力ができなくなるので、undetected_chromedriverを使用
from undetected_chromedriver import Chrome
- Google Drive以外でも使うかもしれないことを想定して、引数-gdが与えられた時のみ実行
parser.add_argument(
"-gd", "--google_drive", action="store_true", help="auth google drive"
)
- ユーザーデータは毎回初期化するために一時ディレクトリを使用
with TemporaryDirectory(ignore_cleanup_errors=True) as dname:
rclone: AutoRclone = AutoRclone(
dname,
- メール・パスワードはコマンドに残したくないので環境変数から読み込む
rclone.google_drive(
args.url,
os.environ["GOOGLE_EMAIL"],
os.environ["GOOGLE_PASSWORD"],
),
- ログイン・許可の実際の画面要素を取得しながら操作
※仕様変更されたり本人確認挟まれたりしたら動作しない。
def google_drive(self, url: str, email: str, password: str) -> bool:
・・・
- Chrome.quit()だとプロセスが残ったりするのでkill(プロファイルも削除するため)
os.kill(self.chrome.browser_pid, SIGTERM)
- TemporaryDirectoryのブロックを抜ける時にディレクトリが削除できない場合があるので、抜けた後に削除
shutil.rmtree(dname) if os.path.exists(dname) else None
Discussion