Шукати в цьому блозі

неділя, 13 вересня 2026 р.

Перенос 2FA з Google Authenticator у GNOME Authenticator (Debian/GNOME)

Проблема

GNOME Authenticator (com.belmoussaoui.Authenticator, Flatpak/Flathub) не вміє напряму читати QR-код "Transfer accounts / Export accounts" з Google Authenticator. Цей QR несе спеціальний формат otpauth-migration://offline?data=... (base64 + Protocol Buffers), а не звичайний otpauth://, який очікує сканер додатку. Ні камера, ні скріншот-сканер це не підхоплюють. Це підтверджений баг/обмеження проєкту, а не помилка користувача.

Рішення: extract_otp_secrets

Репозиторій: https://github.com/scito/extract_otp_secrets (⚠️ раніше називався extract_otp_secret_keys — якщо гуглиться стара назва, файл скрипта тепер лежить у src/extract_otp_secrets.py, не в корені).

Скрипт розбирає otpauth-migration://... і віддає звичайні otpauth:// записи (або одразу JSON/CSV/Aegis-експорт).

Кроки

  1. Отримати сирі дані з QR. На телефоні: Google Authenticator → меню → Transfer accounts → Export accounts → з'являється QR. Відсканувати його будь-яким сканером QR (не обов'язково камерою ПК) — вийде текстовий рядок otpauth-migration://offline?data=.... Якщо QR декілька (багато акаунтів) — кожен рядок в окремий рядок текстового файлу.

  2. Зберегти рядок(и) у текстовий файл, наприклад export.txt (по одному otpauth-migration://... на рядок).

  3. Встановити скрипт в ізольований venv (щоб не смітити систему):

    git clone https://github.com/scito/extract_otp_secrets.git
    cd extract_otp_secrets
    python3 -m venv venv && source venv/bin/activate
    pip install -r requirements.txt
    

    ⚠️ requirements.txt тягне важкі й непотрібні для цього кейсу пакети (torch, opencv, CUDA — потрібні лише для розпізнавання QR з фото/камери через YOLO). Якщо на вході вже готовий текстовий рядок (data=...), а не картинка — усе це зайве, але видалити з requirements.txt важко без ризику зламати залежності, тому просто змиритися й прибрати venv після використання (розділ "Прибирання").

  4. Розпарсити:

    python3 src/extract_otp_secrets.py export.txt --json result.json
    

    Отримаєш name, issuer, secret, algorithm для кожного акаунту.

  5. Завести кожен акаунт у GNOME Authenticator вручну — кнопка "+" → ручний ввід (Issuer / Account / Secret) з даних result.json.

  6. Перенос на інші машини (робочий ПК тощо). Скрипт вміє одразу експортувати у формат, який читає Aegis (--json/--csv + опціонально шифрований контейнер) — так і зробили: експорт у формат Aegis із паролем, файл переносимо на інший комп'ютер і імпортуємо туди штатним імпортом Aegis-бекапу. Зручно, якщо кінцевий застосунок на іншій машині підтримує Aegis-формат краще, ніж пряме ручне заведення.

Прибирання (важливо!)

Проміжні файли містять секрети у відкритому вигляді:

shred -u export.txt result.json

Зашифрований Aegis-експорт (з паролем) можна лишити для переносу, але краще не тримати його довше, ніж треба, і не в хмарних синках без додаткового шифрування диску.

venv зі скриптом важить кілька гігабайт (torch + CUDA-пакети) — після використання просто видалити разом з клоном:

deactivate
rm -rf extract_otp_secrets   # або шлях, куди клонував

Підсумок / чеклист на майбутнє

  • [ ] GNOME Authenticator сам QR від Google Authenticator не читає — одразу
    йти шляхом extract_otp_secrets, не гаяти час на спроби сканування.
    
  • [ ] Файл скрипта: src/extract_otp_secrets.py (не в корені).
  • [ ] Вхід: текстовий файл з otpauth-migration://..., без картинок —
    найпростіший і найшвидший шлях, важкі image-залежності можна
    ігнорувати.
    
  • [ ] Після імпорту — shred проміжних файлів, видалення venv.

субота, 29 серпня 2026 р.

Автомонтування Google Drive через rclone на Debian + GNOME: історія для однієї філіжанки кави

Захотілося простого: підключити Google Drive як звичайну папку в файловій системі, і щоб воно саме піднімалось при вході в систему, без танців з бубном щоразу. Здавалося б, rclone mount — і готово. Виявилось трохи цікавіше.

Крок 1. rclone і конфіг, якого нема

Перша спроба:

rclone mount gdrive: ~/GDrive --daemon

У відповідь:

Failed to create file system for "gdrive:": didn't find section in config file

Логічно — remote gdrive в конфізі просто не існував. Створити порожній файл командою rclone config touch теж не рятує: файл є, секції нема.

Рішення: інтерактивний майстер rclone config. Він сам генерує правильну секцію в ~/.config/rclone/rclone.conf і проводить через OAuth-авторизацію в браузері — 2FA при цьому обробляється звичайним способом, через сам Google, rclone до неї жодного стосунку не має. На машині з GUI (а в мене GNOME) достатньо погодитись на Use auto config? (y) — rclone підійме локальний вебсервер і сам відкриє браузер для логіну.

Крок 2. Автозапуск при логіні — перша перешкода: D-Bus

Щоб не монтувати вручну щоразу, оформив systemd user-юніт:

[Unit]
Description=RClone mount Google Drive
AssertPathIsDirectory=%h/GDrive
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
ExecStart=/usr/bin/rclone mount gdrive: %h/GDrive \
    --vfs-cache-mode writes \
    --config=%h/.config/rclone/rclone.conf
ExecStop=/bin/fusermount -u %h/GDrive
Restart=on-failure
RestartSec=10

[Install]
WantedBy=default.target

І одразу вперся в:

Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined

Спробував виставити loginctl enable-linger, спробував вручну експортувати змінні — не допомагало. Причина виявилась банальною і трохи соромною: у .bashrc завалявся старий

alias systemctl='sudo systemctl'

А sudo за замовчуванням очищує середовище процесу. Тобто щойно виставлені XDG_RUNTIME_DIR і DBUS_SESSION_BUS_ADDRESS до команди просто не доходили — sudo їх зрізав ще до старту systemctl. Найприкріше, що для керування власними user-юнітами (systemctl --user) права root взагалі не потрібні.

Рішення: викликати бінарник напряму, оминаючи alias:

/usr/bin/systemctl --user daemon-reload
/usr/bin/systemctl --user enable --now rclone-gdrive.service

І заразом не завадить loginctl enable-linger <user> — щоб user-сесія (і user-юніти) піднімались навіть без активного графічного логіну.

Крок 3. Сервіс стартує і одразу падає

Failed to create file system for "gdrive:": didn't find section in config file

Та сама помилка, що й на самому початку — але тепер конфіг точно існував. Загадка розкрилась при прямому запуску команди в терміналі (без --daemon, щоб бачити живий вивід):

/usr/bin/rclone mount gdrive: ~/GDrive --vfs-cache-mode writes --config=/home/olden/.config/rclone/rclone.conf

Та сама помилка. Але варто було спробувати з великої літери:

/usr/bin/rclone mount GDrive: ~/GDrive --vfs-cache-mode writes --config=/home/olden/.config/rclone/rclone.conf

— і диск підключився. rclone чутливий до регістру назви remote, а в конфізі секція виявилась записана як [GDrive], а не [gdrive], як я вважав. Дрібниця, яка коштувала половини вечора.

Виправив у юніті:

sed -i 's/rclone mount gdrive:/rclone mount GDrive:/' ~/.config/systemd/user/rclone-gdrive.service

Крок 4. Порожня папка після, здавалося б, вдалого монтування

Сервіс піднявся, systemctl --user status показував active (running), а ls ~/GDrive — порожньо. При тому ручний запуск хвилиною раніше показував повний список файлів.

Причина — накладені (stale) точки монтування. Я тестував ручний запуск rclone mount просто в терміналі, потім намагався його відмонтувати через fusermount3 -u, але процес до кінця не прибрався. Коли systemd підняв новий rclone mount поверх недомонтованого старого шару, ядро показувало нову, ще порожню точку.

Розібратись допомогла така послідовність:

/usr/bin/systemctl --user stop rclone-gdrive.service
fusermount3 -u ~/GDrive          # повторити кілька разів, якщо треба
pkill -f "rclone mount"          # добити зомбі-процеси
ps aux | grep rclone             # переконатись, що чисто
/usr/bin/systemctl --user start rclone-gdrive.service

Після цього ls ~/GDrive нарешті показав реальні файли.

Підсумок: робочий юніт

[Unit]
Description=RClone mount Google Drive
AssertPathIsDirectory=%h/GDrive
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/rclone mount GDrive: %h/GDrive \
    --vfs-cache-mode writes \
    --config=%h/.config/rclone/rclone.conf
ExecStop=/bin/fusermount -u %h/GDrive
Restart=on-failure
RestartSec=10

[Install]
WantedBy=default.target
loginctl enable-linger $USER
/usr/bin/systemctl --user daemon-reload
/usr/bin/systemctl --user enable --now rclone-gdrive.service

(Type=notify теж мав шанс спрацювати — версія rclone тут доволі свіжа, — але коли шукаєш причину падіння, простіше тимчасово спростити до Type=simple, щоб виключити зайву змінну з рівняння.)

Висновки на майбутнє

  • Alias на sudo systemctl і systemctl --user — погана комбінація. Sudo зрізає середовище, а --user-юнітам права root взагалі не потрібні.
  • rclone чутливий до регістру назв remote. gdrive: і GDrive: — це два різні (точніше, один існуючий і один неіснуючий) remote.
  • Перед діагностикою systemd-юніта корисно спершу прогнати ту саму команду вручну в терміналі — так помилка видно одразу, без прошарку journalctl.
  • Stale FUSE-монтування — підступна штука: systemctl status може казати active (running), а точка монтування при цьому виявиться порожньою, бо накладена на недомонтований попередній шар. pkill -f + чистий stop/start знімає це надійно.

Дрібниця на дрібниці, але кожна — окрема пастка, у яку легко провалитись саме тому, що симптом (одна й та сама помилка «not found in config file») мав дві геть різні причини на різних етапах.

середа, 13 травня 2026 р.

Дублювання репозиторію на GitHub

Якось часто останнім часом доводиться робити дублювання власних репозиторіїв на GitHub. Залишу за дужками питання "чому?", але додам до нотатків "як?".

Створюємо новий репозиторій.

На GitHub -> "New repository". Даємо йому назву. "Create repository" (не ініціалізуємо його файлами).

Клонуємо старий репозиторій як дзеркало

git clone --mirror https://github.com/посилання/на_старий_репозиторій.git

Переходимо в директорію

cd назва_старого_репозиторію.git

Відправляємо дані в новий репозиторій

git push --mirror git@github.com:посилання/на_новий_репозиторій.git

Видаляємо директорію

cd ..
rm назва_старого_репозиторію.git