Aller au contenu
View in the app

A better way to browse. Learn more.

Next

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

[v0.1b] Script de montage auto+chiffrement du cloud s3 Next.ink pour Linux.

Featured Replies

Posté(e)

Hello !

Pour celles et ceux qui utilisent des machines sous Debian ou Ubuntu, je partage un script Bash interactif prêt à l’emploi pour configurer le stockage S3 proposé par Next.Ink aux abonnés.
C'est une première version en français, je pense rajouter le support des distributions archlinux-like et rendre certaines choses un peu plus paramétrables ensuite.
N'oubliez pas de sauvegarder la clé maître qui vous sera donné, dans un endroit sûr.

Objectifs :

  • Monter automatiquement un bucket S3 en local.

  • Chiffrer l’intégralité des données avec gocryptfs.

  • Protéger le mot de passe de chiffrement via GPG (clé locale, mot de passe jamais stocké en clair).

  • Obtenir un point de montage transparent dans /media, utilisable comme un stockage classique.

  • Aucune configuration manuelle complexe.

Fonctionnalités principales :

  • Script interactif guidé (clés S3, mot de passe, choix du mode).

  • Gestion claire de deux cas :

    • Première machine : initialisation du chiffrement du bucket (création de gocryptfs.conf).

    • Machines suivantes : montage direct sans réinitialisation.

  • Configuration persistante via /etc/fstab (montage automatique au démarrage).

  • Cache local S3FS configurable (pour éviter les blocages en cas d’espace disque limité).

  • Déchiffrement automatique du mot de passe via un wrapper GPG sécurisé (extpass).

  • Fonction --clean pour un nettoyage complet et interactif (montages, cache, clés, configuration).

Le but est d’avoir un stockage S3 chiffré, fiable et totalement transparent, sans avoir à se battre avec la configuration à chaque machine.

Le script est conçu pour être reproductible, cohérent entre serveurs, et utilisable aussi bien sur une machine principale que sur des serveurs secondaires.

 

#!/usr/bin/env bash
set -euo pipefail

##################################################
# PARAMÈTRES
##################################################
BUCKET="s3-next-ink"
ENDPOINT="https://s3.fr1.next.ink"

MOUNT_CLEAR="/media/s3next"
MOUNT_CRYPT="/media/s3next_crypt"

CACHE_DIR="/var/cache/s3fs"
CLEAN_CACHE="/run/s3fs-clean"

S3FS_PASS="/root/.passwd-s3fs"

GPG_EMAIL="s3next@local"
GPG_PASS_GPG="/root/s3next.gocryptfs.pass.gpg"
GPG_WRAPPER="/usr/local/bin/gocryptfs-pass.sh"

##################################################
# ROOT
##################################################
if [ "$(id -u)" -ne 0 ]; then
  echo "Ce script doit être lancé avec sudo."
  exit 1
fi

##################################################
# OUTILS
##################################################
section() {
  echo
  echo "=================================================="
  echo "$1"
  echo "=================================================="
}

ask_yn() {
  local q="$1"
  local def="$2"
  local p
  [ "$def" = "O" ] && p="O/n" || p="o/N"
  local r
  read -rp "$q ($p) : " r
  r="${r:-$def}"
  [[ "$r" =~ ^[Oo]$ ]]
}

safe_umount() {
  local mp="$1"
  if mountpoint -q "$mp"; then
    umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
  fi
}

fstab_add() {
  grep -Fq "$1" /etc/fstab || echo "$1" >> /etc/fstab
}

fstab_remove() {
  sed -i "\|$1|d" /etc/fstab 2>/dev/null || true
}

##################################################
# MODE --clean
##################################################
if [ "${1:-}" = "--clean" ]; then
  section "NETTOYAGE COMPLET (IRRÉVERSIBLE POUR LES DONNÉES)"

  ask_yn "Confirmer le nettoyage des DONNÉES S3 ?" "N" || exit 0
  ask_yn "DERNIÈRE CONFIRMATION (IRRÉVERSIBLE) ?" "N" || exit 0

  echo "Démontage des points de montage…"
  safe_umount "$MOUNT_CLEAR"
  safe_umount "$MOUNT_CRYPT"

  ################################################
  # NETTOYAGE DISTANT (OPTIONNEL)
  ################################################
  mkdir -p "$CLEAN_CACHE" "$MOUNT_CRYPT"
  chmod 700 "$CLEAN_CACHE"

  echo "Tentative de montage S3 pour nettoyage…"
  if s3fs "$BUCKET" "$MOUNT_CRYPT" \
    -o passwd_file="$S3FS_PASS" \
    -o url="$ENDPOINT" \
    -o use_path_request_style \
    -o use_cache="$CLEAN_CACHE" \
    -o ensure_diskfree=1 \
    -o nomultipart \
    -o multipart_size=5 \
    -o max_dirty_data=50 \
    -o nonempty; then

    rm -rf "$MOUNT_CRYPT"/* || true
    safe_umount "$MOUNT_CRYPT"
  fi

  ################################################
  # NETTOYAGE LOCAL
  ################################################
  rm -rf "$CLEAN_CACHE" "$CACHE_DIR" "$MOUNT_CLEAR" "$MOUNT_CRYPT"
  rm -f "$GPG_PASS_GPG" "$GPG_WRAPPER"

  ################################################
  # SUPPRESSION CLÉ GPG (PROPRE)
  ################################################
  if gpg --list-keys "$GPG_EMAIL" >/dev/null 2>&1; then
    FPR="$(gpg --list-keys --with-colons "$GPG_EMAIL" | awk -F: '/^fpr:/ {print $10; exit}')"
    if [ -n "$FPR" ]; then
      gpg --batch --yes --delete-secret-keys "$FPR" || true
      gpg --batch --yes --delete-keys "$FPR" || true
    fi
  fi

  ################################################
  # FSTAB
  ################################################
  fstab_remove "s3fs#$BUCKET"
  fstab_remove "$MOUNT_CRYPT $MOUNT_CLEAR"
  systemctl daemon-reload

  ################################################
  # CLÉS S3 (OPTIONNEL)
  ################################################
  if [ -f "$S3FS_PASS" ]; then
    if ask_yn "Supprimer les clés S3 locales ?" "N"; then
      rm -f "$S3FS_PASS"
    fi
  fi

  section "NETTOYAGE TERMINÉ"
  exit 0
fi

##################################################
# MODE NORMAL
##################################################
section "INSTALLATION S3 CHIFFRÉ"

ask_yn "Continuer ?" "O" || exit 0

for p in s3fs gocryptfs gnupg; do
  dpkg -s "$p" >/dev/null 2>&1 || apt install -y "$p"
done

mkdir -p "$MOUNT_CLEAR" "$MOUNT_CRYPT" "$CACHE_DIR"
chmod 700 "$CACHE_DIR"

##################################################
# MODE (INIT OU NON)
##################################################
section "MODE"
FIRST_INIT=0
if ask_yn "Première initialisation du bucket (création gocryptfs.conf) ?" "N"; then
  FIRST_INIT=1
fi

##################################################
# CLÉS S3
##################################################
section "CLÉS S3"

if [ -f "$S3FS_PASS" ]; then
  chmod 600 "$S3FS_PASS"
  ask_yn "Clés S3 déjà présentes. Les remplacer ?" "N" && rm -f "$S3FS_PASS"
fi

if [ ! -f "$S3FS_PASS" ]; then
  read -rp "Access Key S3 : " AK
  read -rsp "Secret Key S3 : " SK
  echo
  echo "$AK:$SK" > "$S3FS_PASS"
  chmod 600 "$S3FS_PASS"
fi

##################################################
# MONTAGE S3
##################################################
section "MONTAGE S3"

fstab_add "s3fs#$BUCKET $MOUNT_CRYPT fuse _netdev,nofail,x-systemd.automount,allow_other,nonempty,passwd_file=$S3FS_PASS,use_path_request_style,use_cache=$CACHE_DIR,ensure_diskfree=64,nomultipart,url=$ENDPOINT 0 0"
systemctl daemon-reload
mount "$MOUNT_CRYPT" || true
mountpoint -q "$MOUNT_CRYPT" || exit 1

##################################################
# GPG
##################################################
section "GPG"

if ! gpg --list-keys "$GPG_EMAIL" >/dev/null 2>&1; then
  gpg --batch --generate-key <<EOF
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: s3next
Name-Email: $GPG_EMAIL
Expire-Date: 0
%no-protection
%commit
EOF
fi

##################################################
# MOT DE PASSE GOCRYPTFS
##################################################
section "MOT DE PASSE DE CHIFFREMENT"

if [ ! -f "$GPG_PASS_GPG" ]; then
  read -rsp "Mot de passe : " P1; echo
  read -rsp "Confirmation : " P2; echo
  [ "$P1" = "$P2" ] || exit 1
  ask_yn "As-tu sauvegardé ce mot de passe ?" "N" || exit 0
  printf "%s" "$P1" | gpg -r "$GPG_EMAIL" -e -o "$GPG_PASS_GPG"
fi

##################################################
# WRAPPER (UTILISÉ PAR -extpass)
##################################################
cat > "$GPG_WRAPPER" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec gpg --batch --quiet --decrypt "$GPG_PASS_GPG"
EOF
chmod 700 "$GPG_WRAPPER"

##################################################
# BACKEND GOCRYPTFS
##################################################
section "BACKEND GOCRYPTFS"

TMP_TEST="/tmp/gocryptfs-test-$$"
mkdir -p "$TMP_TEST"

if [ -f "$MOUNT_CRYPT/gocryptfs.conf" ]; then
  gocryptfs -q -extpass "$GPG_WRAPPER" "$MOUNT_CRYPT" "$TMP_TEST" || exit 1
  safe_umount "$TMP_TEST"
else
  if [ "$FIRST_INIT" -eq 1 ]; then
    gocryptfs -init "$MOUNT_CRYPT" < <("$GPG_WRAPPER")
  else
    echo "Erreur: gocryptfs.conf absent sur le bucket. Ce serveur n'est pas en mode initialisation."
    exit 1
  fi
fi
rmdir "$TMP_TEST" 2>/dev/null || true

##################################################
# MONTAGE FINAL (FSTAB)
##################################################
section "MONTAGE FINAL"

GOCRYPTFS_BIN="$(command -v gocryptfs)"
fstab_add "$MOUNT_CRYPT $MOUNT_CLEAR fuse.$GOCRYPTFS_BIN _netdev,nofail,x-systemd.automount,allow_other,extpass=$GPG_WRAPPER 0 0"
systemctl daemon-reload

section "TERMINÉ"
echo "Commande finale : sudo mount -a"
echo "Point d'accès : $MOUNT_CLEAR"

Posté(e)
  • Auteur

Je viens de voir qu'on ne peut pas éditer un topic.... 😒

Rapide mise à jour pour correction d'un problème de montage :

#!/usr/bin/env bash
set -euo pipefail
umask 077

##################################################
# PARAMETRES
##################################################
BUCKET="s3-next-ink"
ENDPOINT="https://s3.fr1.next.ink"

MOUNT_CLEAR="/media/s3next"
MOUNT_CRYPT="/media/s3next_crypt"

# Cache s3fs en tmpfs (RAM) pour eviter "no space left" sur /var
CACHE_DIR="/var/cache/s3fs"
CACHE_TMPFS_SIZE="256m"     # doit etre > 114m dans votre cas

# Cache dedie au mode --clean
CLEAN_CACHE="/var/cache/s3fs-clean"
CLEAN_CACHE_TMPFS_SIZE="256m"

S3FS_PASS="/root/.passwd-s3fs"

GPG_EMAIL="s3next@local"
GPG_PASS_GPG="/root/s3next.gocryptfs.pass.gpg"
GPG_WRAPPER="/usr/local/bin/gocryptfs-pass.sh"

##################################################
# ROOT
##################################################
if [ "$(id -u)" -ne 0 ]; then
  echo "Ce script doit etre lance avec sudo."
  exit 1
fi

##################################################
# OUTILS
##################################################
section() {
  echo
  echo "=================================================="
  echo "$1"
  echo "=================================================="
}

ask_yn() {
  local q="$1"
  local def="$2"
  local p
  [ "$def" = "O" ] && p="O/n" || p="o/N"
  local r
  read -rp "$q ($p) : " r
  r="${r:-$def}"
  [[ "$r" =~ ^[Oo]$ ]]
}

safe_umount() {
  local mp="$1"
  if mountpoint -q "$mp"; then
    umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
  fi
}

fstab_add() {
  grep -Fq "$1" /etc/fstab || echo "$1" >> /etc/fstab
}

fstab_remove_contains() {
  local needle="$1"
  sed -i "\|$needle|d" /etc/fstab 2>/dev/null || true
}

ensure_fuse_allow_other() {
  if [ -f /etc/fuse.conf ]; then
    grep -Eq '^\s*user_allow_other\s*$' /etc/fuse.conf || echo "user_allow_other" >> /etc/fuse.conf
  fi
}

ensure_dir_mountpoint() {
  local p="$1"
  local mode="${2:-755}"

  # Si un fichier 0 octet existe a la place (effet d'un essai precedent), on le supprime.
  if [ -e "$p" ] && [ ! -d "$p" ]; then
    if [ -f "$p" ] && [ ! -s "$p" ]; then
      rm -f "$p"
    else
      echo "Erreur: $p existe et n'est pas un dossier."
      echo "Corriger: sudo rm -f '$p' ; sudo mkdir -p '$p'"
      exit 1
    fi
  fi

  install -d -m "$mode" "$p"
}

ensure_tmpfs_cache() {
  local dir="$1"
  local size="$2"

  ensure_dir_mountpoint "$dir" 700

  if ! mountpoint -q "$dir"; then
    mount -t tmpfs -o "rw,nosuid,nodev,noexec,mode=700,size=$size" tmpfs "$dir"
  fi
}

wipe_dir_contents() {
  local dir="$1"
  [ -d "$dir" ] || return 0
  find "$dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + 2>/dev/null || true
}

##################################################
# MODE --clean
##################################################
if [ "${1:-}" = "--clean" ]; then
  section "NETTOYAGE COMPLET (IRREVERSIBLE POUR LES DONNEES)"

  ask_yn "Confirmer le nettoyage des DONNEES S3 ?" "N" || exit 0
  ask_yn "DERNIERE CONFIRMATION (IRREVERSIBLE) ?" "N" || exit 0

  echo "Demontage..."
  safe_umount "$MOUNT_CLEAR"
  safe_umount "$MOUNT_CRYPT"

  # Cache tmpfs pour le nettoyage
  ensure_tmpfs_cache "$CLEAN_CACHE" "$CLEAN_CACHE_TMPFS_SIZE"
  ensure_dir_mountpoint "$MOUNT_CRYPT" 755

  echo "Montage S3 pour nettoyage..."
  if s3fs "$BUCKET" "$MOUNT_CRYPT" \
    -o "passwd_file=$S3FS_PASS" \
    -o "url=$ENDPOINT" \
    -o "use_path_request_style" \
    -o "allow_other" \
    -o "nonempty" \
    -o "use_cache=$CLEAN_CACHE" \
    -o "ensure_diskfree=64" \
    -o "nomultipart"; then

    wipe_dir_contents "$MOUNT_CRYPT"
    safe_umount "$MOUNT_CRYPT"
  fi

  safe_umount "$CLEAN_CACHE"
  safe_umount "$CACHE_DIR"

  rm -rf "$CLEAN_CACHE" "$CACHE_DIR" "$MOUNT_CLEAR" "$MOUNT_CRYPT" 2>/dev/null || true
  rm -f "$GPG_PASS_GPG" "$GPG_WRAPPER" 2>/dev/null || true

  # Suppression cle GPG
  if gpg --list-keys "$GPG_EMAIL" >/dev/null 2>&1; then
    FPR="$(gpg --list-keys --with-colons "$GPG_EMAIL" | awk -F: '/^fpr:/ {print $10; exit}')"
    if [ -n "$FPR" ]; then
      gpg --batch --yes --delete-secret-keys "$FPR" || true
      gpg --batch --yes --delete-keys "$FPR" || true
    fi
  fi

  # FSTAB
  fstab_remove_contains "s3fs#$BUCKET"
  fstab_remove_contains "gocryptfs#$MOUNT_CRYPT"
  fstab_remove_contains "tmpfs $CACHE_DIR"
  fstab_remove_contains "tmpfs $CLEAN_CACHE"
  systemctl daemon-reload

  # Cle S3 locale
  if [ -f "$S3FS_PASS" ]; then
    if ask_yn "Supprimer les cles S3 locales ?" "N"; then
      rm -f "$S3FS_PASS"
    fi
  fi

  section "NETTOYAGE TERMINE"
  exit 0
fi

##################################################
# MODE NORMAL
##################################################
section "INSTALLATION S3 CHIFFRE"
ask_yn "Continuer ?" "O" || exit 0

section "MODE"
FIRST_INIT=0
if ask_yn "Premiere initialisation du bucket (creation gocryptfs.conf) ?" "N"; then
  FIRST_INIT=1
fi

for p in s3fs gocryptfs gnupg fuse3; do
  dpkg -s "$p" >/dev/null 2>&1 || apt install -y "$p"
done

ensure_fuse_allow_other

ensure_dir_mountpoint "$MOUNT_CLEAR" 755
ensure_dir_mountpoint "$MOUNT_CRYPT" 755

##################################################
# CACHE S3FS (TMPFS)
##################################################
section "CACHE S3FS (TMPFS)"
ensure_tmpfs_cache "$CACHE_DIR" "$CACHE_TMPFS_SIZE"
fstab_remove_contains "tmpfs $CACHE_DIR"
fstab_add "tmpfs $CACHE_DIR tmpfs rw,nosuid,nodev,noexec,mode=700,size=$CACHE_TMPFS_SIZE 0 0"

##################################################
# CLES S3
##################################################
section "CLES S3"
if [ -f "$S3FS_PASS" ]; then
  chmod 600 "$S3FS_PASS"
  if ask_yn "Cles S3 deja presentes. Les remplacer ?" "N"; then
    rm -f "$S3FS_PASS"
  fi
fi

if [ ! -f "$S3FS_PASS" ]; then
  read -rp "Access Key S3 : " AK
  read -rsp "Secret Key S3 : " SK
  echo
  echo "$AK:$SK" > "$S3FS_PASS"
  chmod 600 "$S3FS_PASS"
fi

##################################################
# MONTAGE S3 (S3FS)
##################################################
section "MONTAGE S3"

fstab_remove_contains "s3fs#$BUCKET"
fstab_add "s3fs#$BUCKET $MOUNT_CRYPT fuse _netdev,nofail,x-systemd.automount,allow_other,nonempty,passwd_file=$S3FS_PASS,use_path_request_style,use_cache=$CACHE_DIR,ensure_diskfree=64,nomultipart,url=$ENDPOINT,x-systemd.requires-mounts-for=$CACHE_DIR 0 0"

systemctl daemon-reload

mount "$CACHE_DIR" 2>/dev/null || true
mount "$MOUNT_CRYPT" 2>/dev/null || true
mountpoint -q "$MOUNT_CRYPT" || { echo "Erreur: montage s3fs echoue sur $MOUNT_CRYPT"; exit 1; }

##################################################
# GPG
##################################################
section "GPG"
if ! gpg --list-keys "$GPG_EMAIL" >/dev/null 2>&1; then
  gpg --batch --generate-key <<EOF
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: s3next
Name-Email: $GPG_EMAIL
Expire-Date: 0
%no-protection
%commit
EOF
fi

##################################################
# MOT DE PASSE GOCRYPTFS (CHIFFRE VIA GPG)
##################################################
section "MOT DE PASSE DE CHIFFREMENT"
if [ -f "$GPG_PASS_GPG" ]; then
  if ask_yn "Mot de passe deja configure. Le remplacer ?" "N"; then
    rm -f "$GPG_PASS_GPG"
  fi
fi

if [ ! -f "$GPG_PASS_GPG" ]; then
  read -rsp "Mot de passe : " P1; echo
  read -rsp "Confirmation : " P2; echo
  [ "$P1" = "$P2" ] || { echo "Erreur: mots de passe differents"; exit 1; }
  ask_yn "As-tu sauvegarde ce mot de passe ?" "N" || exit 0
  printf "%s" "$P1" | gpg -r "$GPG_EMAIL" -e -o "$GPG_PASS_GPG"
fi

##################################################
# WRAPPER (EXT PASS)
##################################################
cat > "$GPG_WRAPPER" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec gpg --batch --quiet --decrypt "$GPG_PASS_GPG"
EOF
chmod 700 "$GPG_WRAPPER"

##################################################
# GOCRYPTFS CONF (INIT OU VERIF)
##################################################
section "BACKEND GOCRYPTFS"

if [ -f "$MOUNT_CRYPT/gocryptfs.conf" ]; then
  if [ "$FIRST_INIT" -eq 1 ]; then
    echo "Note: gocryptfs.conf deja present. Initialisation ignoree."
  fi
else
  if [ "$FIRST_INIT" -eq 1 ]; then
    gocryptfs -init "$MOUNT_CRYPT" < <("$GPG_WRAPPER")
  else
    echo "Erreur: gocryptfs.conf absent sur le bucket. Ce serveur n'est pas en mode initialisation."
    exit 1
  fi
fi

# Test de dechiffrement (evite les surprises au montage final)
TMP_TEST="/tmp/gocryptfs-test-$$"
ensure_dir_mountpoint "$TMP_TEST" 700
gocryptfs -q -extpass "$GPG_WRAPPER" "$MOUNT_CRYPT" "$TMP_TEST" -o q >/dev/null 2>&1 || { echo "Erreur: mot de passe incorrect (test gocryptfs)"; exit 1; }
safe_umount "$TMP_TEST"
rmdir "$TMP_TEST" 2>/dev/null || true

##################################################
# MONTAGE FINAL (GOCRYPTFS)
##################################################
section "MONTAGE FINAL"

safe_umount "$MOUNT_CLEAR"
ensure_dir_mountpoint "$MOUNT_CLEAR" 755

# Montage immediat (avec -allow_other pour acces utilisateur)
gocryptfs -q -allow_other -extpass "$GPG_WRAPPER" "$MOUNT_CRYPT" "$MOUNT_CLEAR" -o q

mountpoint -q "$MOUNT_CLEAR" || { echo "Erreur: montage gocryptfs echoue sur $MOUNT_CLEAR"; exit 1; }

# Donner acces a l'utilisateur qui a lance sudo
DATA_USER="${SUDO_USER:-root}"
if [ "$DATA_USER" != "root" ]; then
  chown "$DATA_USER:$DATA_USER" "$MOUNT_CLEAR" 2>/dev/null || true
  chmod 775 "$MOUNT_CLEAR" 2>/dev/null || true
fi

# FSTAB gocryptfs (options gocryptfs via -o, compatibles mount/fstab)
fstab_remove_contains "gocryptfs#$MOUNT_CRYPT"
fstab_add "gocryptfs#$MOUNT_CRYPT $MOUNT_CLEAR fuse _netdev,nofail,x-systemd.automount,allow_other,extpass=$GPG_WRAPPER,q,x-systemd.requires-mounts-for=$MOUNT_CRYPT 0 0"

systemctl daemon-reload

section "TERMINE"
echo "Ecrire ici: $MOUNT_CLEAR"
echo "Backend chiffre (ne pas ecrire): $MOUNT_CRYPT"
echo "Commande utile: sudo mount -a"
Posté(e)
  • Auteur

 Pour finalement m'apercevoir que partager un S3 entre plusieurs machines, c'est une galère monstrueuse.

Troisième essai, cette fois ça part plutôt sur un montage sur serveur, et d'autres machines récupéreront cet accès via sshfs. :

#!/usr/bin/env bash
set -euo pipefail
umask 077

##################################################
# PARAMETRES
##################################################
BUCKET="s3-next-ink"
ENDPOINT="https://s3.fr1.next.ink"

MOUNT_CLEAR="/media/s3next"
MOUNT_CRYPT="/media/s3next_crypt"

CACHE_DIR="/var/cache/s3fs"
CACHE_TMPFS_SIZE="512m"

CLEAN_CACHE="/var/cache/s3fs-clean"
CLEAN_CACHE_TMPFS_SIZE="512m"

S3FS_PASS="/root/.passwd-s3fs"

GPG_EMAIL="s3next@local"
GPG_PASS_GPG="/root/s3next.gocryptfs.pass.gpg"
GPG_WRAPPER="/usr/local/bin/gocryptfs-pass.sh"

SYSTEMD_SERVICE="/etc/systemd/system/s3next-gocryptfs.service"

# Empreinte locale pour detecter tout changement de config distante
CONF_FINGERPRINT_FILE="/root/s3next.gocryptfs.fingerprint"

##################################################
# ROOT
##################################################
if [ "$(id -u)" -ne 0 ]; then
  echo "Erreur: ce script doit etre lance avec sudo."
  exit 1
fi

##################################################
# OUTILS
##################################################
section() {
  echo
  echo "=================================================="
  echo "$1"
  echo "=================================================="
}

ask_yn() {
  local q="$1"
  local def="$2"
  local p
  [ "$def" = "O" ] && p="O/n" || p="o/N"
  local r
  read -rp "$q ($p) : " r
  r="${r:-$def}"
  [[ "$r" =~ ^[Oo]$ ]]
}

safe_umount() {
  local mp="$1"
  if mountpoint -q "$mp"; then
    umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
  fi
}

fstab_remove_contains() {
  local needle="$1"
  sed -i "\|$needle|d" /etc/fstab 2>/dev/null || true
}

fstab_add() {
  grep -Fq "$1" /etc/fstab || echo "$1" >> /etc/fstab
}

ensure_fuse_allow_other() {
  if [ -f /etc/fuse.conf ]; then
    grep -Eq '^\s*user_allow_other\s*$' /etc/fuse.conf || echo "user_allow_other" >> /etc/fuse.conf
  fi
}

ensure_dir_mountpoint() {
  local p="$1"
  local mode="${2:-755}"

  if [ -e "$p" ] && [ ! -d "$p" ]; then
    if [ -f "$p" ] && [ ! -s "$p" ]; then
      rm -f "$p"
    else
      echo "Erreur: $p existe et n'est pas un dossier."
      exit 1
    fi
  fi

  install -d -m "$mode" "$p"
}

ensure_tmpfs_cache() {
  local dir="$1"
  local size="$2"

  ensure_dir_mountpoint "$dir" 700
  if ! mountpoint -q "$dir"; then
    mount -t tmpfs -o "rw,nosuid,nodev,noexec,mode=700,size=$size" tmpfs "$dir"
  fi

  local avail
  avail="$(df -Pm "$dir" | awk 'NR==2 {print $4}')"
  if [ -z "$avail" ] || [ "$avail" -lt 256 ]; then
    echo "Erreur: cache tmpfs insuffisant sur $dir (disponible: ${avail:-0} MiB)."
    exit 1
  fi
}

is_s3fs_mount() {
  local mp="$1"
  mountpoint -q "$mp" || return 1
  local fstype src
  fstype="$(findmnt -n -o FSTYPE --target "$mp" 2>/dev/null || true)"
  src="$(findmnt -n -o SOURCE --target "$mp" 2>/dev/null || true)"
  [[ "$fstype" == "fuse.s3fs" ]] && return 0
  [[ "$src" == "s3fs#${BUCKET}"* ]] && return 0
  grep -F " $mp " /proc/mounts | grep -qi "s3fs" && return 0
  return 1
}

install_packages() {
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -y >/dev/null 2>&1 || true
  apt-get install -y gnupg gocryptfs fuse3 >/dev/null
  apt-get install -y s3fs >/dev/null 2>&1 || apt-get install -y s3fs-fuse >/dev/null
}

get_clear_owner() {
  if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
    echo "$SUDO_USER"
  else
    echo "root"
  fi
}

fingerprint_remote_conf() {
  # Requiert un montage s3fs OK sur $MOUNT_CRYPT
  [ -f "$MOUNT_CRYPT/gocryptfs.conf" ] || return 1
  [ -f "$MOUNT_CRYPT/gocryptfs.diriv" ] || return 1
  sha256sum "$MOUNT_CRYPT/gocryptfs.conf" "$MOUNT_CRYPT/gocryptfs.diriv" | sha256sum | awk '{print $1}'
}

verify_or_store_fingerprint() {
  local fp
  fp="$(fingerprint_remote_conf)" || { echo "Erreur: empreinte config gocryptfs introuvable."; exit 1; }
  if [ -f "$CONF_FINGERPRINT_FILE" ]; then
    local old
    old="$(cat "$CONF_FINGERPRINT_FILE" 2>/dev/null || true)"
    if [ -n "$old" ] && [ "$old" != "$fp" ]; then
      echo "Erreur: gocryptfs.conf/diriv a change sur le bucket (empreinte differente)."
      echo "Refus de continuer pour eviter de casser un serveur deja en prod."
      exit 1
    fi
  else
    echo "$fp" > "$CONF_FINGERPRINT_FILE"
    chmod 600 "$CONF_FINGERPRINT_FILE"
  fi
}

update_fingerprint_after_init() {
  local fp
  fp="$(fingerprint_remote_conf)" || return 0
  echo "$fp" > "$CONF_FINGERPRINT_FILE"
  chmod 600 "$CONF_FINGERPRINT_FILE"
}

##################################################
# MODE --clean
##################################################
if [ "${1:-}" = "--clean" ]; then
  section "NETTOYAGE COMPLET (IRREVERSIBLE POUR LES DONNEES)"

  ask_yn "Confirmer le nettoyage des DONNEES S3 ?" "N" || exit 0
  ask_yn "DERNIERE CONFIRMATION (IRREVERSIBLE) ?" "N" || exit 0

  echo "Arret du service gocryptfs si present..."
  systemctl stop s3next-gocryptfs.service 2>/dev/null || true
  systemctl disable s3next-gocryptfs.service 2>/dev/null || true
  rm -f "$SYSTEMD_SERVICE" 2>/dev/null || true
  systemctl daemon-reload

  echo "Demontage..."
  safe_umount "$MOUNT_CLEAR"
  safe_umount "$MOUNT_CRYPT"
  safe_umount "$CACHE_DIR"
  safe_umount "$CLEAN_CACHE"

  install_packages
  ensure_fuse_allow_other

  ensure_tmpfs_cache "$CLEAN_CACHE" "$CLEAN_CACHE_TMPFS_SIZE"
  ensure_dir_mountpoint "$MOUNT_CRYPT" 755

  echo "Montage S3 pour nettoyage..."
  s3fs "$BUCKET" "$MOUNT_CRYPT" \
    -o "passwd_file=$S3FS_PASS" \
    -o "url=$ENDPOINT" \
    -o "use_path_request_style" \
    -o "allow_other" \
    -o "nonempty" \
    -o "use_cache=$CLEAN_CACHE" \
    -o "ensure_diskfree=64" \
    -o "nomultipart"

  if ! is_s3fs_mount "$MOUNT_CRYPT"; then
    echo "Erreur: $MOUNT_CRYPT ne semble pas etre un montage s3fs, refus de supprimer."
    safe_umount "$MOUNT_CRYPT"
    exit 1
  fi

  # Supprime tout le contenu du bucket via s3fs (y compris gocryptfs.conf)
  find "$MOUNT_CRYPT" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + 2>/dev/null || true

  safe_umount "$MOUNT_CRYPT"
  safe_umount "$CLEAN_CACHE"
  safe_umount "$CACHE_DIR"

  rm -rf "$CLEAN_CACHE" "$CACHE_DIR" "$MOUNT_CLEAR" "$MOUNT_CRYPT" 2>/dev/null || true
  rm -f "$GPG_PASS_GPG" "$GPG_WRAPPER" "$CONF_FINGERPRINT_FILE" 2>/dev/null || true

  if gpg --list-keys "$GPG_EMAIL" >/dev/null 2>&1; then
    FPR="$(gpg --list-keys --with-colons "$GPG_EMAIL" | awk -F: '/^fpr:/ {print $10; exit}')"
    if [ -n "$FPR" ]; then
      gpg --batch --yes --delete-secret-keys "$FPR" >/dev/null 2>&1 || true
      gpg --batch --yes --delete-keys "$FPR" >/dev/null 2>&1 || true
    fi
  fi

  fstab_remove_contains "tmpfs $CACHE_DIR"
  fstab_remove_contains "tmpfs $CLEAN_CACHE"
  fstab_remove_contains "s3fs#${BUCKET}"
  systemctl daemon-reload

  if [ -f "$S3FS_PASS" ]; then
    if ask_yn "Supprimer les cles S3 locales ?" "N"; then
      rm -f "$S3FS_PASS"
    fi
  fi

  section "NETTOYAGE TERMINE"
  exit 0
fi

##################################################
# MODE NORMAL
##################################################
section "INSTALLATION S3 CHIFFRE"
ask_yn "Continuer ?" "O" || exit 0

section "MODE"
FIRST_INIT=0
if ask_yn "Premiere initialisation du bucket (creation gocryptfs.conf) ?" "N"; then
  FIRST_INIT=1
fi

ROLE_RW=0
if ask_yn "Cette machine doit pouvoir ECRIRE dans le bucket ?" "N"; then
  ROLE_RW=1
fi

if [ "$FIRST_INIT" -eq 1 ] && [ "$ROLE_RW" -ne 1 ]; then
  echo "Erreur: l'initialisation exige une machine en ecriture."
  exit 1
fi

install_packages
ensure_fuse_allow_other

ensure_dir_mountpoint "$MOUNT_CLEAR" 755
ensure_dir_mountpoint "$MOUNT_CRYPT" 755

##################################################
# CACHE S3FS (TMPFS)
##################################################
section "CACHE S3FS (TMPFS)"
ensure_tmpfs_cache "$CACHE_DIR" "$CACHE_TMPFS_SIZE"
fstab_remove_contains "tmpfs $CACHE_DIR"
fstab_add "tmpfs $CACHE_DIR tmpfs rw,nosuid,nodev,noexec,mode=700,size=$CACHE_TMPFS_SIZE 0 0"

##################################################
# CLES S3
##################################################
section "CLES S3"
if [ -f "$S3FS_PASS" ]; then
  chmod 600 "$S3FS_PASS"
  if ask_yn "Cles S3 deja presentes. Les remplacer ?" "N"; then
    rm -f "$S3FS_PASS"
  fi
fi

if [ ! -f "$S3FS_PASS" ]; then
  read -rp "Access Key S3 : " AK
  read -rsp "Secret Key S3 : " SK
  echo
  printf "%s:%s" "$AK" "$SK" > "$S3FS_PASS"
  chmod 600 "$S3FS_PASS"
fi

##################################################
# MONTAGE S3 (S3FS)
##################################################
section "MONTAGE S3"

fstab_remove_contains "s3fs#${BUCKET}"

S3FS_RO_OPT=""
if [ "$ROLE_RW" -ne 1 ]; then
  S3FS_RO_OPT=",ro"
fi

fstab_add "s3fs#${BUCKET} $MOUNT_CRYPT fuse _netdev,nofail,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,allow_other,nonempty,passwd_file=$S3FS_PASS,use_path_request_style,use_cache=$CACHE_DIR,ensure_diskfree=64,nomultipart,url=$ENDPOINT${S3FS_RO_OPT} 0 0"

systemctl daemon-reload

mount "$CACHE_DIR" >/dev/null 2>&1 || true

safe_umount "$MOUNT_CLEAR"
safe_umount "$MOUNT_CRYPT"

mount "$MOUNT_CRYPT" || { echo "Erreur: montage s3fs echoue sur $MOUNT_CRYPT"; exit 1; }
is_s3fs_mount "$MOUNT_CRYPT" || { echo "Erreur: $MOUNT_CRYPT n'est pas un montage s3fs attendu"; exit 1; }

ls -la "$MOUNT_CRYPT" >/dev/null 2>&1 || { echo "Erreur: acces au montage s3fs impossible (E/S)"; exit 1; }

##################################################
# GPG
##################################################
section "GPG"
if ! gpg --list-keys "$GPG_EMAIL" >/dev/null 2>&1; then
  gpg --batch --generate-key <<EOF
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: s3next
Name-Email: $GPG_EMAIL
Expire-Date: 0
%no-protection
%commit
EOF
fi

##################################################
# MOT DE PASSE GOCRYPTFS (CHIFFRE VIA GPG)
##################################################
section "MOT DE PASSE DE CHIFFREMENT"
if [ ! -f "$GPG_PASS_GPG" ]; then
  read -rsp "Mot de passe : " P1; echo
  read -rsp "Confirmation : " P2; echo
  [ "$P1" = "$P2" ] || { echo "Erreur: mots de passe differents"; exit 1; }
  ask_yn "As-tu sauvegarde ce mot de passe ?" "N" || exit 0
  printf "%s" "$P1" | gpg --batch -r "$GPG_EMAIL" -e -o "$GPG_PASS_GPG"
fi

cat > "$GPG_WRAPPER" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec gpg --batch --quiet --decrypt "$GPG_PASS_GPG"
EOF
chmod 700 "$GPG_WRAPPER"

##################################################
# GOCRYPTFS CONF (INIT OU VERIF)
##################################################
section "BACKEND GOCRYPTFS"

if [ -f "$MOUNT_CRYPT/gocryptfs.conf" ]; then
  if [ "$FIRST_INIT" -eq 1 ]; then
    echo "Note: gocryptfs.conf deja present. Initialisation ignoree."
  fi
else
  if [ "$FIRST_INIT" -eq 1 ]; then
    gocryptfs -init "$MOUNT_CRYPT" < <("$GPG_WRAPPER")
    update_fingerprint_after_init
  else
    echo "Erreur: gocryptfs.conf absent sur le bucket. Ce serveur n'est pas en mode initialisation."
    exit 1
  fi
fi

# Verif empreinte distante pour eviter qu'un autre serveur ait reinitialise/cree une autre config
verify_or_store_fingerprint

##################################################
# SERVICE SYSTEMD (GOCRYPTFS)
##################################################
section "MONTAGE FINAL"

CLEAR_OWNER="$(get_clear_owner)"

# Stop service existant
systemctl stop s3next-gocryptfs.service 2>/dev/null || true

safe_umount "$MOUNT_CLEAR"
ensure_dir_mountpoint "$MOUNT_CLEAR" 755

GOCRYPTFS_RO_FLAG=""
if [ "$ROLE_RW" -ne 1 ]; then
  GOCRYPTFS_RO_FLAG="-ro"
fi

cat > "$SYSTEMD_SERVICE" <<EOF
[Unit]
Description=Next.Ink S3 - Montage dechiffre via gocryptfs
Wants=network-online.target
After=network-online.target media-s3next_crypt.mount
Requires=media-s3next_crypt.mount

[Service]
Type=simple
ExecStartPre=-/bin/umount -l $MOUNT_CLEAR
ExecStartPre=/bin/mkdir -p $MOUNT_CLEAR
ExecStart=/usr/bin/gocryptfs -f -q -allow_other $GOCRYPTFS_RO_FLAG -extpass $GPG_WRAPPER $MOUNT_CRYPT $MOUNT_CLEAR
ExecStartPost=/bin/chown $CLEAR_OWNER:$CLEAR_OWNER $MOUNT_CLEAR
ExecStartPost=/bin/chmod 775 $MOUNT_CLEAR
ExecStop=/bin/umount -l $MOUNT_CLEAR
Restart=on-failure
RestartSec=2
KillMode=process

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now s3next-gocryptfs.service

for i in {1..20}; do
  mountpoint -q "$MOUNT_CLEAR" && break
  sleep 1
done

mountpoint -q "$MOUNT_CLEAR" || { echo "Erreur: montage gocryptfs non actif sur $MOUNT_CLEAR"; systemctl status --no-pager -l s3next-gocryptfs.service || true; exit 1; }

section "TERMINE"
echo "Ecrire ici: $MOUNT_CLEAR"
echo "Backend chiffre (ne pas ecrire): $MOUNT_CRYPT"
if [ "$ROLE_RW" -eq 1 ]; then
  echo "Mode: ECRITURE"
else
  echo "Mode: LECTURE SEULE"
fi
echo "Service: sudo systemctl status s3next-gocryptfs.service"

Modifié par bingo.crepuscule

Posté(e)

Merci pour ce script!

Et en bash, ça change des "scripts" python qu'on nous pond partout.

Plusieurs remarques rapides:

  • Utilise plutôt un "Spoiler" (divulgâcheur) ce sera mieux pour le scroll

Spoiler...

image.png

Il y a 3 heures, bingo.crepuscule a dit :

Je viens de voir qu'on ne peut pas éditer un topic.... 😒

Si (mais alors est-ce un privilège de vénérable?):

Ma capture

image.png

Il y a 2 heures, bingo.crepuscule a dit :

 Pour finalement m'apercevoir que partager un S3 entre plusieurs machines, c'est une galère monstrueuse.

Peux-tu préciser les problèmes que tu rencontres? (même si je pense en connaître certains: le partage entre plusieurs ordis, c'est forcément complexe, mais avec des technos "déconnectées", c'est terrible)

Question un peu noob: comment fais-tu pour pallier au problème classique sous Unix du montage échoué: si mon montage échoue, mais que mon logiciel écrit dans mon point de montage, cela ne déclenche pas d'erreur...
J'ai ce problème avec mes sauvegardes (mon lecteur USB se monte sur un point précis pour que mes scripts de sauvegarde ne soient pas compliqués), mais si le montage a échoué, mes sauvegardes vont saturer mon disque. Du coup maintenant je change les permissions après montage, mais en cas de panne de courant/arrachement, ça ne change pas le problème.

Posté(e)
  • Auteur

Malheureusement comme je ne peux pas éditer mes publications, pas possible d'y adjoindre un spoiler ! 😅

Concernant le python, c'est à la mode, mais je déteste ça, essentiellement pour des raisons de stabilité. J'ai des vieux scripts bash qui fonctionnent avec des outils standards depuis plus de 15 ans, et je n'ai jamais été em*erdé avec ça, alors que des scripts python obsolètes, cassés pour raison de version, où de changement/disparition de dépendances, c'est une plaie ! 😒 (J'ai conscience de ses qualités, mais à mon sens, je préfère la stabilité)

Du coup je m'occupe de tous mes outils en bash, et rien d'autre. Et quand je demande un coup de main à un LLM pour accélérer le boulot, je lui interdit d'utiliser python, ou même npm, la majorité du temps. J'essaie de tout faire le plus possible en bash pour la pérennité. 😄

En pratique, les problèmes que j’ai rencontrés viennent surtout du fait que je cherchais à utiliser S3 comme un disque “classique”, alors que ce n’est pas fait pour ça. Dès qu’on sort du cas simple (un seul ordi, peu d’écritures), ça devient vite bancal : lenteurs, comportements bizarres, erreurs difficiles à comprendre, surtout quand on commence à vouloir partager l’accès entre plusieurs machines.

Pour ta question sur le montage *nix, je n’ai pas trouvé de solution parfaite non plus. Si le montage échoue et que le point de montage redevient un simple dossier, le système ne bloque rien : les écritures partent ailleurs sans prévenir...

Avec des montages réseau ou FUSE, et encore plus avec S3 derrière, on empile des couches qui peuvent casser sans signal clair, j'ai eu un énorme problème en utilisant le script sur plusieurs machines à la fois (avec le chiffrement, je précise, c'était beaucoup moins problématique sans), dès qu'une seconde se connectait, j'avais des soucis de lecture/écriture sur la première, et parfois même le montage chiffré apparaissait comme un simple fichier, plus comme un dossier...
C'est pour ça que comme compromis, j'ai pensé à SSHFS. Une seule machine se connecte au S3 avec chiffrement, et ensuite d'autres machines devront se connecter via SSH pour monter ledit répertoire, ainsi on ne casse rien. Mais ça implique d'avoir accès audit serveur de partout, donc plutôt à un dédié/vps en ligne, ou chez soi avec les bons ports ouverts dans un conteneur LXC pour cloisonner le tout. 🙂

J'ai bien planché dernièrement sur un script interactif de gestion des domaines .onion avec Tor aussi (100% bash), avec quelques améliorations maison (alias, description...), chaque adresse étant associée à un fichier json, et chiffrement automatique et imposé de chaque clé privé, avec gpg, automatisé.
SI ça intéresse du monde je pourrais aussi partager ça, j'ai même intégré la génération de compilation/config mini + ce script + empaquetage dans un paquet .deb depuis les sources pour un paquet minimal préconfiguré "tor-onion" (uniquement pour la publication de domaine)... Et la génération en masse/association à un/des ports spécifiques. 👌

Bon par contre, ça risque d'avoir du mal à passer, directement sur le forum, ça représente, avec l'aide integrée et l'anglais + français, autour de 8000 lignes, il me semble 😅

Un de ces jours, il va vraiment falloir que j'apprenne à utiliser GIT...


image.png

image.png

Modifié par bingo.crepuscule

Rejoindre la conversation

Vous pouvez publier maintenant et vous inscrire plus tard. Si vous avez un compte, connectez-vous maintenant pour publier avec votre compte.

Invité
Répondre à ce sujet…

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.