diff --git a/services/anki-sync-server/default.nix b/services/anki-sync-server/default.nix
index 1cd9945..2a998e7 100644
--- a/services/anki-sync-server/default.nix
+++ b/services/anki-sync-server/default.nix
@@ -4,19 +4,68 @@
# standalone depuis le repo ankitects/anki (même version que le desktop Anki,
# 25.09.4 dans la nixpkgs épinglée). Zéro Python.
#
-# Écoute en loopback (127.0.0.1:27701), exposé uniquement via le blueprint
-# Pangolin/newt sur anki.marcpartensky.com. PAS de SSO Pangolin : les clients
-# Anki (desktop, AnkiMobile, AnkiDroid) ne savent pas suivre une redirection
-# navigateur ; l'authentification repose sur le couple utilisateur/mot de passe
-# du serveur Anki lui-même (SYNC_USER), transmis en HTTPS par le reverse proxy.
+# Le serveur écoute en loopback (127.0.0.1:27701) et ne sert AUCUNE page web
+# (404 vide sur /, ce qui veut dire « rien » dans un navigateur). Une façade
+# nginx en loopback (127.0.0.1:27702) sert une page de statut sur / et proxifie
+# les routes de sync (/sync/*, /msync/*, /health) vers 27701. Le blueprint
+# Pangolin/newt expose la façade sur anki.marcpartensky.com.
+# PAS de SSO Pangolin : les clients Anki (desktop, AnkiMobile, AnkiDroid) ne
+# savent pas suivre une redirection navigateur ; l'authentification repose sur
+# le couple utilisateur/mot de passe du serveur Anki lui-même (SYNC_USER),
+# transmis en HTTPS par le reverse proxy.
#
# Mot de passe du compte "marc" dans secrets/anki.yml (sops). Le module nixpkgs
# lit passwordFile via LoadCredential systemd (jamais en clair dans le store).
{
config,
lib,
+ pkgs,
...
-}: {
+}: let
+ syncPort = 27701; # port du serveur Anki (loopback)
+ proxyPort = 27702; # port de la façade nginx (loopback)
+ statusPage = pkgs.stdenv.mkDerivation {
+ pname = "anki-sync-status";
+ version = "1";
+ src = pkgs.writeTextFile {
+ name = "anki-sync-status.html";
+ text = ''
+
+
+
+
+
+ Anki Sync Server
+
+
+
+
+
Anki Sync Server
+
en ligne
+
Ce domaine expose uniquement l'API de synchronisation Anki pour les clients de bureau et mobiles. Il n'y a pas d'interface web à afficher ici.
+
Dans Anki (préférences, onglet synchronisation), renseignez https://anki.marcpartensky.com comme serveur de sync.
+
Santé du service : GET /health
+
+
+
+ '';
+ };
+ dontUnpack = true;
+ installPhase = ''
+ runHook preInstall
+ mkdir -p $out
+ cp $src $out/index.html
+ '';
+ meta.description = "Page de statut du serveur de sync Anki";
+ };
+in {
sops.secrets."anki-sync-server/password" = {
key = "password";
sopsFile = ../../secrets/anki.yml;
@@ -34,12 +83,47 @@
];
};
+ # Façade nginx, loopback uniquement : page de statut sur / (le serveur Anki
+ # n'a pas d'UI), proxy des routes de sync vers 27701. Timeouts longs (grosses
+ # collections), pas de limite de taille de corps (uploads de médias : images,
+ # vidéos).
+ services.nginx.virtualHosts."anki-sync-status" = {
+ listen = [
+ {
+ addr = "127.0.0.1";
+ port = proxyPort;
+ }
+ ];
+ extraConfig = "client_max_body_size 0;";
+ locations = {
+ "/sync" = {
+ proxyPass = "http://127.0.0.1:${toString syncPort}";
+ extraConfig = ''
+ proxy_read_timeout 3600s;
+ proxy_send_timeout 3600s;
+ '';
+ };
+ "/msync" = {
+ proxyPass = "http://127.0.0.1:${toString syncPort}";
+ extraConfig = ''
+ proxy_read_timeout 3600s;
+ proxy_send_timeout 3600s;
+ '';
+ };
+ "/health" = {
+ proxyPass = "http://127.0.0.1:${toString syncPort}";
+ };
+ "/" = {
+ root = statusPage;
+ };
+ };
+ };
+
# Ressource publique Pangolin (newt, site "tower") : anki.marcpartensky.com
- # -> 127.0.0.1:27701. Cle `proxy-resources` + champ `protocol` = schema
- # réellement compris par le Pangolin du VPS (cf. services/newt). Pas d'auth
- # Pangolin (clients natifs). Pas de healthcheck http : le serveur de sync ne
- # sert aucun GET 2xx (les routes /sync/* et /msync/* sont du POST), donc un
- # check sur "/" le marquerait unhealthy et Pangolin le sortirait du LB.
+ # -> 127.0.0.1:27702 (façade nginx). Cle `proxy-resources` + champ
+ # `protocol` = schema réellement compris par le Pangolin du VPS (cf.
+ # services/newt). Pas d'auth Pangolin (clients natifs). Pas de healthcheck
+ # déclaré : la façade répond 200 sur /, donc le check par défaut passe.
services.newt.blueprint.proxy-resources."anki-sync-server" = {
name = "anki-sync-server";
protocol = "http";
@@ -48,17 +132,19 @@
targets = [
{
hostname = "127.0.0.1";
- port = config.services.anki-sync-server.port;
+ port = proxyPort;
method = "http";
}
];
};
- # Garde-fou : le port local ne doit jamais être ouvert au firewall/LAN.
+ # Garde-fou : les ports locaux ne doivent jamais être ouverts au
+ # firewall/LAN.
assertions = [
{
- assertion = !(lib.elem config.services.anki-sync-server.port config.networking.firewall.allowedTCPPorts);
- message = "anki-sync-server : le port local ne doit pas être ouvert au firewall (exposition uniquement via Pangolin/newt).";
+ assertion = !(lib.elem syncPort config.networking.firewall.allowedTCPPorts)
+ && !(lib.elem proxyPort config.networking.firewall.allowedTCPPorts);
+ message = "anki-sync-server : les ports locaux ne doivent pas être ouverts au firewall (exposition uniquement via Pangolin/newt).";
}
];
}
\ No newline at end of file