Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ Thumbs.db

# build-generated per-release (URLs+sha256 of the on-demand cloud addons)
module/etc/cloud_addons.json

__pycache__/
*.pyc
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ import existing Neo-Backup and Android-DataBackup backups so you can move over.
- **Compression:** none / lz4 / zstd / gzip (gzip = "compatibility mode", opens anywhere).
- **Encryption:** optional and portable (`age` + passphrase, or AES-256 zip) - never
tied to device hardware, so a backup restores on another device or opens on a PC.
- **Destinations:** local, SFTP, SCP, SMB, WebDAV, FTP, S3 and 70+ more (`rclone`),
plus `rsync` and `restic` repos. Optional Google Drive via a tiny opt-in companion.
- **Destinations:** local, Telegram Cloud Storage (private storage channel),
SFTP, SCP, SMB, WebDAV, FTP, S3 and 70+ more (`rclone`), plus `rsync` and `restic`
repos. Optional Google Drive via a tiny opt-in companion.
- **Integrity:** every archive is verified against the manifest; dry-run and
restore-test let you check a backup without touching anything.
- **Selective & batch restore:** restore any subset of a backup's items, or multi-select
Expand Down
11 changes: 11 additions & 0 deletions docs/USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,17 @@ automatically, so **no PC is needed** (pasting a token by hand still works as a
The remote then appears in the destination's remote list. Passwords are stored obscured,
never in clear text. You can list and delete your remotes from the same place.

### Telegram Cloud Storage

You can back up directly to your personal Telegram account as private cloud storage:

1. In the **Destinations** tab, tap **Link Telegram**.
2. Enter your phone number with country code (e.g. `+1 234 567 8900`).
3. Enter the login verification code (OTP) sent to your Telegram app (and your 2FA cloud password if enabled).
4. A dedicated private channel starting with `VD_` (e.g. `VD_Backup_4F2A`) is automatically created in your Telegram account to serve as your private storage.
5. Backups targeting the Telegram destination upload directly to this channel.
6. In the **Backups** tab, tap **Telegram Cloud** to browse, download, and restore backups directly from your Telegram channel.

Two rules keep remote use safe:

- **Credentials are stored encrypted** at rest in the module's state directory, never
Expand Down
4 changes: 3 additions & 1 deletion module/customize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ set_perm_recursive "$MODPATH" 0 0 0755 0644
set_perm_recursive "$MODPATH/bin" 0 0 0755 0755
set_perm "$MODPATH/scripts/helper.sh" 0 0 0755
set_perm "$MODPATH/scripts/worker.sh" 0 0 0755
set_perm "$MODPATH/scripts/telegram.sh" 0 0 0755
set_perm "$MODPATH/scripts/telegram_cli.py" 0 0 0755
set_perm "$MODPATH/service.sh" 0 0 0755
[ -f "$MODPATH/uninstall.sh" ] && set_perm "$MODPATH/uninstall.sh" 0 0 0755

Expand All @@ -109,7 +111,7 @@ if [ "$BOOTMODE" = "true" ] && [ "$MODPATH" != "$ACTIVE" ]; then
ui_print "- Activating without reboot"
mkdir -p "$ACTIVE"
cp -afT "$MODPATH" "$ACTIVE" 2>/dev/null || cp -af "$MODPATH"/. "$ACTIVE"/ 2>/dev/null
chmod 0755 "$ACTIVE"/*.sh "$ACTIVE"/scripts/*.sh "$ACTIVE"/bin/*/* 2>/dev/null
chmod 0755 "$ACTIVE"/*.sh "$ACTIVE"/scripts/*.sh "$ACTIVE"/scripts/*.py "$ACTIVE"/bin/*/* 2>/dev/null
# the manager writes an "update" marker AFTER customize.sh returns; clear it (and
# kick the scheduler) from a detached job so the module shows as active, not pending.
setsid sh -c "sleep 4; rm -f '$ACTIVE/update' '$ACTIVE/remove' '$ACTIVE/disable'; '$ACTIVE/scripts/helper.sh' scheduler-tick" >/dev/null 2>&1 &
Expand Down
74 changes: 62 additions & 12 deletions module/scripts/helper.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ BIN="$MODDIR/bin/$BINABI"
export PATH="$BIN:$PATH"
export LD_LIBRARY_PATH="$BIN/lib:${LD_LIBRARY_PATH:-}"

HELPER_DEX="$MODDIR/etc/vdbackup_helper.dex"
[ -f "$HELPER_DEX" ] || HELPER_DEX="$MODDIR/vdbhelper.dex"
COMPANION_APK="$MODDIR/etc/companion.apk"
[ -f "$COMPANION_APK" ] || COMPANION_APK="$MODDIR/companion.apk"

mkdir -p "$LOGS"

# --- tiny JSON helpers (no jq dependency) -------------------------------
Expand Down Expand Up @@ -133,8 +138,8 @@ auto_backup_scan() { # <jq> <now>; echoes detached
_seen="$STATE/autobackup_seen.json"
_cur="$STATE/staging/autobackup_cur.$$.json"; mkdir -p "$STATE/staging"
# current user apps as {pkg:vc}; dex gives versionCode, pm fallback gives vc=0
if [ -f "$MODDIR/etc/vdbackup_helper.dex" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$MODDIR/etc/vdbackup_helper.dex" /system/bin \
if [ -f "$HELPER_DEX" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$HELPER_DEX" /system/bin \
ru.vd171.vdbackup.Helper list-apps "$_u" 2>/dev/null \
| "$_jq" -c '[.apps[]? | select(.system|not) | {(.package): (.versionCode // 0)}] | add // {}' > "$_cur" 2>/dev/null
else
Expand Down Expand Up @@ -313,8 +318,8 @@ case "$verb" in
"$(command -v "$BIN/jq" 2>/dev/null || echo jq)" -r '.user // 0' 2>/dev/null)"
case "$u" in ''|*[!0-9]*) u=0 ;; esac
# delegates to the dex framework helper when available, else pm fallback
if [ -f "$MODDIR/etc/vdbackup_helper.dex" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$MODDIR/etc/vdbackup_helper.dex" /system/bin \
if [ -f "$HELPER_DEX" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$HELPER_DEX" /system/bin \
ru.vd171.vdbackup.Helper list-apps "$u" 2>/dev/null && exit 0
fi
# shell fallback: package name + apk path
Expand Down Expand Up @@ -343,8 +348,8 @@ case "$verb" in
[ -e "$old" ] && [ "$old" != "$png" ] && rm -f "$old"
done
fi
if [ ! -s "$png" ] && [ -f "$MODDIR/etc/vdbackup_helper.dex" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$MODDIR/etc/vdbackup_helper.dex" /system/bin \
if [ ! -s "$png" ] && [ -f "$HELPER_DEX" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$HELPER_DEX" /system/bin \
ru.vd171.vdbackup.Helper icon "$pkg" "$png" "$u" >/dev/null 2>&1
fi
if [ -s "$png" ]; then ok "\"package\":\"$(jstr "$pkg")\",\"icon\":\"$(base64 -w0 < "$png")\""
Expand All @@ -368,8 +373,8 @@ case "$verb" in
printf '%s\t%s\n' "$pkg" "$png" >> "$lst"
fi
done
if [ -s "$lst" ] && [ -f "$MODDIR/etc/vdbackup_helper.dex" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$MODDIR/etc/vdbackup_helper.dex" /system/bin \
if [ -s "$lst" ] && [ -f "$HELPER_DEX" ]; then
LD_LIBRARY_PATH= app_process -Djava.class.path="$HELPER_DEX" /system/bin \
ru.vd171.vdbackup.Helper icons "$u" "$lst" >/dev/null 2>&1
fi
rm -f "$lst"
Expand All @@ -384,7 +389,7 @@ case "$verb" in
done
printf '}}\n' ;;

backup|restore|import|special-backup|special-restore|cloud-fetch|path-backup|path-restore)
backup|restore|import|special-backup|special-restore|cloud-fetch|path-backup|path-restore|telegram-download)
opid="$(new_opid)"
detach "$opid" "$verb" "$ARGS_B64"
ok "\"opid\":\"$opid\"" ;;
Expand Down Expand Up @@ -731,9 +736,54 @@ case "$verb" in
i=0; while [ ! -f "$res.done" ] && [ "$i" -lt 30 ]; do sleep 1; i=$((i+1)); done
if [ -f "$res.done" ] && "$JQ" -e '.ok==true' "$res" >/dev/null 2>&1; then rm -f "$res" "$res.done"; ok '"reachable":true'
else c="$("$JQ" -r '.code // "ERR_DRIVE_TIMEOUT"' "$res" 2>/dev/null)"; rm -f "$res" "$res.done"; err "${c:-ERR_DRIVE}" "Drive not reachable - authorize consent"; fi ;;
telegram)
st="$("$MODDIR/scripts/telegram.sh" check-auth 2>/dev/null)"
if printf '%s' "$st" | "$JQ" -e '.authorized == true' >/dev/null 2>&1; then
ok '"reachable":true'
else
err ERR_DEST_AUTH "Telegram not connected. Please link your Telegram account."
fi ;;
*) err ERR_DEST_TYPE "unknown type" ;;
esac ;;

telegram-status)
"$MODDIR/scripts/telegram.sh" check-auth ;;

telegram-send-code)
JQ="$(command -v "$BIN/jq" 2>/dev/null || echo jq)"
j="$(printf '%s' "$ARGS_B64" | base64 -d 2>/dev/null)"
phone="$(printf '%s' "$j" | "$JQ" -r '.phone // empty' 2>/dev/null)"
[ -n "$phone" ] || { err ERR_TG_PHONE "Phone number is required"; exit 0; }
"$MODDIR/scripts/telegram.sh" send-code "$phone" ;;

telegram-sign-in)
JQ="$(command -v "$BIN/jq" 2>/dev/null || echo jq)"
j="$(printf '%s' "$ARGS_B64" | base64 -d 2>/dev/null)"
phone="$(printf '%s' "$j" | "$JQ" -r '.phone // empty' 2>/dev/null)"
code="$(printf '%s' "$j" | "$JQ" -r '.code // empty' 2>/dev/null)"
pw="$(printf '%s' "$j" | "$JQ" -r '.password // empty' 2>/dev/null)"
[ -n "$phone" ] && [ -n "$code" ] || { err ERR_TG_CODE "Phone and verification code are required"; exit 0; }
"$MODDIR/scripts/telegram.sh" sign-in "$phone" "$code" "$pw" ;;

telegram-init-storage)
JQ="$(command -v "$BIN/jq" 2>/dev/null || echo jq)"
j="$(printf '%s' "$ARGS_B64" | base64 -d 2>/dev/null)"
title="$(printf '%s' "$j" | "$JQ" -r '.title // empty' 2>/dev/null)"
"$MODDIR/scripts/telegram.sh" init-storage "$title" ;;

telegram-list-remote)
JQ="$(command -v "$BIN/jq" 2>/dev/null || echo jq)"
j="$(printf '%s' "$ARGS_B64" | base64 -d 2>/dev/null)"
cid="$(printf '%s' "$j" | "$JQ" -r '.channel_id // empty' 2>/dev/null)"
if [ -z "$cid" ] && [ -f "$STATE/dests/telegram.json" ]; then
cid="$("$JQ" -r '.channel_id // empty' "$STATE/dests/telegram.json" 2>/dev/null)"
fi
[ -n "$cid" ] || { err ERR_TG_NO_CHANNEL "No storage channel configured"; exit 0; }
"$MODDIR/scripts/telegram.sh" list-remote "$cid" ;;

telegram-logout)
"$MODDIR/scripts/telegram.sh" logout ;;

schedule-set)
JQ="$(command -v "$BIN/jq" 2>/dev/null || echo jq)"
j="$(printf '%s' "$ARGS_B64" | base64 -d 2>/dev/null)"
Expand Down Expand Up @@ -856,16 +906,16 @@ case "$verb" in
companion-status)
JQ="$(command -v "$BIN/jq" 2>/dev/null || echo jq)"
inst=false; pm path ru.vd171.vdbackup >/dev/null 2>&1 && inst=true
bundled=false; [ -f "$MODDIR/etc/companion.apk" ] && bundled=true
bundled=false; [ -f "$COMPANION_APK" ] && bundled=true
vc="$(dumpsys package ru.vd171.vdbackup 2>/dev/null | sed -n 's/.*versionCode=\([0-9]*\).*/\1/p' | head -1)"
"$JQ" -nc --argjson installed "$inst" --argjson bundled "$bundled" --arg vc "${vc:-}" \
'{ok:true,installed:$installed,bundled:$bundled,versionCode:$vc}' ;;

companion-install)
# optional + on-demand: only ever called when the user picks Google Drive.
[ -f "$MODDIR/etc/companion.apk" ] || { err ERR_COMPANION "companion APK not bundled in this build"; exit 0; }
[ -f "$COMPANION_APK" ] || { err ERR_COMPANION "companion APK not bundled in this build"; exit 0; }
# system_server cannot read /data/adb (adb_data_file); stage under /data/local/tmp
capk=/data/local/tmp/vdbackup-companion.apk; cp "$MODDIR/etc/companion.apk" "$capk"; chmod 644 "$capk"
capk=/data/local/tmp/vdbackup-companion.apk; cp "$COMPANION_APK" "$capk"; chmod 644 "$capk"
if pm install -r "$capk" >/dev/null 2>&1; then rm -f "$capk"; ok '"installed":true,"hma":true'
else rm -f "$capk"; err ERR_COMPANION_INSTALL "could not install companion"; fi ;;

Expand Down
53 changes: 53 additions & 0 deletions module/scripts/lib/pyaes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# The MIT License (MIT)
#
# Copyright (c) 2014 Richard Moore
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# This is a pure-Python implementation of the AES algorithm and AES common
# modes of operation.

# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation


# Supported key sizes:
# 128-bit
# 192-bit
# 256-bit


# Supported modes of operation:
# ECB - Electronic Codebook
# CBC - Cipher-Block Chaining
# CFB - Cipher Feedback
# OFB - Output Feedback
# CTR - Counter

# See the README.md for API details and general information.

# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
# https://www.dlitz.net/software/pycrypto/


VERSION = [1, 3, 0]

from .aes import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, AESModeOfOperationOFB, AESModesOfOperation, Counter
from .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter
from .blockfeeder import PADDING_NONE, PADDING_DEFAULT
Loading