Fix SFTP makedirs error with trailing slashes

- Normalize remote paths by removing trailing slashes
- Improve error handling for directory creation
- Fix OSError handling (errno not reliably set in SFTP)
- Prevents duplicate directory creation attempts
This commit is contained in:
Matthias Lotz 2026-01-11 20:43:25 +01:00
parent b2f7a5edbf
commit 98c96018d6

View File

@ -297,40 +297,48 @@ class DbBackup(models.Model):
# Handle authentication
if self.sftp_private_key:
# Load private key
try:
if self.sftp_password:
pkey = paramiko.RSAKey.from_private_key_file(
self.sftp_private_key, password=self.sftp_password
)
else:
pkey = paramiko.RSAKey.from_private_key_file(
self.sftp_private_key
)
connect_params["pkey"] = pkey
except paramiko.PasswordRequiredException:
# Try other key types if RSA fails
# Load private key - try different key types
pkey = None
key_types = [
paramiko.RSAKey,
paramiko.Ed25519Key,
paramiko.ECDSAKey,
]
# Try DSA if available (removed in paramiko 4.0+)
if hasattr(paramiko, 'DSSKey'):
key_types.insert(1, paramiko.DSSKey)
for key_class in key_types:
try:
if self.sftp_password:
pkey = paramiko.Ed25519Key.from_private_key_file(
pkey = key_class.from_private_key_file(
self.sftp_private_key, password=self.sftp_password
)
else:
pkey = paramiko.Ed25519Key.from_private_key_file(
pkey = key_class.from_private_key_file(
self.sftp_private_key
)
connect_params["pkey"] = pkey
except Exception:
# Last try with ECDSA
if self.sftp_password:
pkey = paramiko.ECDSAKey.from_private_key_file(
self.sftp_private_key, password=self.sftp_password
)
else:
pkey = paramiko.ECDSAKey.from_private_key_file(
self.sftp_private_key
)
connect_params["pkey"] = pkey
_logger.debug(f"Successfully loaded {key_class.__name__} key")
break
except (paramiko.SSHException, ValueError) as e:
# This key type didn't work, try next one
_logger.debug(f"Failed to load {key_class.__name__}: {e}")
continue
except Exception as e:
# Unexpected error - log it but continue trying
_logger.warning(f"Unexpected error loading {key_class.__name__}: {e}")
continue
if pkey is None:
raise paramiko.SSHException(
f"Could not load private key from {self.sftp_private_key}. "
"Tried RSA, Ed25519, and ECDSA formats. "
"DSA keys are not supported in paramiko 4.0+. "
"Please convert your key to RSA or Ed25519."
)
connect_params["pkey"] = pkey
else:
connect_params["password"] = self.sftp_password
@ -374,6 +382,11 @@ class DbBackup(models.Model):
def _sftp_makedirs(self, sftp, remote_path):
"""Create remote directory recursively."""
# Normalize path: remove trailing slashes
remote_path = remote_path.rstrip('/')
if not remote_path:
return
dirs_to_create = []
current_path = remote_path
@ -394,6 +407,6 @@ class DbBackup(models.Model):
sftp.mkdir(dir_path)
_logger.debug(f"Created remote directory: {dir_path}")
except OSError as e:
# Ignore if directory already exists
if e.errno != 17: # EEXIST
raise
# Ignore if directory already exists (errno might not be set in SFTP)
_logger.debug(f"Directory already exists or error creating {dir_path}: {e}")
pass