Passer de Google timeline à Dawarich

Aurelienazerty Aucun commentaire Modifié le Article

Depuis la fin 2024, Google a discrètement sabordé l'un des rares trucs sympas qu'il nous offrait gratuitement : l'accès à l'historique de ses trajets depuis un navigateur PC. Désormais, « Vos trajets » ne vit plus que sur votre téléphone, les données sont stockées localement sur l'appareil, et la version web a tout bonnement disparu. Aucune annonce fracassante, juste une fonctionnalité qui s'évapore, à la Google. Et clairement pour le quadra que je suis, l'écran du téléphone, c'est vraiment moins pratique qu'un 24 pouces !

La Timeline desktop, paix à son âme (2008-2024).

C'est en cherchant une alternative que je suis tombé sur Dawarich, un projet open source qui se présente comme « votre alternative auto-hébergeable à Google Timeline ». Le principe : vous installez ça sur votre propre serveur, vous importez votre historique Google, et vous retrouvez une interface web complète pour consulter vos trajets depuis n'importe quel navigateur. Avec, en prime, un tracking continu automatique via une app mobile.

Parce que ça m'a tenu éveillé plusieurs heures, et parce que j'ai documenté chaque piège au fur et à mesure, voici le tuto complet pour installer Dawarich sur un NAS Synology avec accès HTTPS via un sous-domaine perso chez OVH.

Prérequis

Ce tuto suppose que vous avez :

  • Un NAS Synology sous DSM 7 ou supérieur
  • Le Container Manager installé (l'ancienne appli Docker de Synology)
  • Un accès SSH au NAS
  • Un domaine chez OVH
  • acme.sh déjà installé sur le NAS (si ce n'est pas le cas, consultez le tutoriel d'Anthony Jacob)

Si vous avez déjà suivi ma brève sur FreshRSS, vous avez tout ce qu'il faut. La procédure est similaire dans l'esprit, un peu plus complexe dans les détails car Dawarich tourne sur 4 containers interdépendants au lieu d'un seul.

Étape 1 : Créer les dossiers dans File Station

Dans File Station, naviguez jusqu'à /volume1/docker/ et créez la structure suivante :

/volume1/docker/dawarich/
├── db/  ← données PostgreSQL
├── db_shared/ ← données partagées base de données
├── redis/  ← cache Redis
├── public/ ← fichiers statiques de l'interface
├── storage/ ← fichiers importés
└── watched/ ← dossier d'import automatique

Étape 2 : Créer le docker-compose.yml

Dawarich ne se déploie pas comme un container unique : il en fait tourner quatre en parallèle. La base de données PostGIS, le cache Redis, l'application Rails, et Sidekiq pour les tâches de fond. C'est pour ça qu'on passe par le mode Projet du Container Manager plutôt que par l'interface graphique classique.

Créez le fichier /volume1/docker/dawarich/docker-compose.yml :

name: dawarich

services:
 dawarich_db:
 image: postgis/postgis:17-3.5-alpine
 container_name: dawarich_db
 shm_size: 1G
 restart: unless-stopped
 healthcheck:
 test: ["CMD-SHELL", "pg_isready -U postgres -d dawarich_development"]
 interval: 10s
 retries: 5
 start_period: 30s
 timeout: 10s
 volumes:
 - /volume1/docker/dawarich/db:/var/lib/postgresql/data
 - /volume1/docker/dawarich/db_shared:/var/shared
 environment:
 POSTGRES_USER: postgres
 POSTGRES_PASSWORD: UnMotDePasseSolide
 POSTGRES_DB: dawarich_development

 dawarich_redis:
 image: redis:7.4-alpine
 container_name: dawarich_redis
 command: redis-server --stop-writes-on-bgsave-error no --save ""
 restart: unless-stopped
 healthcheck:
 test: ["CMD-SHELL", "redis-cli ping || exit 1"]
 volumes:
 - /volume1/docker/dawarich/redis:/data

 dawarich_app:
 image: freikin/dawarich:latest
 container_name: dawarich_app
 restart: unless-stopped
 depends_on:
 dawarich_db:
 condition: service_healthy
 dawarich_redis:
 condition: service_healthy
 ports:
 - "3765:3000"
 volumes:
 - /volume1/docker/dawarich/public:/var/app/public
 - /volume1/docker/dawarich/storage:/var/app/storage
 - /volume1/docker/dawarich/watched:/var/app/tmp/imports/watched
 env_file:
 - .env
 environment:
 - LANG=fr_FR.UTF-8
 - LC_ALL=fr_FR.UTF-8
 - RAILS_LOCALE=fr
 entrypoint: web-entrypoint.sh
 command: bin/rails server -b 0.0.0.0 -p 3000

 dawarich_sidekiq:
 image: freikin/dawarich:latest
 container_name: dawarich_sidekiq
 restart: unless-stopped
 depends_on:
 dawarich_db:
 condition: service_healthy
 dawarich_redis:
 condition: service_healthy
 volumes:
 - /volume1/docker/dawarich/public:/var/app/public
 - /volume1/docker/dawarich/storage:/var/app/storage
 - /volume1/docker/dawarich/watched:/var/app/tmp/imports/watched
 env_file:
 - .env
 environment:
 - LANG=fr_FR.UTF-8
 - LC_ALL=fr_FR.UTF-8
 - RAILS_LOCALE=fr
 entrypoint: sidekiq-entrypoint.sh
 command: bundle exec sidekiq

Le port 3765 est arbitraire, choisissez-en un autre s'il est déjà occupé sur votre NAS.

⚠️ Note sur la ligne command de Redis : les deux options --stop-writes-on-bgsave-error no --save "" sont indispensables sur NAS Synology. Sans elles, Redis panique s'il n'arrive pas à écrire son snapshot sur le disque et bloque toutes les écritures, y compris les jobs d'import. Le --save "" désactive les snapshots RDB : Redis n'est ici qu'une file de jobs temporaires, pas une base de données à persister sur disque.

Créez ensuite le fichier /volume1/docker/dawarich/.env dans le même dossier :

RAILS_ENV=development
APPLICATION_HOSTS=trajets.votredomaine.fr,localhost
APPLICATION_PROTOCOL=http
TIME_ZONE=Europe/Paris
LANG=fr
MIN_MINUTES_SPENT_IN_CITY=60
BACKGROUND_PROCESSING_CONCURRENCY=15

DATABASE_HOST=dawarich_db
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=UnMotDePasseSolide
DATABASE_NAME=dawarich_development

REDIS_URL=redis://dawarich_redis:6379/0

⚠️ Point important : laissez APPLICATION_PROTOCOL=http même si l'accès public sera en HTTPS. La terminaison SSL est gérée par le reverse proxy DSM, si vous mettez https ici, vous obtenez une boucle de redirection infinie qui casse tout. Voilà, je vous ai épargné 20 minutes de debug.

Étape 3 : Lancer le projet dans Container Manager

Dans Container Manager, allez dans Projets → Créer :

  • Nom du projet : dawarich
  • Chemin : /volume1/docker/dawarich
  • Quand DSM demande quoi faire du fichier existant : "Utiliser le docker-compose.yml existant"
  • Sur l'écran "Paramètres du portail Web" : décochez "Configurer le portail Web via Web Station", vous n'en avez pas besoin, le reverse proxy DSM s'en chargera
  • Cliquez Terminer

Les images se téléchargent, les containers démarrent. Attendez 3 à 5 minutes. Les quatre containers (dawarich_db, dawarich_redis, dawarich_app, dawarich_sidekiq) doivent tous passer en vert. Il est normal que dawarich_app reste en attente quelques instants : il attend que la base de données soit prête avant de démarrer.

Testez en local : http://IP-de-votre-NAS:3765 doit afficher la page de connexion Dawarich.

Étape 4 : Entrée DNS chez OVH

Avant de demander un certificat, il faut que le domaine pointe quelque part. Dans votre espace client OVH, deux options selon votre configuration :

Si vous utilisez le DNS standard OVH (zone DNS) : Domaines → votredomaine.fr → Zone DNS → Ajouter une entrée A avec trajets comme sous-domaine et votre IP publique comme cible.

Si vous utilisez DynHost (comme c'est mon cas pour gérer une IP qui peut changer) : Domaines → votredomaine.fr → DynHost → Ajouter un DynHost avec trajets comme sous-domaine. Pensez aussi à mettre à jour votre script de mise à jour DynHost pour inclure ce nouveau sous-domaine.

Attendez quelques minutes que le DNS se propage, puis vérifiez avec un ping trajets.votredomaine.fr.

Étape 5 : Certificat Let's Encrypt via acme.sh

Puisque notre NAS est derrière un reverse proxy, on ne peut pas utiliser le challenge HTTP intégré à DSM, il a besoin que le port 80 soit directement accessible, ce qui n'est pas toujours le cas. On passe donc par le DNS challenge via l'API OVH, qui ne nécessite aucune ouverture de port. Deux situations possibles selon votre historique.

Scénario A : Vous avez déjà acme.sh et des clés API OVH (par exemple pour FreshRSS)

Bonne nouvelle : une seule clé API OVH suffit pour tous vos sous-domaines du même compte. Les droits /domain/zone/* couvrent toute la zone DNS, inutile d'en créer de nouvelles. Connectez-vous en SSH avec votre utilisateur acme.sh et lancez directement :

./acme.sh --issue 
 -d trajets.votredomaine.fr 
 --dns dns_ovh 
 --server letsencrypt 
 --keylength 2048

acme.sh récupère automatiquement les clés OVH déjà enregistrées dans son fichier account.conf. Pas besoin de les ré-exporter.

Scénario B : Vous partez de zéro (première installation)

Il faut d'abord créer les clés API OVH sur eu.api.ovh.com/createToken/. Les droits nécessaires sont :

  • GET /auth/time
  • GET /domain
  • GET /domain/zone/*
  • GET /domain/zone/*/record
  • PUT /domain/zone/*/record/*
  • POST /domain/zone/*/record
  • POST /domain/zone/*/refresh
  • DELETE /domain/zone/*/record/* ← ne pas oublier celui-ci

⚠️ Mettez la durée de validité sur Unlimited. Avec "1 day", vos clés expirent le lendemain et le renouvellement automatique dans 90 jours échoue silencieusement.

⚠️ Le droit DELETE /domain/zone/*/record/* est celui que les gens oublient le plus souvent. Sans lui, acme.sh crée bien l'enregistrement TXT de validation mais ne peut pas le supprimer ensuite, ce qui fait échouer la vérification avec une erreur cryptique "Add txt record error". Si vous avez cette erreur, c'est presque certainement ça.

Puis lancez :

export OVH_AK="votre_application_key"
export OVH_AS="votre_application_secret"
export OVH_CK="votre_consumer_key"

./acme.sh --issue 
 -d trajets.votredomaine.fr 
 --dns dns_ovh 
 --server letsencrypt 
 --keylength 2048

Déploiement du certificat sur DSM (commun aux deux scénarios)

Une fois le certificat généré, déployez-le sur DSM. Le point important ici : nommez explicitement le certificat avec SYNO_CERTIFICATE pour éviter qu'il n'écrase un certificat existant (FreshRSS par exemple).

export SYNO_USERNAME='votre_user_admin_dsm'
export SYNO_PASSWORD='MotDePasseAdmin'
export SYNO_HOSTNAME="votre_quickid.synology.me"
export SYNO_SCHEME="https"
export SYNO_PORT="5001"
export SYNO_CERTIFICATE="trajets.votredomaine.fr"
export SYNO_CREATE=1

./acme.sh --deploy 
 --home /var/services/homes/VotreUser/.acme.sh/ 
 -d trajets.votredomaine.fr 
 --deploy-hook synology_dsm

Le paramètre SYNO_CREATE=1 force la création d'un nouveau certificat plutôt que le remplacement d'un existant. Sans lui, si votre account.conf contient un SYNO_CERTIFICATE par défaut hérité d'une installation précédente, le déploiement écrase le mauvais certificat.

Ajoutez ensuite ces deux commandes à votre tâche planifiée existante pour le renouvellement automatique tous les 90 jours.

Étape 6 : Reverse proxy DSM

Dans Panneau de configuration → Portail de connexion → Proxy inversé → Créer :

Champ Valeur
Protocole source HTTPS
Hostname source trajets.votredomaine.fr
Port source 443
Protocole destination HTTP
Hostname destination localhost
Port destination 3765

Dans l'onglet En-têtes personnalisés, cliquez Créer → WebSocket pour ajouter les deux headers WebSocket automatiquement. Dawarich en a besoin pour les mises à jour en temps réel de l'interface.

Étape 7 : Assigner le certificat (LE piège qui m'a coûté 2 heures)

C'est l'étape que personne ne mentionne dans les tutos, et c'est pourtant elle qui a failli me faire abandonner. Vous avez un certificat valide, un reverse proxy correctement configuré, et pourtant votre navigateur affiche une erreur SSL avec le certificat par défaut de Synology. Explication : DSM ne sert pas automatiquement le bon certificat au bon service. Il faut lui dire explicitement.

Allez dans Panneau de configuration → Sécurité → Certificat. Cliquez sur le bouton Paramètres (en haut à droite). Dans la liste qui s'affiche, trouvez la ligne correspondant à trajets.votredomaine.fr et sélectionnez dans la colonne "Certificat" votre certificat Let's Encrypt dédié. Appliquez.

C'est tout. Et pourtant c'est ça qui m'a bloqué le plus longtemps. Les certificats étaient parfaitement générés et déployés depuis le début, ils n'étaient juste pas assignés au bon service. DSM continuait à servir son certificat Synology par défaut, d'où l'erreur SSL.

Test final

Pour vérifier que tout est en ordre, depuis un terminal :

curl -I https://trajets.votredomaine.fr

Vous devez obtenir HTTP/2 200. Dans Firefox : aucune erreur SSL, cadenas fermé dans la barre d'adresse. Si vous voyez encore une alerte de sécurité malgré tout, videz le cache SSL du navigateur avec Ctrl+Shift+R, Firefox est parfois têtu sur ce point.

Premier accès et import de l'historique Google

Naviguez sur https://trajets.votredomaine.fr. Les identifiants par défaut sont demo@dawarich.app / password. Changez le mot de passe immédiatement dans les paramètres du compte.

Pour importer votre historique Google, il faut d'abord l'exporter depuis votre téléphone. Et c'est là que Google a fait fort en termes d'ergonomie inversée.

Sur Android, ils ont sorti le bouton d'export de l'application Google Maps pour l'enterrer dans les paramètres système :

  1. Quittez Google Maps et ouvrez l'application Paramètres de votre téléphone
  2. Appuyez sur Position (ou Localisation)
  3. Appuyez sur Services de localisation
  4. Cherchez et sélectionnez Vos trajets (ou Google - Vos trajets)
  5. Vous trouverez enfin l'option Exporter les données de vos trajets

Sur iOS, c'est heureusement resté dans Google Maps : photo de profil → Paramètres → Confidentialité et localisation → Exporter les données de vos trajets.

Le fichier généré s'appelle Timeline.json sur Android ou location-history.json sur iOS. Transférez-le sur votre PC puis importez-le dans Dawarich via le menu Import de l'interface web. Dawarich accepte les deux formats sans manipulation.

Cas des gros fichiers : le mur de la RAM

L'interface web fonctionne très bien pour les fichiers légers. En revanche, si vous avez plusieurs années d'historique, votre fichier peut dépasser les 100 Mo, et là, les ennuis commencent. Sur un NAS équipé de 2 Go de RAM comme le DS220+, j'ai découvert à mes dépens qu'il ne suffit pas de contourner le timeout du navigateur : c'est le NAS lui-même qui capitule.

Le symptôme est traître : l'import reste bloqué sur "Created" dans l'interface, le compteur de points reste désespérément à zéro, et les logs de dawarich_sidekiq affichent des erreurs Connection timed out sur Redis. La cause est mécanique : le parser JSON de Ruby charge l'intégralité du fichier en mémoire avant de commencer à le traiter. Un fichier de 94 Mo peut ainsi réclamer plus d'1 Go de RAM rien que pour être lu, ce qui, sur un NAS qui fait déjà tourner DSM, PostgreSQL et Redis, provoque un freeze en règle et la coupure en cascade des containers Docker.

La solution n'est pas de compresser le fichier, mais de le découper. On passe d'un seul bloc indigeste à une série de petits fichiers que le NAS peut traiter séquentiellement, sans jamais saturer la mémoire.

Étape A : Préparer le script de conversion

Autre surprise de taille : Google a silencieusement changé le format de ses exports en 2024. L'ancien format utilisait une clé locations avec des coordonnées en entiers (latitudeE7, longitudeE7) et des timestamps en millisecondes. Le nouveau format, lui, s'appelle semanticSegments et stocke les coordonnées sous forme de chaînes de caractères ("45.763523°, 4.853053°") réparties dans plusieurs types d'entrées : timelinePath pour les points de trajet, visit pour les lieux visités, activity pour les déplacements, et position pour les positions brutes. Dawarich ne reconnaît pas ce nouveau format nativement, il faut donc le convertir avant l'import.

Voici le script Python qui s'occupe des deux opérations à la fois : conversion du format Google 2024 vers le format locations attendu par Dawarich, et découpage en fichiers de 20 000 points :

import json
import os
import sys
from datetime import datetime, timezone

INPUT_FILE = "histo.json"
OUTPUT_DIR = "chunks"
POINTS_PER_FILE = 20000

def parse_latlng(s):
 if not s:
 return None
 cleaned = s.replace('°', '').replace(' ', '')
 parts = cleaned.split(',')
 if len(parts) != 2:
 return None
 try:
 lat = float(parts[0])
 lng = float(parts[1])
 if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
  return None
 return int(lat * 1e7), int(lng * 1e7)
 except ValueError:
 return None

def parse_timestamp(ts_str):
 if not ts_str:
 return None
 try:
 ts_clean = ts_str.replace('Z', '+00:00')
 dt = datetime.fromisoformat(ts_clean)
 return int(dt.timestamp() * 1000)
 except Exception:
 return None

def ts_to_iso(ts_ms):
 dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc)
 return dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')

def extract_points_from_segment(segment):
 points = []

 if 'timelinePath' in segment:
 for path_point in segment['timelinePath']:
  coords = parse_latlng(path_point.get('point'))
  ts_ms = parse_timestamp(path_point.get('time'))
  if coords and ts_ms:
  lat_e7, lng_e7 = coords
  points.append({
   "latitudeE7": lat_e7, "longitudeE7": lng_e7,
   "timestamp": ts_to_iso(ts_ms), "accuracy": 50
  })

 if 'visit' in segment:
 visit = segment['visit']
 top = visit.get('topCandidate', {})
 coords = parse_latlng(top.get('placeLocation', {}).get('latLng'))
 ts_start = parse_timestamp(segment.get('startTime'))
 ts_end = parse_timestamp(segment.get('endTime'))
 if coords and ts_start:
  lat_e7, lng_e7 = coords
  ts_ms = ts_start if not ts_end else (ts_start + ts_end) // 2
  points.append({
  "latitudeE7": lat_e7, "longitudeE7": lng_e7,
  "timestamp": ts_to_iso(ts_ms), "accuracy": 100
  })

 if 'activity' in segment:
 activity = segment['activity']
 for loc_key, time_key in [('start', 'startTime'), ('end', 'endTime')]:
  coords = parse_latlng(activity.get(loc_key, {}).get('latLng'))
  ts_ms = parse_timestamp(segment.get(time_key))
  if coords and ts_ms:
  lat_e7, lng_e7 = coords
  points.append({
   "latitudeE7": lat_e7, "longitudeE7": lng_e7,
   "timestamp": ts_to_iso(ts_ms), "accuracy": 50
  })

 if 'position' in segment:
 pos = segment['position']
 coords = parse_latlng(pos.get('LatLng'))
 ts_ms = parse_timestamp(pos.get('timestamp'))
 if coords and ts_ms:
  lat_e7, lng_e7 = coords
  point = {
  "latitudeE7": lat_e7, "longitudeE7": lng_e7,
  "timestamp": ts_to_iso(ts_ms),
  "accuracy": int(pos.get('accuracyMeters', 50))
  }
  if 'altitudeMeters' in pos:
  point['altitude'] = pos['altitudeMeters']
  points.append(point)

 return points

def convert_and_split(input_file, output_dir, points_per_file):
 print(f"Chargement de {input_file}...")
 with open(input_file, 'r', encoding='utf-8') as f:
 data = json.load(f)

 segments = data.get('semanticSegments', [])
 print(f"{len(segments)} segments trouvés. Extraction en cours...")

 all_points = []
 for i, segment in enumerate(segments):
 all_points.extend(extract_points_from_segment(segment))
 if (i + 1) % 5000 == 0:
  print(f" ... {i+1}/{len(segments)} segments ({len(all_points)} points)")

 all_points.sort(key=lambda p: p.get('timestamp', ''))

 seen = set()
 unique_points = []
 for p in all_points:
 key = (p['timestamp'], p['latitudeE7'], p['longitudeE7'])
 if key not in seen:
  seen.add(key)
  unique_points.append(p)

 print(f"{len(unique_points)} points uniques après dédoublonnage.")
 os.makedirs(output_dir, exist_ok=True)

 for i, start in enumerate(range(0, len(unique_points), points_per_file)):
 chunk = {"locations": unique_points[start:start + points_per_file]}
 filename = os.path.join(output_dir, f"dawarich_part_{i:04d}.json")
 with open(filename, 'w', encoding='utf-8') as f:
  json.dump(chunk, f, ensure_ascii=False, separators=(',', ':'))
 print(f" ✅ {filename} ({len(chunk['locations'])} points)")

 print(f"nTerminé : {i+1} fichiers créés dans /{output_dir}/")

if __name__ == "__main__":
 convert_and_split(
 sys.argv[1] if len(sys.argv) > 1 else INPUT_FILE,
 sys.argv[2] if len(sys.argv) > 2 else OUTPUT_DIR,
 int(sys.argv[3]) if len(sys.argv) > 3 else POINTS_PER_FILE
 )

Lancez-le depuis votre PC (Python 3.7+ requis, aucune dépendance externe) :

python convert_google.py

Le script produit une série de fichiers dawarich_part_0000.json, dawarich_part_0001.json, etc. dans un sous-dossier chunks/. Chaque fichier pèse environ 3 Mo une fois minifié, une taille que le NAS digère sans effort. La minification (suppression des espaces inutiles dans le JSON) réduit le poids des fichiers d'environ 80% sans perdre aucune donnée de géolocalisation.

Étape B : Déposer les fichiers dans le dossier watched

Copiez l'ensemble des fichiers chunks/ directement dans le dossier watched de votre installation :

/volume1/docker/dawarich/watched/

Vous pouvez le faire via File Station par glisser-déposer, ou via SSH avec scp depuis votre PC.

Étape C : Vérifier que Redis est opérationnel

Si vous avez suivi l'étape 2 de ce tuto et copié le docker-compose.yml tel quel, Redis est déjà correctement configuré grâce aux options --stop-writes-on-bgsave-error no --save "" dans la ligne command. Ces options sont persistantes : elles survivent aux redémarrages du NAS et des containers.

Si vous avez une installation existante avec l'ancien docker-compose.yml, vérifiez que la ligne a bien été mise à jour, puis appliquez la modification :

cd /volume1/docker/dawarich
sudo docker compose stop dawarich_redis
sudo docker compose rm -f dawarich_redis
sudo rm -f /volume1/docker/dawarich/redis/dump.rdb
sudo docker compose up -d dawarich_redis

Pour confirmer que Redis est sain avant de lancer l'import :

sudo docker exec dawarich_redis redis-cli config get stop-writes-on-bgsave-error
# doit retourner : no

sudo docker exec dawarich_redis redis-cli ping
# doit retourner : PONG

Sur le système hôte, ajoutez également cette commande pour éviter les avertissements mémoire de Redis :

sudo sysctl vm.overcommit_memory=1

Étape D : Lancer les imports en mode "Bulldozer" (la méthode qui fonctionne sur DS220+)

L'interface web et les commandes d'import classiques utilisent Sidekiq et Redis. Sur un NAS avec peu de RAM comme le DS220+, c'est la recette assurée pour un crash. On va donc utiliser le "mode Bulldozer" : on court-circuite la file d'attente pour exécuter les tâches directement en mémoire, une par une.

Ouvrez un terminal SSH et lancez ce script unique. Il va scanner votre dossier, ignorer ce qui est déjà fait, et traiter le reste avec une pause de 30 secondes entre chaque fichier pour laisser le NAS "respirer" :

sudo docker exec -it dawarich_app bin/rails runner "
user = User.find_by(email: 'votre@email.fr')
dir = '/var/app/tmp/imports/watched'

Dir.glob("#{dir}/dawarich_part_*.json").sort.each do |path|
 name = File.basename(path)

 # On saute ce qui est déjà terminé
 if Import.exists?(user: user, name: name, status: 'completed')
 puts "⏭️ Déjà fait : #{name}"
 next
 end

 # Nettoyage des anciennes tentatives bloquées
 Import.where(user: user, name: name).destroy_all

 import = Import.new(name: name, user: user, source: :google_records)
 import.file.attach(io: File.open(path), filename: name, content_type: 'application/json')

 if import.save
 puts "n🚀 Traitement de #{name}..."
 begin
  # Le secret : perform_now au lieu de perform_later
  Import::ProcessJob.perform_now(import.id)
  import.reload
  puts "✅ Terminé : #{name} → #{import.points_count || 0} points."

  # Crucial pour la survie de la RAM sur le NAS
  sleep 30
 rescue => e
  puts "❌ Erreur sur #{name} : #{e.message}"
 end
 end
end
puts "n🏆 Import global terminé !"
"

Contrairement à la méthode officielle qui envoie les données dans Redis (le maillon faible sur NAS), perform_now exécute le code directement dans le processus Rails.

  • Zéro concurrence : on traite un fichier à la fois, séquentiellement.
  • Zéro dépendance Redis : on ne dépend plus de la stabilité du cache pour l'import.
  • Gestion de la RAM : le sleep 30 permet au Garbage Collector de Ruby de libérer la mémoire vive entre chaque chunk.
  • Reprise automatique : si le script est interrompu, il reprend là où il en était au prochain lancement grâce au contrôle du statut completed.

Pour vérifier l'avancement de vos points en temps réel dans un autre terminal :

sudo docker exec dawarich_app bin/rails runner "puts User.find_by(email: 'votre@email.fr').points.count"

Étape E : Méthode alternative via Sidekiq (serveurs avec plus de RAM)

Si votre machine dispose de suffisamment de RAM (4 Go ou plus), vous pouvez utiliser la méthode originale basée sur Sidekiq, plus rapide car elle traite plusieurs fichiers en parallèle. Pour chaque fichier chunk, lancez :

sudo docker exec -it dawarich_app bin/rails 
 "import:big_file[/var/app/tmp/imports/watched/dawarich_part_0000.json,votre@email.fr]"

Conservez bien les guillemets autour de la commande, sans eux, le terminal interprète mal les crochets. Attendez que le premier fichier soit entièrement traité avant de lancer le suivant. Vous pouvez suivre la progression en temps réel avec :

sudo docker logs -f dawarich_sidekiq

Faites Ctrl+C pour quitter la vue des logs. Si vous avez de nombreux fichiers à importer, le script bash import_chunks.sh suivant automatise cette attente et enchaîne les imports sans intervention manuelle :

#!/bin/bash
# import_chunks.sh
# Usage : bash import_chunks.sh votre@email.fr

EMAIL="${1:-votre@email.fr}"
WATCHED_DIR="/volume1/docker/dawarich/watched"
CONTAINER="dawarich_app"
CHECK_INTERVAL=30
MAX_WAIT=7200

GREEN=' 33[0;32m'
YELLOW=' 33[1;33m'
RED=' 33[0;31m'
NC=' 33[0m'

echo "=================================================="
echo " Import automatique Dawarich"
echo " Email : $EMAIL"
echo " Dossier : $WATCHED_DIR"
echo "=================================================="

FILES=$(ls -1 "$WATCHED_DIR"/dawarich_part_*.json 2>/dev/null | sort)

if [ -z "$FILES" ]; then
 echo -e "${RED}Aucun fichier dawarich_part_*.json trouvé dans $WATCHED_DIR${NC}"
 exit 1
fi

TOTAL=$(echo "$FILES" | wc -l)
echo -e "n$TOTAL fichiers trouvésn"

COUNT=0
for FILEPATH in $FILES; do
 FILENAME=$(basename "$FILEPATH")
 COUNT=$((COUNT + 1))

 echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
 echo -e "[$COUNT/$TOTAL] Import de $FILENAME"
 echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"

 POINTS_BEFORE=$(sudo docker exec "$CONTAINER" 
 bin/rails runner "puts User.find_by(email: '$EMAIL')&.points&.count.to_i" 
 2>/dev/null | tail -1)
 POINTS_BEFORE=${POINTS_BEFORE:-0}
 echo " Points avant import : $POINTS_BEFORE"

 sudo docker exec "$CONTAINER" 
 bin/rails "import:big_file[/var/app/tmp/imports/watched/$FILENAME,$EMAIL]"

 if [ $? -ne 0 ]; then
 echo -e "${RED} Erreur lors du lancement de $FILENAME — on continue dans 10s${NC}"
 sleep 10
 continue
 fi

 echo " Job créé. Attente du traitement par Sidekiq..."

 ELAPSED=0
 LAST_POINTS=$POINTS_BEFORE
 STABLE_COUNT=0

 while [ $ELAPSED -lt $MAX_WAIT ]; do
 sleep $CHECK_INTERVAL
 ELAPSED=$((ELAPSED + CHECK_INTERVAL))

 PENDING=$(sudo docker exec dawarich_redis 
  redis-cli llen "queue:default" 2>/dev/null | tr -d '[:space:]')
 PENDING=${PENDING:-0}

 POINTS_NOW=$(sudo docker exec "$CONTAINER" 
  bin/rails runner "puts User.find_by(email: '$EMAIL')&.points&.count.to_i" 
  2>/dev/null | tail -1)
 POINTS_NOW=${POINTS_NOW:-0}

 ADDED=$((POINTS_NOW - POINTS_BEFORE))
 echo " ${ELAPSED}s | Points ajoutés : $ADDED | Jobs en attente : $PENDING"

 if [ "$POINTS_NOW" = "$LAST_POINTS" ] && [ "$PENDING" = "0" ]; then
  STABLE_COUNT=$((STABLE_COUNT + 1))
  if [ $STABLE_COUNT -ge 2 ]; then
  echo -e "${GREEN} Import terminé : $ADDED points ajoutés.${NC}"
  break
  fi
 else
  STABLE_COUNT=0
 fi

 LAST_POINTS=$POINTS_NOW
 done

 if [ $ELAPSED -ge $MAX_WAIT ]; then
 echo -e "${RED} Timeout atteint pour $FILENAME. On passe au suivant.${NC}"
 fi

 echo " Pause de 15 secondes avant le prochain fichier..."
 sleep 15
done

echo ""
echo -e "${GREEN}=================================================="
echo " Tous les imports sont terminés !"
echo -e "==================================================${NC}"

TOTAL_POINTS=$(sudo docker exec "$CONTAINER" 
 bin/rails runner "puts User.find_by(email: '$EMAIL')&.points&.count.to_i" 
 2>/dev/null | tail -1)
echo " Total de points dans Dawarich : $TOTAL_POINTS"
bash import_chunks.sh votre@email.fr

Commandes utiles :

Si vous avez des soucis d'imports :

Nettoyer les imports ratés et bloqués :

sudo docker exec -it dawarich_app bin/rails runner "
failed = Import.where(status: [:failed, :created, :processing])
puts "#{failed.count} imports à supprimer"
failed.destroy_all
puts 'Nettoyé.'
"

Vider la file Redis des jobs fantômes :

sudo docker exec dawarich_redis redis-cli flushdb

Vérifier que Redis est sain :

sudo docker exec dawarich_redis redis-cli config get stop-writes-on-bgsave-error
# doit retourner : no

sudo docker exec dawarich_redis redis-cli ping
# doit retourner : PONG

Si le config get retourne yes, c'est que le container Redis a été recréé sans prendre en compte le docker-compose.yml modifié. Dans ce cas :

sudo docker compose -f /volume1/docker/dawarich/docker-compose.yml stop dawarich_redis
sudo docker compose -f /volume1/docker/dawarich/docker-compose.yml rm -f dawarich_redis
sudo rm -f /volume1/docker/dawarich/redis/dump.rdb
sudo docker compose -f /volume1/docker/dawarich/docker-compose.yml up -d dawarich_redis

Tracking automatique continu

L'import de l'historique passé, c'est bien. Mais le vrai intérêt de Dawarich, c'est de continuer à enregistrer vos trajets automatiquement, comme Google le faisait. Pour ça, installez l'application Dawarich (disponible sur iOS et Android), configurez l'URL de votre instance et la clé API que vous trouverez dans les paramètres de votre compte Dawarich. L'application tourne en fond et envoie vos positions automatiquement, avec un impact minimal sur la batterie.

Et voilà : vos données de trajets sont de nouveau accessibles depuis un navigateur PC, hébergées chez vous, sans pub, sans revente de données, et avec un renouvellement automatique qui ne vous demandera plus jamais rien. Le tout pour le prix d'un peu d'électricité supplémentaire sur votre NAS.