Héberger son propre serveur Firefox Sync

J'utilise Firefox Sync depuis des années pour retrouver mes marque-pages et mots de passe sur tous mes appareils. Ça marche bien. Mais récemment, je me suis demandé : est-ce que je peux héberger ça moi-même ?

Spoiler : oui. Mais j'ai passé une heure à debugger une erreur que la documentation ne mentionne nulle part.

Pourquoi faire ça ?

Honnêtement ? Surtout par curiosité. Mozilla chiffre déjà les données côté client, donc même eux ne peuvent pas les lire (source). L'auto-hébergement ne change pas grand-chose à la vie privée — vos données étaient déjà illisibles.

Mais ça fait un service de moins qui dépend d'un tiers. Et c'était un bon prétexte pour apprendre comment Firefox Sync fonctionne.

Ce qu'il faut savoir avant de commencer

Mozilla a open-sourcé le serveur sous le nom syncstorage-rs. C'est du Rust, ça supporte PostgreSQL, MySQL ou Spanner.

Le truc un peu décevant : l'authentification reste chez Mozilla. Vous avez toujours besoin d'un compte Firefox pour vous connecter. Impossible de créer des comptes locaux. Votre serveur stocke les données, mais Mozilla gère l'identité.

Firefox → accounts.firefox.com (Mozilla) → votre serveur
            ↑ auth                           ↑ stockage

Prérequis

Si vous partez de zéro, j'ai publié: docker-compose-homelab. C'est le setup que j'utilise au quotidien : nginx-proxy pour le SSL automatique, et une architecture où chaque service est dans son propre fichier YAML.

L'installation

DNS

Rien de spécial, un enregistrement A vers votre serveur :

sync.example.org    A       1.2.3.4

Secrets

Générez trois secrets :

openssl rand -hex 32      # master secret
openssl rand -hex 32      # metrics secret
openssl rand -base64 24   # mot de passe postgres

Mettez-les dans .env :

SYNCSTORAGE_MASTER_SECRET=...
SYNCSTORAGE_METRICS_SECRET=...
SYNCSTORAGE_DB_USER=syncstorage
SYNCSTORAGE_DB_PASSWORD=...

Docker Compose

Voici le fichier complet. Un point important : l'image Docker mozilla/syncstorage-rs:latest ne marche pas avec PostgreSQL. Elle est compilée pour Spanner. Il faut utiliser une image avec le suffixe -postgres. J'ai perdu du temps là-dessus.

services:
  syncstorage:
    image: mozilla/syncstorage-rs:11659d98f9c69948a0aab353437ce2036c388711-postgres
    container_name: syncstorage
    environment:
      - SYNC_HOST=0.0.0.0
      - SYNC_MASTER_SECRET=${SYNCSTORAGE_MASTER_SECRET}
      - SYNC_SYNCSTORAGE__DATABASE_URL=postgres://${SYNCSTORAGE_DB_USER}:${SYNCSTORAGE_DB_PASSWORD}@syncstorage-db:5432/syncstorage
      - SYNC_SYNCSTORAGE__ENABLED=true
      - SYNC_TOKENSERVER__DATABASE_URL=postgres://${SYNCSTORAGE_DB_USER}:${SYNCSTORAGE_DB_PASSWORD}@syncstorage-tokendb:5432/tokenserver
      - SYNC_TOKENSERVER__ENABLED=true
      - SYNC_TOKENSERVER__RUN_MIGRATIONS=true
      - SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN=api.accounts.firefox.com
      - SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL=https://oauth.accounts.firefox.com
      - SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET=${SYNCSTORAGE_METRICS_SECRET}
      - VIRTUAL_HOST=sync.example.org
      - VIRTUAL_PORT=8000
    networks:
      - proxy-tier
      - syncstorage-internal
    restart: unless-stopped
    depends_on:
      syncstorage-db:
        condition: service_healthy
      syncstorage-tokendb:
        condition: service_healthy

  syncstorage-db:
    image: postgres:17-alpine
    container_name: syncstorage-db
    environment:
      - POSTGRES_DB=syncstorage
      - POSTGRES_USER=${SYNCSTORAGE_DB_USER}
      - POSTGRES_PASSWORD=${SYNCSTORAGE_DB_PASSWORD}
    volumes:
      - /data/syncstorage/postgres-sync:/var/lib/postgresql/data
    networks:
      - syncstorage-internal
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${SYNCSTORAGE_DB_USER} -d syncstorage"]
      interval: 10s
      timeout: 5s
      retries: 5

  syncstorage-tokendb:
    image: postgres:17-alpine
    container_name: syncstorage-tokendb
    environment:
      - POSTGRES_DB=tokenserver
      - POSTGRES_USER=${SYNCSTORAGE_DB_USER}
      - POSTGRES_PASSWORD=${SYNCSTORAGE_DB_PASSWORD}
    volumes:
      - /data/syncstorage/postgres-token:/var/lib/postgresql/data
    networks:
      - syncstorage-internal
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${SYNCSTORAGE_DB_USER} -d tokenserver"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  proxy-tier:
    external: true
  syncstorage-internal:

Créez les répertoires et lancez :

sudo mkdir -p /data/syncstorage/postgres-sync /data/syncstorage/postgres-token
sudo chown -R $(id -u):$(id -g) /data/syncstorage
docker compose -f docker-compose.yml -f syncstorage.yml up -d

L'étape que personne ne mentionne

Tout démarre, le healthcheck passe, le SSL fonctionne. Je configure Firefox, je me connecte et... erreur 503. Dans les logs Firefox (about:sync-log) :

"Unexpected error: unable to get a node"

J'ai cherché cette erreur pendant un moment. Le problème : le tokenserver a une table nodes qui liste les serveurs de stockage disponibles. Chez Mozilla, il y en a plusieurs pour la répartition de charge. Chez vous, il y en a un. Et il faut le déclarer manuellement.

docker exec syncstorage-tokendb psql -U syncstorage -d tokenserver -c \
  "INSERT INTO nodes (service, node, available, current_load, capacity, downed, backoff) \
   VALUES (1, 'https://sync.example.org', 1, 0, 1000000, 0, 0);"

Vérifiez :

docker exec syncstorage-tokendb psql -U syncstorage -d tokenserver -c "SELECT * FROM nodes;"

Après ça, tout fonctionne.

Configurer Firefox

Dans about:config, cherchez identity.sync.tokenserver.uri. Si ça n'existe pas, créez-le (clic droit → Nouveau → Chaîne). Valeur :

https://sync.example.org/1.0/sync/1.5

Redémarrez Firefox. C'est lu au démarrage, pas à chaud.

Si vous étiez déjà connecté avant de changer le tokenserver : déconnectez-vous, redémarrez, reconnectez-vous. Sinon Firefox continue d'utiliser l'ancien serveur en cache.

Vérifier que ça marche

Côté serveur, vous pouvez voir les données synchronisées :

docker exec syncstorage-db psql -U syncstorage -d syncstorage -c "
SELECT c.name as collection, COUNT(b.bso_id) as items
FROM bsos b
JOIN collections c ON b.collection_id = c.collection_id
GROUP BY c.name
ORDER BY items DESC;"

Chez moi après la première sync :

    collection     | items
-------------------+-------
 bookmarks         |    59
 addons            |     8
 tabs              |     2
 clients           |     2

Côté Firefox, about:sync-log doit montrer votre domaine, pas sync.services.mozilla.com.

Ce qui ne marche pas

Pas de migration. Les données qui étaient sur les serveurs Mozilla y restent. La première sync avec votre serveur part de zéro. Si vous avez des bookmarks sur deux machines avec des serveurs différents, vous aurez des fusions intéressantes.

Pas de comptes locaux. Vous dépendez toujours de Firefox Accounts pour l'authentification.

Conclusion

Ça marche. Mes deux Firefox (un Mac, un Linux) synchronisent via mon serveur. L'installation prend 20 minutes si vous connaissez l'astuce du node, une heure sinon.

Astuce: demandez à votre assistant IA de faire le job en mentionnant cette article !


Sources