Servidor de sincronización para NSLY. Implementa exactamente el protocolo que el cliente ya espera:
PUT {URL} Authorization: Bearer {token} <body = blob cifrado> → guarda
GET {URL} Authorization: Bearer {token} → devuelve
El servidor nunca ve tus contraseñas en claro. Solo guarda y devuelve el blob que NSLY ya cifró con AES-256-GCM antes de subirlo — es un almacén ciego. Aunque alguien comprometiera este servidor por completo, solo se llevaría bytes cifrados.
Es multi-tenant: un solo servidor puede alojar varias bóvedas (vos, tu
pareja, tu equipo...), cada una en /vault/{id} con su propio token. Para
compatibilidad con el ejemplo mínimo de un solo usuario del README
principal, /vault (sin id) equivale a /vault/default.
Cero dependencias externas — todo es librería estándar de Go. Se compila a un único binario estático, igual de portable que el propio NSLY.
cd server-example
go test ./... # correr los tests (ver qué se cubre más abajo)
make build # compila ./nsly-sync-server
./nsly-sync-server adduser andres
# ✅ Bóveda creada: andres
# ⚠️ Este token se muestra UNA sola vez. Anótalo en un lugar seguro:
# aB3x... (256 bits, cópialo ahora)
#
# Configura en NSLY → ☁️ Sync → ⚙️ Configurar:
# URL: https://tu-servidor/vault/andres
# Token: (el de arriba)
NSLY_LISTEN=:8080 ./nsly-sync-server serveEn NSLY: Menú → ☁️ Sync → ⚙️ Configurar con esa URL y ese token.
⚠️ El cliente NSLY rechaza URLs que no empiecen conhttps://(a propósito, para no viajar el token en texto plano). Para desarrollo local podés generar un certificado autofirmado o simplemente probar el servidor concurlsin pasar por NSLY todavía — ver más abajo.
Estos corren sin necesidad de tener el servidor levantado (operan
directo sobre tokens.json):
| Comando | Qué hace |
|---|---|
adduser <id> |
Crea una bóveda nueva y genera su token (se muestra una sola vez) |
rotate <id> |
Genera un token nuevo para una bóveda existente; el anterior queda invalidado al instante |
rmuser <id> [--delete-data] |
Revoca el acceso; con --delete-data borra también el blob cifrado |
list |
Lista los ids de bóvedas registradas (nunca muestra tokens) |
Todos aceptan --data-dir (default: $NSLY_DATA_DIR o ./data).
| Variable | Default | Descripción |
|---|---|---|
NSLY_LISTEN |
:8443 |
Dirección/puerto a escuchar |
NSLY_DATA_DIR |
./data |
Dónde viven tokens.json y los blobs .blob |
NSLY_TOKENS_FILE |
<data-dir>/tokens.json |
Ruta del archivo de tokens |
NSLY_MAX_BODY_BYTES |
5242880 (5 MiB) |
Límite de tamaño del blob subido |
NSLY_TLS_CERT / NSLY_TLS_KEY |
(vacío) | Si ambas están seteadas, sirve HTTPS directo sin proxy |
- Round-trip PUT → GET devuelve exactamente los mismos bytes.
- PUT/GET sin token, o con token incorrecto →
401, sin importar si el id existe. - GET de una bóveda que nunca recibió un PUT →
404(primer sync). - Ruta legacy
/vault(sin id) → mapea adefault, para compatibilidad. - Body más grande que el límite configurado →
413. - Un id con
..o/(intento de path traversal) →400, nunca llega al filesystem. - Método no soportado (
POST, etc.) →405. - Body vacío →
400. - Al subir una segunda versión, la anterior queda en
<id>.blob.bak(rollback manual barato). rotateinvalida el token anterior de inmediato.- El store de tokens persiste correctamente a disco y se recarga bien.
cp Caddyfile.example Caddyfile
# editá el dominio en Caddyfile
docker compose up -d
docker compose exec nsly-sync /nsly-sync-server adduder andres # (ver nota abajo)Nota: la imagen Docker es
distroless(sin shell, endurecida), así quedocker compose exec ... shno existe. Para crear usuarios contra los datos del volumen, corré el binario compilado localmente apuntando al mismoNSLY_DATA_DIRmontado, o agregá un contenedor "admin" temporal con la imagengolang:alpineque monte el volumennsly-data.
docker-compose.yml deja el servidor NSLY solo en la red interna
(expose, no ports) — únicamente Caddy queda expuesto a internet,
terminando TLS y renovando el certificado solo.
make release # cross-compila a dist/
sudo useradd --system --home /var/lib/nsly-sync --create-home nsly-sync
sudo cp dist/nsly-sync-server-linux-amd64 /usr/local/bin/nsly-sync-server
sudo cp nsly-sync-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nsly-sync-server
sudo -u nsly-sync nsly-sync-server adduser andres --data-dir /var/lib/nsly-sync/dataLuego apuntá tu nginx (nginx.conf.example) o Caddy existente a
127.0.0.1:8080 (o el puerto que hayas puesto en NSLY_LISTEN).
Para un homelab o un uso muy simple, sin nginx/Caddy delante:
NSLY_LISTEN=:8443 \
NSLY_TLS_CERT=/ruta/fullchain.pem \
NSLY_TLS_KEY=/ruta/privkey.pem \
./nsly-sync-server serveHace:
- Compara tokens en tiempo constante (
crypto/subtle) — no filtra el token por timing. - Guarda solo
sha256(token), nunca el token en claro. - Aplica un delay fijo (300ms) ante auth fallida, para encarecer fuerza bruta.
- Valida los ids de bóveda contra una lista blanca de caracteres — cero path traversal posible.
- Escribe los blobs de forma atómica (
.tmp+rename) y conserva la versión anterior en.bak. - Limita el tamaño del body subido (
NSLY_MAX_BODY_BYTES). - Apagado ordenado ante SIGINT/SIGTERM (no corta un PUT a la mitad).
NO hace (y es intencional):
- No descifra ni entiende el contenido de tu bóveda — es un almacén ciego.
- No reemplaza HTTPS: si corrés
servesinNSLY_TLS_CERT/NSLY_TLS_KEYy sin poner un reverse proxy con TLS delante, el tráfico viaja sin cifrar en la red. El cliente NSLY ya rechaza URLshttp://por esta razón — no hay forma de "saltarse" esto sin editar el propio NSLY. - No implementa rate-limiting sofisticado por IP (más allá del delay fijo en fallos):
a esta escala, un firewall (
fail2ban,ufw) delante es más apropiado que reinventarlo acá. - No mantiene un historial completo de versiones — solo un
.bak(la versión inmediatamente anterior). Si necesitás versionado real, lo más simple es apuntarNSLY_DATA_DIRa una carpeta bajo control degitorestic/borgcon snapshots periódicos.
Si ya tenías el snippet de Express corriendo: apagalo, montá este binario
apuntando a un NSLY_DATA_DIR nuevo, corré adduser default (mismo id que
usaba el ejemplo viejo), y actualizá el token en NSLY → Sync → Configurar.
La URL puede seguir siendo la misma (/vault) gracias a la compatibilidad
legacy.