Implémentation Stockage Personnel — ZFS + SFTPGo¶
Date : 2026
Solution retenue : ZFS (IronWolf 4TB) + Sanoid + SFTPGo v2.7.1+ + ZfDash
Évaluation complète : storage-solution-evaluation.md
1. Architecture Cible¶
Structure de stockage¶
IronWolf 4TB (sdd) — pool ZFS "storage"
storage/ (dataset racine)
├── alice/ ← dataset ZFS, espace alice
│ ├── drive/ ← rclone PULL drive Google alice
│ ├── photos/ ← Takeout alice
│ └── archives/ ← archives personnelles alice
├── nico/ ← dataset ZFS, espace nico (admin)
│ ├── drive/ ← rclone PULL drive Google nico
│ ├── photos/ ← Takeout nico
│ └── archives/ ← archives personnelles nico
├── family/ ← dataset ZFS, espace commun alice+nico
│ ├── drive/ ← rclone PULL dossier Drive partagé
│ └── archives/ ← archives communes
├── shared/ ← dataset ZFS, optionnel — contenu pour guests
└── others/ ← inboxes guests (optionnel)
└── {username}/
Sanoid (host Proxmox) → snapshots auto daily×7 / weekly×4 / monthly×3
Composants et rôles¶
| Composant | Rôle | Hébergement |
|---|---|---|
ZFS pool storage |
Stockage plain files, checksums, compression zstd | Host Proxmox, sdd |
| Sanoid | Snapshots automatiques ZFS | Host Proxmox |
| SFTPGo v2.7.1+ | Accès web/SFTP/WebDAV, users, share links, OIDC | LXC 106 dédié (Portainer stack) |
| ZfDash | Interface web admin ZFS | LXC 101 (Portainer stack) |
| rclone PULL | Sync Google Drive → stockage local | Host Proxmox (consolidé) |
| nginx + Authelia | Reverse proxy + SSO | LXC 103 (existant) |
2. Utilisateurs et Groupes SFTPGo¶
Comptes¶
| Compte | Type | Rôle |
|---|---|---|
alice |
Utilisateur OIDC (Authelia) | Propriétaire, accès données personnelles + family |
nico |
Utilisateur OIDC (Authelia) + admin SFTPGo | Propriétaire + administration du serveur |
| guests | Utilisateurs optionnels (Authelia) | Accès limité via groupe guests ou share links |
nico est le compte admin SFTPGo. Il dispose simultanément d'un compte utilisateur ordinaire (espace personnel, accès family) et du rôle administrateur (gestion des users, groupes, virtual folders via web UI).
Groupes¶
| Groupe | Type SFTPGo | Membres | Héritage |
|---|---|---|---|
owners |
primary | alice, nico | home_dir = /storage/%username% |
owners-shared |
secondary | alice, nico | virtual folder family/ → /storage/family/ [rw] |
owners-shared |
secondary | alice, nico | virtual folder .snapshots/ → /storage/.zfs/snapshot/ [ro] |
guests |
secondary | autres users | virtual folder shared/ → /storage/shared/ [ro] (optionnel) |
Vue utilisateur dans SFTPGo¶
alice :
/ ← home = /storage/alice/
drive/
photos/
archives/
family/ ← virtual folder → /storage/family/ [rw]
.snapshots/ ← virtual folder → /storage/.zfs/snapshot/ [ro]
nico (compte utilisateur) :
/ ← home = /storage/nico/
drive/
photos/
archives/
family/ ← virtual folder → /storage/family/ [rw]
.snapshots/ ← virtual folder → /storage/.zfs/snapshot/ [ro]
nico (vue admin via interface admin SFTPGo) : accès à tous les datasets, utilisateurs, groupes et virtual folders.
Autres utilisateurs (guests)¶
Les autres utilisateurs ont un compte Authelia mais pas de sync Drive/Photos côté serveur. Deux options :
| Besoin | Outil |
|---|---|
| Accès ponctuel | Share link SFTPGo — URL avec token + expiration + MdP. Aucun compte SFTPGo requis. |
| Accès régulier (ex: consulte régulièrement family/photos/) | Compte SFTPGo + groupe guests → shared/ [ro] ± family/photos/ [ro] |
3. Plan d'Implémentation¶
Phase 1 — Conversion ZFS (IronWolf sdd)¶
Prérequis : sauvegarder
/mnt/storage/— seulement 1.1 GB, trivial.
Risque : faible, peu de données existantes.
# 1. Backup du contenu actuel
rsync -avP /mnt/storage/ /tmp/storage-backup/
# 2. Démonter et recréer en ZFS
umount /mnt/storage
# (supprimer la ligne sdd dans /etc/fstab si présente)
zpool create -o ashift=12 storage /dev/sdd
zfs set compression=zstd storage
zfs set mountpoint=/mnt/storage storage
# 3. Créer les datasets
zfs create storage/alice
zfs create storage/nico
zfs create storage/family
zfs create storage/shared # optionnel — guests
zfs create storage/others # optionnel — inboxes guests
# 4. Restaurer le contenu antérieur
rsync -avP /tmp/storage-backup/archives/ /mnt/storage/family/archives/
# (ou storage/alice/archives/ selon l'appartenance)
Sanoid — configuration snapshots¶
Installer Sanoid sur le host Proxmox puis configurer /etc/sanoid/sanoid.conf :
[storage/alice]
use_template = personal
[storage/nico]
use_template = personal
[storage/family]
use_template = personal
[storage/shared]
use_template = personal
[template_personal]
frequently = 0
hourly = 0
daily = 7
weekly = 4
monthly = 3
autosnap = yes
autoprune = yes
Phase 2 — rclone PULL (Host Proxmox)¶
Prérequis : rclone v1.73.3 déjà installé sur host, remote
gdrive:existant (compte nico).
Risque : nul — read-only depuis Google Drive.
Remote existant et dual-role de gdrive-nico:¶
Le compte nico sert en même temps de cible backup (gdrive-nico:backup/homeserver/ — PUSH) et de source Drive personnel (PULL). Pas de conflit : les paths sont disjoints. Seule précaution : exclure backup/ du job PULL pour éviter de syncer les archives serveur localement.
Renommer le remote existant gdrive: → gdrive-nico: et mettre à jour les scripts backup :
# Renommer la section dans rclone.conf
sed -i 's/^\[gdrive\]$/[gdrive-nico]/' /root/.config/rclone/rclone.conf
# Mettre à jour les scripts backup existants
sed -i 's/gdrive:/gdrive-nico:/g' /usr/local/bin/backup-docker-configs.sh
sed -i 's/gdrive:/gdrive-nico:/g' /usr/local/bin/backup-proxmox-host.sh
# Créer le remote alice (OAuth interactif — navigateur requis)
rclone config # → créer "gdrive-alice" (OAuth compte alice)
LXC 102 conserve son rclone et son cron PBS→GDrive sans modification. Une consolidation ultérieure (copier le script + cron vers le host) est triviale mais hors périmètre.
Script /usr/local/bin/pull-gdrive.sh¶
#!/bin/bash
set -e
LOG_DIR=/var/log
# Drive personnel alice
rclone sync gdrive-alice:/ /mnt/storage/alice/drive/ \
--exclude="**/.Trash/**" \
--backup-dir=/mnt/storage/alice/.deleted/$(date +%Y%m) \
--log-file=${LOG_DIR}/rclone-pull-alice.log \
--log-level INFO
# Drive personnel nico
# --exclude "backup/**" : gdrive-nico: est aussi la cible des backups serveur
rclone sync gdrive-nico:/ /mnt/storage/nico/drive/ \
--exclude="**/.Trash/**" \
--exclude="backup/**" \
--backup-dir=/mnt/storage/nico/.deleted/$(date +%Y%m) \
--log-file=${LOG_DIR}/rclone-pull-nico.log \
--log-level INFO
# Dossier Drive partagé (depuis le compte propriétaire du dossier)
rclone sync gdrive-alice:Dossier-Partage/ /mnt/storage/family/drive/ \
--log-file=${LOG_DIR}/rclone-pull-family.log \
--log-level INFO
Cron sur host (/etc/cron.d/pull-gdrive) :
Le compte propriétaire du dossier Drive partagé (alice) suffit pour les trois jobs. L'OAuth nico n'est nécessaire que pour son Drive personnel.
Phase 3 — Déploiement SFTPGo (LXC 106 dédié)¶
⚠️ Version minimale : v2.7.1 — fix CVE-2026-30914 (path traversal virtual folders, 2026-03-13).
LXC 106 dédié plutôt que LXC 104 : isolation du blast radius, et surtout nécessité d'un custom idmap UID 1000 pour que SFTPGo Docker lise les fichiers créés par rclone sur le host (UID 1000). Modifier l'idmap de LXC 104 briserait les services existants (Endurain, FreshRSS, Romm).
Étape 0 — Créer LXC 106 avec custom idmap¶
# Sur host — autoriser le mapping UID/GID 1000 pour root
echo "root:1000:1" >> /etc/subuid
echo "root:1000:1" >> /etc/subgid
# Créer user "storage" UID 1000 sur le host (propriétaire de /mnt/storage)
useradd -u 1000 -M -s /usr/sbin/nologin storage
chown -R 1000:1000 /mnt/storage/
# Créer le LXC 106
pct create 106 local:vztmpl/debian-12-standard_12.x-1_amd64.tar.zst \
--hostname storage \
--memory 512 \
--cores 2 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.1.106/24,gw=192.168.1.254 \
--rootfs local-zfs:8 \
--unprivileged 1 \
--features nesting=1 \
--onboot 1
Dans /etc/pve/lxc/106.conf :
mp0: /mnt/lxc-data/106-storage,mp=/opt/docker
mp1: /mnt/storage,mp=/mnt/storage
lxc.idmap: u 0 100000 1000
lxc.idmap: u 1000 1000 1
lxc.idmap: u 1001 101001 64535
lxc.idmap: g 0 100000 1000
lxc.idmap: g 1000 1000 1
lxc.idmap: g 1001 101001 64535
Optionnel : ajouter
mp2: /mnt/media,mp=/mnt/mediasi l'admin nico doit accéder à/mnt/media/via SFTPGo.
Étape 1 — Portainer agent¶
Démarrer LXC 106, installer Docker, puis créer mkdir -p /mnt/lxc-data/106-storage sur le host. Déployer le portainer-agent standard et ajouter LXC 106 comme nouvel environnement dans Portainer (192.168.1.106:9001).
Étape 2 — Stack SFTPGo (Portainer)¶
Créer une stack "sftpgo" dans Portainer (environnement LXC 106) :
services:
sftpgo:
image: drakkan/sftpgo:v2.7.1
container_name: sftpgo
restart: unless-stopped
user: "1000:1000"
ports:
- "2022:2022" # SFTP
- "8090:8090" # WebDAV + web client
- "8080:8080" # Interface admin (restreindre à VPN/LAN)
volumes:
- /mnt/storage:/storage:rw
- /mnt/media:/media:ro
- ./sftpgo/data:/var/lib/sftpgo
- ./sftpgo/config:/etc/sftpgo
environment:
- SFTPGO_HTTPD__BINDINGS__0__PORT=8090
- SFTPGO_HTTPD__BINDINGS__1__PORT=8080
- SFTPGO_HTTPD__BINDINGS__1__ENABLE_WEB_ADMIN=true
Configuration OIDC (Authelia)¶
Dans sftpgo.json (ou variables d'env) :
{
"oidc": {
"client_id": "sftpgo",
"client_secret": "...",
"config_url": "https://auth.example.com/.well-known/openid-configuration",
"redirect_base_url": "https://files.example.com",
"username_field": "preferred_username"
}
}
Configuration des groupes et utilisateurs (web UI admin)¶
Ordre de création important :
- Créer virtual folders (onglet "Folders" dans l'admin) :
family→/storage/family[rw]snapshots→/storage/.zfs/snapshot[ro]shared→/storage/shared[ro] (optionnel)-
media→/media[ro] -
Créer groupe primaire
owners: -
home_dir=/storage/%username% -
Créer groupe secondaire
owners-shared: - Virtual folder
family/→ folderfamily[rw] -
Virtual folder
.snapshots/→ foldersnapshots[ro] -
Créer groupe secondaire
guests(optionnel) : -
Virtual folder
shared/→ foldershared[ro] -
Créer utilisatrice
alice: - Groupe primaire :
owners - Groupe secondaire :
owners-shared -
Auth : OIDC (Authelia)
-
Créer utilisateur
nico: - Groupe primaire :
owners - Groupe secondaire :
owners-shared - Auth : OIDC (Authelia)
-
Cocher "admin" → nico devient administrateur SFTPGo
-
Pour guests (optionnel) : créer le compte, groupe secondaire
guests.
Configuration nginx (LXC 103)¶
# Web client SFTPGo (HTTPS public, accès via Authelia)
server {
server_name files.example.com;
location / {
proxy_pass http://192.168.1.106:8090;
# middleware Authelia
}
}
# Interface admin (VPN/LAN only)
server {
server_name sftpgo-admin.internal;
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://192.168.1.106:8080;
}
}
Phase 4 — ZfDash (admin ZFS)¶
ZfDash est un outil de visualisation ZFS — sa place est dans LXC 101 (management) avec Homepage, Portainer, Uptime-Kuma et CoolerControl, et non dans LXC 106 dont le rôle est l'accès aux fichiers. Les deux LXC nécessitent le même passthrough /dev/zfs; LXC 101 offre en plus le backup PBS et le cycle de maintenance déjà établis pour les outils admin.
Pré-requis — passer /dev/zfs dans LXC 101¶
Ajouter dans /etc/pve/lxc/101.conf puis redémarrer LXC 101 :
Stack Portainer (LXC 101)¶
Créer une stack "zfdash" dans Portainer (environnement LXC 101) :
services:
zfdash:
image: ghcr.io/j0sh-park/zfdash:latest
container_name: zfdash
restart: unless-stopped
ports:
- "8091:8080"
privileged: true
volumes:
- /dev:/dev
Exposer uniquement sur VPN/réseau local — pas de nginx public pour cet outil d'administration.
Phase 5 — Backup Photos (Takeout)¶
- Déclencher export Google Takeout (alice + nico séparément) → format
.tgz - Télécharger les archives sur
/mnt/storage/alice/et/mnt/storage/nico/ - Extraire avec
immich-gopour dédupliquer et organiser dansphotos/: - Planifier rappel Takeout tous les 3 mois (Google limite à ~100 exports/an)
Phase 6 — Migration Archives (HGST externe)¶
- Brancher le disque externe HGST Touro (~1TB) sur le host Proxmox
- Monter et migrer :
- Vérifier, puis prendre un snapshot ZFS manuel :
- Conserver le HGST externe comme backup offline hors-site
Phase 7 — Mise à Jour Documentation¶
Suivre les guidelines CONTRIBUTING — documenter l'état final en production, pas le process d'implémentation.
| Page | Action |
|---|---|
infrastructure/proxmox/containers/lxc-106-storage.md |
Créer — LXC 106 : SFTPGo, mounts, idmap |
infrastructure/proxmox/containers/index.md |
Ajouter LXC 106 dans le tableau |
infrastructure/hardware/storage/hdd-storage.md |
Mettre à jour — ZFS pool storage, datasets |
applications/sftpgo.md |
Créer — SFTPGo : config, groupes, OIDC, share links |
applications/zfdash.md |
Créer — ZfDash : accès, port, restrictions |
applications/index.md |
Ajouter SFTPGo et ZfDash |
infrastructure/proxmox/index.md |
Ajouter cron pull-gdrive, remotes rclone |
maintenance/backup/offsite.md |
Mettre à jour — remotes gdrive-alice: / gdrive-nico:, dual-role |
infrastructure/proxmox/containers/lxc-101-management.md |
Ajouter ZfDash, noter lxc.mount.entry /dev/zfs |
roadmap/index.md |
Marquer "Stockage personnel" comme ✅ Réalisé |
4. Accès Distant¶
| Données | Politique | Justification |
|---|---|---|
| Interface web SFTPGo (alice, nico) | HTTPS via nginx + cookie Authelia | Accès normal — déjà protégé par OIDC |
| Liens de partage SFTPGo | HTTPS public via nginx | Intentionnellement publics, protégés par token + expiration |
| Interface admin SFTPGo (port 8080) | VPN uniquement (WireGuard LXC 103) | Admin tool — pas d'exposition publique |
| Interface admin ZfDash | VPN uniquement | Admin ZFS — pas d'exposition publique |
SFTPGo gère nativement les deux cas : l'interface web complète derrière Authelia, et les share links accessibles sans authentification (token + expiration + MdP optionnel).
5. Résumé Décisionnel¶
| Question | Réponse |
|---|---|
| App de stockage | SFTPGo v2.7.1+ |
| Système de fichiers | ZFS sur IronWolf sdd — conversion depuis ext4 |
| Snapshots auto | Sanoid sur host Proxmox |
| Admin ZFS web | ZfDash — LXC 101 (Portainer stack, VPN only) |
| Sync Drive → local | rclone PULL sur host Proxmox — remotes gdrive-alice: + gdrive-nico: |
| gdrive dual-role (nico) | gdrive-nico: = cible backups serveur + source Drive personnel — pas de conflit (paths disjoints, --exclude "backup/**" dans le PULL) |
| Sync dossier Drive partagé | 1 seul job rclone depuis le compte propriétaire → /storage/family/drive/ |
| Sync Photos → local | Google Takeout trimestriel + immich-go |
| Structure disque | storage/{alice,nico,family}/ — structure plate, datasets ZFS indépendants |
| Accès individuel | ✅ home dir chroot = /storage/%username% via groupe owners |
| Espace commun alice+nico | ✅ virtual folder family/ via groupe owners-shared |
| Accès guests ponctuels | ✅ Share links — sans compte SFTPGo requis |
| Accès guests réguliers | ✅ compte SFTPGo + groupe guests → shared/ [ro] |
| Espace partagé ad-hoc | ✅ web UI SFTPGo (virtual folder + assign utilisateurs) |
Accès admin /mnt/media/ |
✅ virtual folder dans le compte nico (admin) |
| Nouveau LXC nécessaire | Oui — LXC 106 dédié — isolation SFTPGo + custom idmap UID 1000 (cohérence avec rclone host) |
| SSO Authelia | ✅ documenté officiellement SFTPGo v2.6.6+ + Authelia v4.39.16+ |
| Lock-in | Aucun — plain files ZFS, formats ouverts |
| CVE à adresser | CVE-2026-30914 — utiliser SFTPGo ≥ v2.7.1 obligatoirement |