Very fast asynchronous FTP server library providing RFC-959 compliant FTP servers with advanced features including FTPS, IPv6, Unicode support, and flexible authentication systems
—
Logging configuration, process management, and utility functions for debugging, deployment, and command-line usage of pyftpdlib. These utilities provide essential support functions for production FTP server deployments.
Comprehensive logging configuration with colored output, multi-process support, and flexible formatting options.
# Module-level variables
logger: logging.Logger # Default pyftpdlib logger instance
LEVEL: int = logging.INFO # Default logging level
PREFIX: str = '[%(levelname)1.1s %(asctime)s]' # Default log prefix
PREFIX_MPROC: str = '[%(levelname)1.1s %(asctime)s %(process)s]' # Multi-process prefix
COLOURED: bool # Terminal color support detection
TIME_FORMAT: str = "%Y-%m-%d %H:%M:%S" # Timestamp format
class LogFormatter(logging.Formatter):
"""
Custom log formatter with color support and robust encoding.
Features terminal color detection and proper str/bytes handling.
"""
def __init__(self, *args, **kwargs):
"""Initialize formatter with color detection."""
def format(self, record):
"""
Format log record with colors and timestamps.
Parameters:
- record: logging.LogRecord instance
Returns:
- Formatted log message string
"""
def config_logging(level=LEVEL, prefix=PREFIX, other_loggers=None):
"""
Configure pyftpdlib logging system.
Parameters:
- level: logging level (logging.DEBUG, logging.INFO, etc.)
- prefix: log message prefix format string
- other_loggers: list of other logger names to configure
"""
def debug(s, inst=None):
"""
Debug logging helper function.
Parameters:
- s: message to log
- inst: optional instance for context information
"""
def is_logging_configured():
"""
Check if logging has been configured.
Returns:
- True if logging is already configured
"""Multi-process server support with worker process forking and automatic restart capabilities.
def cpu_count():
"""
Get number of CPU cores available.
Returns:
- Number of CPU cores as integer
"""
def fork_processes(number, max_restarts=100):
"""
Fork multiple worker processes for multi-process server model.
Parameters:
- number: number of worker processes to fork
- max_restarts: maximum automatic restarts per worker (0 = no limit)
Returns:
- Process ID in parent process, None in worker processes
Note:
- Available on POSIX systems only
- Handles SIGCHLD for automatic worker restart
- Distributes work among processes using SO_REUSEPORT where available
"""
def _reseed_random():
"""
Internal function to reseed random number generator in child processes.
Ensures each worker process has independent random state.
"""Full-featured command-line interface for quick FTP server deployment with extensive configuration options.
def main(args=None):
"""
Main entry point for command-line FTP server.
Accessible via: python -m pyftpdlib
Parameters:
- args: command line arguments list (None = use sys.argv)
Command-line options:
-i, --interface ADDRESS Bind to specific interface (default: all interfaces)
-p, --port PORT Port number (default: 2121)
-w, --write Enable write permissions for anonymous
-d, --directory FOLDER Directory to serve (default: current directory)
-n, --nat-address ADDRESS NAT address to use in PASV responses
-r, --range FROM-TO Passive port range (e.g., 8000-9000)
-D, --debug Enable debug logging
-v, --version Show version and exit
-V, --verbose Enable verbose logging
-u, --username USER Username for authentication (requires -P)
-P, --password PASS Password for authentication (requires -u)
"""from pyftpdlib.log import config_logging, debug
import logging
# Configure basic logging
config_logging(level=logging.INFO)
# Configure debug logging with custom prefix
config_logging(
level=logging.DEBUG,
prefix='[%(asctime)s] %(name)s: %(levelname)s - '
)
# Configure logging for other components
config_logging(
level=logging.WARNING,
other_loggers=['paramiko', 'requests']
)
# Use debug helper
debug("Connection established", inst=handler_instance)from pyftpdlib.prefork import fork_processes, cpu_count
from pyftpdlib.servers import FTPServer
def start_worker():
"""Worker process main function."""
# Setup server in worker process
server = FTPServer(("0.0.0.0", 21), handler)
server.serve_forever()
# Fork worker processes
num_workers = cpu_count()
print(f"Starting {num_workers} worker processes")
if fork_processes(num_workers) is None:
# This is a worker process
start_worker()
else:
# This is the parent process
print("All workers started")
# Parent can exit or do other work# Basic FTP server on default port 2121
python -m pyftpdlib
# FTP server with write access for anonymous users
python -m pyftpdlib --write
# Custom port and directory
python -m pyftpdlib -p 21 -d /var/ftp/pub
# Server with authentication
python -m pyftpdlib -u ftpuser -P secret123
# Advanced configuration
python -m pyftpdlib \
--interface 0.0.0.0 \
--port 21 \
--directory /home/ftp \
--nat-address 203.0.113.10 \
--range 50000-50099 \
--verbosefrom pyftpdlib.log import LogFormatter
import logging
import sys
class CustomFTPHandler(FTPHandler):
def __init__(self, conn, server, ioloop=None):
super().__init__(conn, server, ioloop)
# Setup custom logger for this session
self.session_logger = logging.getLogger(f'ftp.{self.remote_ip}')
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(LogFormatter())
self.session_logger.addHandler(handler)
self.session_logger.setLevel(logging.INFO)
def on_login(self, username):
self.session_logger.info(f"User {username} logged in")
def on_file_sent(self, file):
self.session_logger.info(f"File sent: {file}")
def on_logout(self, username):
self.session_logger.info(f"User {username} logged out")import logging
import logging.handlers
from pyftpdlib.log import LogFormatter
def setup_production_logging():
"""Configure logging for production deployment."""
# Main application logger
logger = logging.getLogger('pyftpdlib')
logger.setLevel(logging.INFO)
# File handler with rotation
file_handler = logging.handlers.RotatingFileHandler(
'/var/log/pyftpdlib.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
file_handler.setFormatter(LogFormatter())
logger.addHandler(file_handler)
# Syslog handler for system integration
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
syslog_formatter = logging.Formatter(
'pyftpdlib[%(process)d]: %(levelname)s - %(message)s'
)
syslog_handler.setFormatter(syslog_formatter)
logger.addHandler(syslog_handler)
# Console handler for interactive debugging
console_handler = logging.StreamHandler()
console_handler.setFormatter(LogFormatter())
console_handler.setLevel(logging.WARNING)
logger.addHandler(console_handler)
setup_production_logging()import signal
import sys
import time
from pyftpdlib.prefork import fork_processes
class ProcessManager:
def __init__(self, worker_count=None):
self.worker_count = worker_count or cpu_count()
self.should_exit = False
def signal_handler(self, signum, frame):
"""Handle shutdown signals."""
print(f"Received signal {signum}, shutting down...")
self.should_exit = True
def start_master(self):
"""Start master process with worker management."""
# Setup signal handlers
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
print(f"Starting master process with {self.worker_count} workers")
# Fork workers with restart capability
if fork_processes(self.worker_count, max_restarts=10) is None:
# Worker process
self.start_worker()
else:
# Master process
self.monitor_workers()
def start_worker(self):
"""Worker process main loop."""
try:
server = FTPServer(("0.0.0.0", 21), handler)
server.serve_forever()
except KeyboardInterrupt:
pass
except Exception as e:
print(f"Worker error: {e}")
sys.exit(1)
def monitor_workers(self):
"""Master process monitoring loop."""
while not self.should_exit:
time.sleep(1)
print("Master process exiting")
# Usage
manager = ProcessManager(worker_count=4)
manager.start_master()import os
from pyftpdlib.log import config_logging
def configure_for_environment():
"""Configure logging based on environment."""
if os.getenv('ENVIRONMENT') == 'development':
# Development: verbose console logging
config_logging(
level=logging.DEBUG,
prefix='[%(asctime)s] %(name)s:%(lineno)d - %(levelname)s - '
)
print("Development logging enabled")
elif os.getenv('ENVIRONMENT') == 'production':
# Production: structured logging to files
config_logging(
level=logging.INFO,
prefix='[%(asctime)s] %(process)d - %(levelname)s - '
)
print("Production logging enabled")
else:
# Default: standard logging
config_logging(level=logging.INFO)
configure_for_environment()#!/usr/bin/env python3
"""
Systemd-compatible FTP server script.
Save as /usr/local/bin/pyftpd and make executable.
"""
import sys
import logging
from pyftpdlib.log import config_logging
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
def main():
# Configure logging for systemd (no timestamps, systemd adds them)
config_logging(
level=logging.INFO,
prefix='%(levelname)s - '
)
# Setup FTP server
authorizer = DummyAuthorizer()
authorizer.add_anonymous('/var/ftp/pub', perm='elr')
handler = FTPHandler
handler.authorizer = authorizer
server = FTPServer(('0.0.0.0', 21), handler)
try:
logging.info("Starting FTP server")
server.serve_forever()
except KeyboardInterrupt:
logging.info("Received SIGINT, shutting down")
except Exception as e:
logging.error(f"Server error: {e}")
sys.exit(1)
finally:
server.close_all()
logging.info("FTP server stopped")
if __name__ == '__main__':
main()Corresponding systemd service file (/etc/systemd/system/pyftpd.service):
[Unit]
Description=Python FTP Server
After=network.target
[Service]
Type=simple
User=ftp
Group=ftp
ExecStart=/usr/local/bin/pyftpd
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetpyftpdlib respects several environment variables for configuration:
Install with Tessl CLI
npx tessl i tessl/pypi-pyftpdlib