feat(services): serveur de sync Anki auto-hébergé (anki.marcpartensky.com)

This commit is contained in:
Hermes Agent
2026-10-09 17:38:38 -04:00
parent 727dd5ad31
commit 917ebe40d3
4 changed files with 85 additions and 0 deletions
+4
View File
@@ -88,3 +88,7 @@ creation_rules:
key_groups:
- age:
- *tower
- path_regex: secrets/anki\.yml$
key_groups:
- age:
- *tower
+16
View File
@@ -0,0 +1,16 @@
password: ENC[AES256_GCM,data:YD0/j/xerPFCVpxo4VZAiqWPuf7MLw0F,iv:Gx9IxUs1SEsehzUN8l08sYlhFCLGHBtDTLcw7VHJhis=,tag:pNsOCkJ0AKfh678bxZJ7ZA==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGQlNFUHowVDZoRVU3dUE3
eGkzOFZmMnhjbzd4NVJ1cHYzUmRQUVJLRm1jCnE5K0JiWCtoQzQ4WWxMbURSMVdv
TDVuTVV5dlpLQml0Yk14R1I3dXJZYUkKLS0tIC9sd2xmNjc5d25jZ2dvRDg0WUgx
S2prWFI5OEtzUmw3aFFSaXFFWlRpUDgKTruEdbNXcS6HjBLQsE4BfUbXbwxxnwum
xwGLamJWV3y6yBeUdvXS4RyV6/gTtHeEE6CJ8aRFnuLraYkXWjL9jw==
-----END AGE ENCRYPTED FILE-----
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
lastmodified: "2026-10-09T21:09:41Z"
mac: ENC[AES256_GCM,data:I0F0oVbr/8CAT0dHuHfHvvDugXj1e5i5psCTNOJjl2HTdZwA6yDtsTbmrlE3hQNm3gNgt8N8CFhLc59lSIv+MYt6amWUDBKwW2kzSiUT554piRUoVyhwY9lFSpAzRSQXG3aaeBQxOSAkI1Nd9CeXLKIHk+qMnvKoPUKQwGS7L60=,iv:XqmMnst4W993YZ4T2f3JJaKZomKJ6bibMm0lRE7Iul4=,tag:TFVL1wKdwVnG8ZKHT+mMRw==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.3
+64
View File
@@ -0,0 +1,64 @@
# services/anki-sync-server/default.nix
# Serveur de synchronisation Anki auto-hébergé, implémentation OFFICIELLE en
# Rust : le paquet nixpkgs `anki-sync-server` compile uniquement le binaire
# 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.
#
# 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,
...
}: {
sops.secrets."anki-sync-server/password" = {
key = "password";
sopsFile = ../../secrets/anki.yml;
};
services.anki-sync-server = {
enable = true;
address = "127.0.0.1";
# port par défaut : 27701
users = [
{
username = "marc";
passwordFile = config.sops.secrets."anki-sync-server/password".path;
}
];
};
# 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.
services.newt.blueprint.proxy-resources."anki-sync-server" = {
name = "anki-sync-server";
protocol = "http";
full-domain = "anki.marcpartensky.com";
auth.sso-enabled = false;
targets = [
{
hostname = "127.0.0.1";
port = config.services.anki-sync-server.port;
method = "http";
}
];
};
# Garde-fou : le port local ne doit jamais être ouvert 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).";
}
];
}
+1
View File
@@ -63,6 +63,7 @@
./gitea-mcp
./openrouter-mcp
./reactive-resume
./anki-sync-server
./winnie
# ./kanidm
# ./syncserver