- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Un archivo borrado del directorio de origen solo se avisaba; había que repetir la sincronización con --delete. Ahora el valor por defecto propaga los borrados de local a Drive en la misma pasada. - `--delete` deja de ser un booleano y acepta el lado a propagar: `never`, `remote` (por defecto), `local` o `both`. Sin valor equivale a `both`, que es el comportamiento anterior de la bandera. - `--no-delete` conserva el modo antiguo de solo avisar y es incompatible con `--delete`. - `--direction push --delete` ya no borra en Drive contenido que el estado no registre como sincronizado: se avisa en lugar de destruir lo ajeno. Los remotos se siguen moviendo a la papelera de Drive (recuperables). Incluye pruebas unitarias del plan, pruebas de extremo a extremo contra el mock y la documentación del README. |
||
| .github/workflows | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
DriveSync
Versión 0.4.0 · CLI en Rust para sincronizar archivos y carpetas con Google Drive usando un token de Google. Sin SDKs pesados: habla directamente con la API v3 de Drive sobre HTTPS, guarda el estado de la sincronización y resuelve conflictos sin perder ninguna versión.
drivesync auth --access-token "$(gcloud auth print-access-token)"
drivesync sync ./documentos --remote Backup/Documentos
Características
- Autenticación por token: token de acceso directo, refresh token, flujo
OAuth de escritorio con redirección a
127.0.0.1o flujo manual (--manual: imprime la URL y pide el código o el token a mano). - Sincronización en tres vías con un archivo de estado local: distingue "cambió en local", "cambió en remoto" y "borrado" en cada lado.
- Borrados propagados: lo que desaparece del directorio de origen se mueve
a la papelera de Drive (
--deleteextiende los borrados al lado local y--no-deletesolo avisa). - Subidas reanudables por bloques (8 MiB) para archivos grandes: si un bloque falla, se reintenta y, si el fallo persiste, se pregunta a Google cuánto lleva recibido y se continúa desde ahí.
- Descargas verificadas con el MD5 que reporta Drive.
- Conflictos sin pérdida: por defecto conserva la versión local y la remota
(esta última como
archivo.conflicto-remoto-AAAAMMDD-HHMMSS.ext). - Comandos sueltos:
list,upload,download,mkdir,status. - Tamaño de carpetas en
list: suma el subárbol completo ([dir ] [3.0 GiB] Backup/), con--no-sizespara evitarlo. - Simulación con
--dry-run, exclusiones con--excludey direccionespush/pull/both. - Reintentos con backoff para 429/5xx y renovación automática del token.
Requisitos
- Rust 1.74 o superior (
cargo). - Una cuenta de Google y un cliente OAuth de tipo Aplicación de escritorio (solo si quieres refresh token; con un token de acceso temporal no hace falta).
Instalar
Binario precompilado (Linux)
Cada tag publica una release con los binarios de x86_64 y aarch64 en
https://github.com/DanimanX/DriveSync/releases.
tag=v0.4.0
arch=x86_64-unknown-linux-gnu # o aarch64-unknown-linux-gnu
base="https://github.com/DanimanX/DriveSync/releases/download/$tag"
curl -fsSLO "$base/drivesync-${tag#v}-$arch"
curl -fsSLO "$base/SHA256SUMS-$arch.txt"
sha256sum -c "SHA256SUMS-$arch.txt"
install -m 755 "drivesync-${tag#v}-$arch" ~/.local/bin/drivesync
drivesync --version
Desde el código
cargo build --release
install -m 755 target/release/drivesync ~/.local/bin/drivesync
drivesync --help
Configurar Google Cloud (una sola vez)
- Entra en https://console.cloud.google.com/ y crea (o elige) un proyecto.
- Activa Google Drive API en APIs y servicios → Biblioteca.
- Configura la pantalla de consentimiento OAuth (APIs y servicios → Pantalla de consentimiento OAuth): tipo Externo, añade tu correo como usuario de prueba. La app queda en modo prueba y solo esos usuarios pueden autorizarla.
- Crea credenciales en APIs y servicios → Credenciales → Crear credenciales → ID de cliente OAuth: tipo de aplicación Aplicación de escritorio.
- Copia el client ID y el client secret.
El alcance por defecto es https://www.googleapis.com/auth/drive (acceso
completo a los archivos de la cuenta). Es un alcance restringido: para uso
personal con la app en modo prueba funciona añadiendo tu usuario; publicarla
para terceros exige el proceso de verificación de Google.
Autenticación
Hay tres formas. Las credenciales se guardan en
~/.config/drivesync/config.json con permisos 0600 (o en la ruta que indique
--config / DRIVESYNC_CONFIG).
1. Token de acceso (rápido, caduca en ~1 hora)
drivesync auth --access-token "$(gcloud auth print-access-token)"
# o
drivesync --token "$TOKEN" status
# o
export GOOGLE_ACCESS_TOKEN="$TOKEN"
Cuando el token caduca, la API devuelve 401 y DriveSync pide volver a autenticarse. Útil para scripts y CI.
2. Navegador + refresh token (recomendado para uso continuo)
drivesync auth --client-id ID.apps.googleusercontent.com --client-secret SECRET
Abre la URL que imprime (o intenta abrir el navegador), escucha en
http://127.0.0.1:8765/callback, canjea el código y guarda un refresh token
con access_type=offline. A partir de ahí el token de acceso se renueva solo.
Opciones: --port 9000 si el puerto está ocupado, --no-browser en servidores
sin escritorio. Si Google rechaza la redirección, añade
http://127.0.0.1:8765/callback en los URI de redirección autorizados del
cliente OAuth.
3. Sin servidor local: la URL y el código a mano (--manual)
Para máquinas sin navegador, sesiones SSH o cuando el puerto local está bloqueado. DriveSync imprime la URL de consentimiento, tú autorizas en cualquier navegador (puede ser otro equipo) y pegas el resultado:
drivesync auth --manual --client-id ID --client-secret SECRET
1. Abre esta URL en el navegador:
https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...
2. Autoriza la aplicación. DriveSync escucha en http://127.0.0.1:8765/callback
y te mostrará el código en una página, listo para copiar.
3. Si el navegador está en otra máquina y no llega a la página, copia la URL
completa de la barra de direcciones (contiene code=...).
Pega la URL, el código o el token:
Si el navegador corre en la misma máquina, al autorizar verás una página con el código en grande, botón Copiar código y, plegada, la URL completa por si prefieres pegarla. El CLI recibe el código automáticamente desde esa página, así que normalmente no hay que pegar nada; el prompt queda para cuando el navegador está en otro equipo (o el puerto está ocupado y no se puede servir la página).
Qué acepta el prompt:
- La URL de redirección completa (
http://127.0.0.1:8765/callback?state=…&code=…). - El código suelto (
4/0AX…). - Un token de acceso ya emitido (
ya29.…), que se guarda tal cual con el aviso de que caduca en ~1 hora y no se renueva.
La respuesta se valida contra el state de esa ejecución; si no coincide, se
rechaza el canje. Usa --redirect-uri para incrustar otra URI (debe estar
autorizada en el cliente OAuth) y --state VALOR para fijar el valor, útil en
scripts.
En el caso del código se obtiene un refresh token igual que en el flujo con navegador, así que el token se renueva después solo.
4. Refresh token ya emitido
drivesync auth --client-id ID --client-secret SECRET --refresh-token REFRESH
Se valida contra Google antes de guardarlo.
Variables de entorno
| Variable | Uso |
|---|---|
GOOGLE_ACCESS_TOKEN |
token de acceso (equivale a --token) |
GOOGLE_CLIENT_ID |
client ID para renovar |
GOOGLE_CLIENT_SECRET |
client secret para renovar |
GOOGLE_REFRESH_TOKEN |
refresh token |
DRIVESYNC_CONFIG |
ruta alternativa de configuración |
DRIVESYNC_API_BASE |
URL base de la API (pruebas) |
drivesync status muestra la cuenta, el alcance, el modo de autenticación y el
almacenamiento usado. drivesync logout borra los tokens y conserva el client
ID/secret.
Problemas frecuentes
Error 401: invalid_client — "The OAuth client was not found"
La página de Google aparece, pero no reconoce el cliente. Casi siempre es una de estas:
-
El
client_idno está completo o tiene un espacio/retorno de carro al pegar. Debe terminar en.apps.googleusercontent.comy medir ~72 caracteres. Compruébalo sin abrir el navegador:drivesync auth --check-client --client-id TU_ID --client-secret TU_SECRETEnvía un código inválido al endpoint de tokens: si Google responde
invalid_clientel cliente no existe; si respondeinvalid_grantelclient_ides correcto y el problema está en otra parte. No guarda nada. -
El cliente es de tipo Android, iOS o cuenta de servicio: solo los clientes de tipo Aplicación de escritorio o Aplicación web sirven para este flujo. Compruébalo en Credenciales del proyecto.
-
El cliente se creó en otro proyecto del que crees, o se borró.
DriveSync avisa si el client_id no termina en .apps.googleusercontent.com.
Verifica el valor guardado con:
drivesync status # no muestra el secreto
python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.config/drivesync/config.json')))['client_id'])"
redirect_uri_mismatch
La URI de redirección no está autorizada en el cliente. Añade
http://127.0.0.1:8765/callback en URI de redirección autorizados, o usa el
mismo --port / --redirect-uri que tengas registrado.
access_denied — "DriveSync no completó el proceso de verificación de Google"
La app está en estado Prueba y la cuenta con la que autorizas no está en la
lista de usuarios de prueba. Es el caso normal mientras la app no pasa la
verificación de Google (el scope drive es restringido y verificarlo es un
proceso aparte). Solución, en el proyecto donde creaste el client_id:
- https://console.cloud.google.com/ → selecciona el proyecto.
- APIs y servicios → Pantalla de consentimiento OAuth (en la consola nueva, Google Auth Platform → Audience).
- En Usuarios de prueba → Add users → escribe el correo exacto con el que
autorizas (por ejemplo
danimanx@gmail.com) → Save. - Espera un par de minutos y repite
drivesync auth.
Límites del modo prueba: hasta 100 usuarios de prueba y los refresh tokens
caducan a los 7 días, así que toca reautorizar semanalmente. Para evitarlo hay
que publicar la app (Publish app), lo que con un scope restringido como
drive implica el proceso de verificación de Google; la alternativa práctica
para uso propio es un cliente con el scope drive.file, o seguir usando
drivesync auth --access-token con un token de corta duración en scripts.
Comprueba qué cuenta tienes configurada y con qué cliente:
drivesync status
408 Request Timeout al subir un archivo grande
Google devuelve una página HTML "Your client has taken too long to issue its request". Es un timeout del frontend, transitorio, típico con archivos de varios GB o conexiones lentas. DriveSync lo reintenta y, si el bloque sigue fallando, consulta el progreso de la sesión de carga y continúa desde el último byte confirmado, sin volver a subir lo que ya está.
Si aparece [recuperado] la sesión conserva X, es justo eso: la subida siguió
adelante. Con -v se ve el avance bloque a bloque:
drivesync -v upload ./juego.nsp --to Backup
Si el error persiste tras varios intentos, el mensaje ya no vuelca el HTML: indica el código, la causa y que conviene reintentar.
invalid_grant
El código ya se canjeó, caducó (10 minutos) o el refresh_token se revocó.
Vuelve a ejecutar drivesync auth.
Comandos
drivesync list [--folder CARPETA] [--max N] [--no-sizes]
drivesync upload RUTA... [--to CARPETA] [--name NOMBRE] [--contents] [--exclude PATRÓN]
drivesync download REMOTO [--to RUTA] [--overwrite]
drivesync mkdir NOMBRE [--folder CARPETA] [--literal]
drivesync sync DIRECTORIO --remote CARPETA [opciones]
drivesync status
drivesync logout
CARPETA/REMOTO aceptan un ID de Drive, una ruta desde Mi unidad
(Backup/Fotos/2026) o un nombre (se busca y se avisa si hay duplicados).
En las rutas vale tanto / como \, así que Backup\Fotos\2026 y
Backup/Fotos/2026 son lo mismo. mkdir además crea las carpetas
intermedias que falten:
drivesync mkdir "Recomp/NSW 2" # crea Recomp si no existe y NSW 2 dentro
drivesync mkdir --folder Recomp "NSW 2" # equivalente, con el nombre suelto
drivesync mkdir "Recomp\GBA 2" # estilo Windows, también válido
drivesync mkdir --literal "c\raro" # nombre literal, sin separar
Solo mkdir admite --literal; el resto de comandos interpretan \ como
separador, así que una carpeta cuyo nombre contenga una barra invertida debe
referenciarse por su ID.
Ejemplos
# Listar una carpeta por ruta (las carpetas muestran su tamaño recursivo)
drivesync list --folder Backup/2026
# Igual pero sin calcular tamaños (más rápido en árboles enormes)
drivesync list --folder Backup --no-sizes
# Subir un archivo (si ya existe, se actualiza)
drivesync upload ./informe.pdf --to Backup
# Subir una carpeta completa de forma recursiva: crea Backup/proyecto/...
drivesync upload ./proyecto --to Backup
# Subir solo el contenido, sin crear la carpeta contenedora
drivesync upload ./proyecto --to Backup/Codigo --contents
# Varias rutas de una vez, excluyendo temporales y cachés
drivesync upload ./proyecto ./informe.pdf --to Backup \
--exclude '*.tmp' --exclude 'cache/**' --exclude '.git'
# Descargar una carpeta entera a ./restaurado
drivesync download Backup/Codigo --to ./restaurado --overwrite
# Sincronizar en ambos sentidos, mostrando el plan
drivesync sync ./documentos --remote Backup/Documentos --dry-run
# Sincronizar de verdad, excluyendo temporales y cachés
drivesync sync ./documentos --remote Backup/Documentos \
--exclude '*.tmp' --exclude 'cache/**'
# Propagar los borrados también de Drive a local (por defecto solo va a Drive)
drivesync sync ./documentos --remote Backup/Documentos --delete
# No borrar nada: solo avisar de lo que falta en cada lado
drivesync sync ./documentos --remote Backup/Documentos --no-delete
# Solo bajar cambios (el remoto manda)
drivesync sync ./documentos --remote Backup/Documentos --direction pull
Subida recursiva
upload acepta uno o varios archivos o carpetas y recorre las carpetas en
profundidad, creando la estructura de subcarpetas que falte en Drive.
drivesync upload ./proyecto --to Backup # Backup/proyecto/{...}
drivesync upload ./proyecto --to Backup --contents # Backup/{...} sin la carpeta
drivesync upload a.txt b/ c/ --to Backup # varias rutas a la vez
drivesync upload ./proyecto --to Backup --dry-run # solo describe
- Idempotente: compara el MD5 de cada archivo con el que reporta Drive y omite los que ya están iguales. Volver a subir el mismo árbol no genera escrituras.
- Carpetas vacías: también se crean.
- Exclusiones:
--excludecon comodines*y?, repetible. Aplica a rutas, nombres base y subárboles;cache/**excluye además la propia carpeta. - Enlaces simbólicos y archivos especiales se omiten con aviso.
--namerenombra una única ruta;--replace IDsobrescribe un archivo concreto de Drive.- Resiliencia: si un elemento falla (por ejemplo ya existe un archivo donde debería ir una carpeta), se informa, se continúa con el resto y el comando termina con código distinto de cero.
- Resumen al final: subidos, actualizados, sin cambios, carpetas creadas y omitidos.
Cómo funciona la sincronización
El estado se guarda en DIRECTORIO/.drivesync/state.json: por cada ruta
relativa anota el MD5 local, el ID remoto, el MD5 remoto y la fecha. En cada
pasada se calcula el MD5 de los archivos locales, se listan recursivamente los
remotos y se comparan tres versiones (base, local, remoto).
Con --direction both (por defecto):
- Solo local y sin base → subir.
- Solo remoto y sin base → bajar.
- Ambos lados iguales → nada.
- Cambió el local → actualizar remoto.
- Cambió el remoto → actualizar local.
- Ambos cambiaron → conflicto.
- Local borrado (y remoto intacto) → mover el remoto a la papelera de Drive.
- Remoto borrado (y local intacto) → con
--delete, borrar el local; sin él, solo avisar. - Lado borrado pero el otro modificado → se restaura la versión modificada.
Por defecto los borrados van solo del directorio local a Drive: si un
archivo ya no existe en el directorio de origen, la siguiente pasada lo manda a
la papelera. --delete acepta el lado a propagar:
--delete(o--delete=both): ambos sentidos, también Drive → local.--delete=remote: solo local → Drive, el comportamiento por defecto.--delete=local: solo Drive → local.--delete=never(o--no-delete): nada, solo avisos.
Nunca se borra en Drive algo que el estado no registre como sincronizado, ni un archivo remoto que haya cambiado desde la última pasada: en esos casos se avisa o se restaura la versión remota. Los archivos remotos se mueven a la papelera de Drive (recuperables).
Conflictos
--conflict elige la política:
keep-both(por defecto): descarga la versión remota comonombre.conflicto-remoto-AAAAMMDD-HHMMSS.ext, sube la local al archivo remoto original y sube la copia remota como archivo nuevo. Ambos lados quedan con las dos versiones.keep-local: la versión local sobrescribe la remota.keep-remote: la versión remota sobrescribe la local.
Exclusiones
--exclude acepta comodines * y ? y se aplica a la ruta relativa, al nombre
base y a todo el subárbol (cache/**), tanto en local como en remoto (así
--delete no borra en Drive algo que estás excluyendo). .drivesync se excluye
siempre. Los enlaces simbólicos se omiten con un aviso.
Una sola sincronización por directorio
sync toma un cerrojo exclusivo sobre DIRECTORIO/.drivesync/lock antes de
leer o escribir nada. Si ya hay otra ejecución trabajando ahí, la segunda sale
de inmediato sin tocar archivos ni estado:
error: ya hay otra sincronización en curso sobre este directorio (pid 48213, hace 2 min, en mi-pc).
Espera a que termine: el bloqueo se libera solo al acabar ese proceso.
Si crees que no hay ninguno, compruébalo con: ps aux | grep drivesync
El cerrojo es un flock del kernel, no un archivo de PID: si el proceso muere
—incluido kill -9 o un corte de luz— el bloqueo desaparece con él y no hay
que borrar nada a mano. El archivo puede quedarse en disco; su presencia no
bloquea, solo el cerrojo lo hace. El bloqueo cubre el escaneo, el plan y la
escritura del estado, así que dos procesos nunca se pisan entre medias.
Limitaciones conocidas
- Los documentos nativos de Google (Docs, Sheets, Slides) no tienen MD5 ni contenido binario descargable: se omiten con un aviso. Para sincronizarlos habría que exportarlos a un formato concreto.
- Los accesos directos de Drive y los tipos no binarios se omiten.
- No se borran carpetas vacías: se propagan archivos, no la estructura vacía.
- Si el directorio de origen no está montado (o se vacía por error), la pasada
siguiente lo interpreta como "todo borrado" y manda a la papelera lo que
estaba sincronizado. En orígenes que puedan faltar, revisa el plan con
--dry-runo usa--no-delete. - Se calcula el MD5 de todos los archivos locales en cada pasada (correcto, pero más lento en árboles muy grandes).
- La sincronización es secuencial (una operación a la vez) y no reanuda una subida entre ejecuciones distintas: los bloques se reintentan dentro de la misma ejecución.
- Nombres remotos con
/se sanean a_; nombres duplicados en la misma carpeta remota se resuelven usando el primero y avisando. - El bloqueo impide dos
syncsimultáneos sobre el mismo directorio, pero no coordina procesos que apunten a la misma carpeta de Drive desde directorios locales distintos.
Seguridad
- El archivo de configuración se crea con permisos
0600y el directorio con0700en sistemas Unix. Aun así, el token se guarda en texto plano: protege tu directorio personal. - El alcance
driveda acceso completo a Drive. Si solo vas a manejar archivos creados por la app, puedes usar--scope https://www.googleapis.com/auth/drive.file, pero entonces DriveSync no verá archivos preexistentes. - Para CI, prefiere
GOOGLE_ACCESS_TOKENde corta duración antes que guardar un refresh token en disco. logoutelimina los tokens; revoca el acceso desde https://myaccount.google.com/permissions si quieres invalidar el refresh token por completo.
Desarrollo y pruebas
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
El flujo de GitHub Actions (.github/workflows/ci.yml) ejecuta formato, clippy,
pruebas y compilación release para x86_64 y aarch64 en cada push a main,
en cada pull request y en cada tag v*.
Los binarios compilados se publican de dos formas:
- Artefactos del run (
drivesync-<target>): disponibles para inspección, requieren sesión en GitHub y caducan a los 90 días. - Release de GitHub: al empujar un tag
v*se crea la release con el binario crudo, su.tar.gzySHA256SUMS-<target>.txtde cada arquitectura. Es la vía normal para descargar e instalar.
Para publicar una release de un tag que ya existe sin volver a etiquetar, lanza el workflow a mano indicando el tag:
gh workflow run ci.yml -f release_tag=v0.0.1
La suite incluye:
- Pruebas unitarias del plan de sincronización (subidas, bajadas, conflictos,
borrados,
push/pull, exclusiones, utilidades). - Pruebas de extremo a extremo en
tests/mock_drive.rsque ejecutan el binario real contra un servidor HTTP local que implementa el subconjunto usado de la API de Drive: subida multipart y reanudable, descarga, papelera, refresh token, conflictos y--dry-run.
Estructura
src/main.rs CLI (clap) y comandos
src/auth.rs tokens: refresh, OAuth de bucle local, token estático
src/api.rs cliente HTTP de Drive v3 (listar, subir, bajar, papelera)
src/sync.rs escaneo, estado de tres vías, plan y ejecución
src/config.rs configuración persistente
src/util.rs MD5, comodines, MIME, fechas y tamaños
tests/ Drive simulado de extremo a extremo