#!/bin/bash # Set up logging to file only LOG_DIR="/home/jelastic/add-sftp-user-addon/logs" LOG_FILE="$LOG_DIR/script_output.log" ERROR_LOG="$LOG_DIR/errors.log" OPERATION_LOG="$LOG_DIR/operations.log" DEBUG_LOG="$LOG_DIR/debug.log" DEBUG=${4:-0} # Ensure log directory exists mkdir -p $LOG_DIR &>/dev/null # Function to log ONLY to file, not to stdout log_to_file() { local level=${1:-INFO} local message=${2} local timestamp=$(date +"%Y-%m-%d %H:%M:%S") local script_id="$(date +%Y%m%d%H%M%S)-$$" echo "[$script_id] $timestamp [$level] $message" >> "$LOG_FILE" # Log errors to error log if [[ "$level" == "ERROR" || "$level" == "WARNING" ]]; then echo "[$script_id] $timestamp [$level] $message" >> "$ERROR_LOG" fi # Log success to operation log if [[ "$level" == "INFO" || "$level" == "SUCCESS" ]]; then echo "[$script_id] $timestamp [$level] $message" >> "$OPERATION_LOG" fi # Log debug messages if enabled if [[ "$level" == "DEBUG" && "$DEBUG" -eq 1 ]]; then echo "[$script_id] $timestamp [$level] $message" >> "$DEBUG_LOG" fi } # Enhanced logging functions - file only log() { log_to_file "INFO" "$1" } log_error() { log_to_file "ERROR" "$1" } log_warning() { log_to_file "WARNING" "$1" } log_debug() { if [ "$DEBUG" -eq 1 ]; then log_to_file "DEBUG" "$1" fi } log_success() { log_to_file "SUCCESS" "$1" } # Log system information for debugging context log_system_info() { log_debug "============= SYSTEM INFORMATION =============" log_debug "Operating System: $(cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"')" log_debug "Kernel: $(uname -r)" log_debug "SSH Version: $(ssh -V 2>&1)" log_debug "SSH Config Status: $(systemctl status sshd | grep Active | awk '{print $2}')" log_debug "Script Parameters: username=$USERNAME, ssh_enabled=$SSH_ENABLED" log_debug "==============================================" } # Function to log command execution with result log_cmd() { local cmd="$1" local cmd_desc="$2" log_debug "Executing: $cmd_desc" log_debug "Command: $cmd" # Execute command and capture output and status local output output=$(eval "$cmd" 2>&1) local status=$? if [ $status -eq 0 ]; then log_debug "Command succeeded: $cmd_desc" if [ -n "$output" ]; then log_debug "Output: $output" fi else log_error "Command failed ($status): $cmd_desc" log_error "Error output: $output" fi return $status } # Fix SFTP configuration for Jelastic Virtuozzo LLSMP fix_sftp_config() { log "Checking and fixing SSH configuration for SFTP access" # Create a backup of the original config log_cmd "cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)" "Creating backup of original sshd_config" # Fix the malformed SFTP subsystem line - using safer sed approach if grep -q "Subsystemsftp" /etc/ssh/sshd_config; then log "Fixing malformed SFTP subsystem configuration" # Use a temporary file for safer editing if log_cmd "sed 's|Subsystemsftp/usr/libexec/openssh/sftp-server|Subsystem sftp /usr/libexec/openssh/sftp-server|g' /etc/ssh/sshd_config > /etc/ssh/sshd_config.new" "Fixing malformed Subsystem line"; then log_cmd "mv /etc/ssh/sshd_config.new /etc/ssh/sshd_config" "Applying fixed configuration" log_success "Fixed malformed Subsystem line" else log_error "Failed to fix Subsystem line, reverting to backup" log_cmd "cp /etc/ssh/sshd_config.bak.$(ls -t /etc/ssh/sshd_config.bak.* | head -1 | awk -F/ '{print $NF}') /etc/ssh/sshd_config" "Restoring backup" fi fi # Enable password authentication globally if it's set to no if grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config; then log "Enabling password authentication in SSH" if log_cmd "sed 's/^PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config > /etc/ssh/sshd_config.new" "Enabling password authentication"; then log_cmd "mv /etc/ssh/sshd_config.new /etc/ssh/sshd_config" "Applying configuration with password authentication" log_success "Enabled password authentication" else log_error "Failed to enable password authentication, reverting to backup" log_cmd "cp /etc/ssh/sshd_config.bak.$(ls -t /etc/ssh/sshd_config.bak.* | head -1 | awk -F/ '{print $NF}') /etc/ssh/sshd_config" "Restoring backup" fi fi # Remove any existing incomplete/duplicate Match Group sftpusers blocks if grep -q "Match Group sftpusers" /etc/ssh/sshd_config; then log "Removing existing Match Group sftpusers blocks to prevent duplicates" # This complex sed pattern removes the Match Group sftpusers block completely if log_cmd "sed '/^Match Group sftpusers/,/^Match\|^[[:space:]]*$/d' /etc/ssh/sshd_config > /etc/ssh/sshd_config.new" "Removing existing Match Group blocks"; then log_cmd "mv /etc/ssh/sshd_config.new /etc/ssh/sshd_config" "Applying cleaned configuration" log_success "Removed existing Match Group sftpusers blocks" else log_error "Failed to remove existing Match Group blocks, reverting to backup" log_cmd "cp /etc/ssh/sshd_config.bak.$(ls -t /etc/ssh/sshd_config.bak.* | head -1 | awk -F/ '{print $NF}') /etc/ssh/sshd_config" "Restoring backup" fi fi # Configure SFTP chroot jail cleanly at the end of the file log "Adding fresh SFTP chroot configuration for sftpusers group" cat >> /etc/ssh/sshd_config << EOF # SFTP chroot configuration added by SFTP addon Match Group sftpusers ChrootDirectory /home/sftpusers/%u ForceCommand internal-sftp PasswordAuthentication yes AllowTcpForwarding no X11Forwarding no EOF # Remove duplicate lines and verify config log "Cleaning up configuration file" if log_cmd "awk '!seen[\$0]++' /etc/ssh/sshd_config > /etc/ssh/sshd_config.new" "Removing duplicate lines"; then log_cmd "mv /etc/ssh/sshd_config.new /etc/ssh/sshd_config" "Applying deduplicated configuration" log_success "Removed duplicate lines from configuration" else log_error "Failed to remove duplicate lines, reverting to backup" log_cmd "cp /etc/ssh/sshd_config.bak.$(ls -t /etc/ssh/sshd_config.bak.* | head -1 | awk -F/ '{print $NF}') /etc/ssh/sshd_config" "Restoring backup" fi # Verify the configuration before restarting log "Verifying SSH configuration before restart" if log_cmd "sshd -t" "Validating sshd configuration"; then log_success "SSH configuration is valid, restarting service" log_cmd "systemctl restart sshd" "Restarting SSH service" else log_error "SSH configuration is INVALID, reverting to backup" log_cmd "cp /etc/ssh/sshd_config.bak.$(ls -t /etc/ssh/sshd_config.bak.* | head -1 | awk -F/ '{print $NF}') /etc/ssh/sshd_config" "Restoring backup" log "Restarting SSH with original configuration" log_cmd "systemctl restart sshd" "Restarting SSH service with original config" return 1 fi return 0 } validate_username() { local username=$1 log_debug "Validating username: $username" if ! [[ $username =~ ^[a-zA-Z0-9_]{3,32}$ ]]; then log_error "Invalid username format. Username must be 3-32 characters long and contain only letters, numbers, and underscores." return 1 fi log_debug "Username validation passed" return 0 } # Fix directory permissions for chroot fix_chroot_permissions() { # Check and fix /home permissions if [ -d "/home" ]; then current_mode=$(stat -c "%a" /home) current_owner=$(stat -c "%U:%G" /home) log "Checking /home directory permissions - Current: $current_owner $current_mode" # /home must be owned by root and not writable by others if [ "$current_mode" != "755" ] || [ "$current_owner" != "root:root" ]; then log "Fixing /home directory permissions for chroot" chown root:root /home chmod 755 /home fi fi # Check and fix /home/sftpusers permissions if [ -d "/home/sftpusers" ]; then current_mode=$(stat -c "%a" /home/sftpusers) current_owner=$(stat -c "%U:%G" /home/sftpusers) log "Checking /home/sftpusers directory permissions - Current: $current_owner $current_mode" # /home/sftpusers must be owned by root and not writable by others if [ "$current_mode" != "755" ] || [ "$current_owner" != "root:root" ]; then log "Fixing /home/sftpusers directory permissions for chroot" chown root:root /home/sftpusers chmod 755 /home/sftpusers fi fi } # Main script USERNAME=$1 PASSWORD=$2 SSH_ENABLED=${3:-false} # Log to file only log "======== STARTING SFTP USER SETUP ========" log "Script started with username: $USERNAME, ssh_enabled: $SSH_ENABLED" # Log system information log_system_info # Fix SFTP configuration log "Phase 1: Configuring SSH/SFTP service" if ! fix_sftp_config; then log_error "Failed to configure SSH/SFTP service, exiting" exit 1 fi log_success "SSH/SFTP service configuration completed" # Fix directory permissions for chroot log "Phase 1.1: Fixing directory permissions for chroot" fix_chroot_permissions log_success "Directory permissions fixed for chroot" # Validate username format log "Phase 2: Validating username" if ! validate_username "$USERNAME"; then log_error "Username validation failed, exiting" exit 1 fi log_success "Username validation passed" # Check if user already exists log "Phase 3: Checking if user already exists" if id "$USERNAME" &>/dev/null; then log_error "Username $USERNAME already exists. Please choose a different username." exit 1 fi log_success "Username is available for creation" USER_HOME="/home/sftpusers/$USERNAME" ROOT_DIRECTORY="/var/www/webroot/ROOT" log_debug "Setting paths - USER_HOME: $USER_HOME, ROOT_DIRECTORY: $ROOT_DIRECTORY" # Create the sftpusers group if it doesn't exist log "Phase 4: Setting up groups" if ! getent group sftpusers > /dev/null; then log "Creating sftpusers group for SFTP chroot access" log_cmd "groupadd sftpusers" "Creating sftpusers group" fi log_success "Group setup completed" # Ensure the parent directory for user home directories exists log "Phase 5: Setting up directories" if [ ! -d "/home/sftpusers" ]; then log "Creating /home/sftpusers directory" log_cmd "mkdir -p /home/sftpusers" "Creating /home/sftpusers directory" log_cmd "chown root:root /home/sftpusers" "Setting ownership for /home/sftpusers" log_cmd "chmod 755 /home/sftpusers" "Setting permissions for /home/sftpusers" fi log_success "Directory setup completed" # Create the user account log "Phase 6: Creating user account" if [ "$SSH_ENABLED" = "true" ]; then log "Creating user with SSH access" log_cmd "useradd -d $USER_HOME -m -s /bin/bash $USERNAME" "Creating user with bash shell" else log "Creating user with SFTP-only access" log_cmd "useradd -d $USER_HOME -m -s /sbin/nologin $USERNAME" "Creating user with nologin shell" fi log_success "User account created" # Set password log "Phase 7: Setting user password" log_cmd "echo '$USERNAME:$PASSWORD' | chpasswd" "Setting user password" log_success "Password set for user $USERNAME" # Set up proper directory structure for chroot jail log "Phase 8: Setting up chroot jail structure" log_cmd "chown root:root $USER_HOME" "Setting ownership for chroot directory" log_cmd "chmod 755 $USER_HOME" "Setting permissions for chroot directory" # Create writable data directory for user log_cmd "mkdir -p $USER_HOME/data" "Creating data directory" log_cmd "chown $USERNAME:$USERNAME $USER_HOME/data" "Setting ownership for data directory" log_cmd "chmod 775 $USER_HOME/data" "Setting permissions for data directory" # Create mount point for webroot (using bind mount instead of symlink) log "Phase 9: Setting up webroot access via bind mount" log_cmd "mkdir -p $USER_HOME/data/ROOT" "Creating ROOT mount point" log_cmd "mount --bind $ROOT_DIRECTORY $USER_HOME/data/ROOT" "Binding webroot to user's ROOT directory" # Add mount to fstab to persist across reboots if ! grep -q "$ROOT_DIRECTORY $USER_HOME/data/ROOT" /etc/fstab; then log_cmd "echo \"$ROOT_DIRECTORY $USER_HOME/data/ROOT none bind 0 0\" >> /etc/fstab" "Adding bind mount to fstab" fi log_success "Created bind mount for webroot access" # Add user to the required groups log "Phase 10: Adding user to groups" log_cmd "usermod -aG sftpusers $USERNAME" "Adding user to sftpusers group" log_success "Added $USERNAME to sftpusers group for chroot access" log_cmd "usermod -aG litespeed $USERNAME" "Adding user to litespeed group" log_success "Added $USERNAME to litespeed group for file access" # Create welcome file log "Phase 11: Creating welcome file" cat > $USER_HOME/data/welcome.txt << EOF Welcome to your SFTP account on Jelastic! Your account has been set up with the following details: Username: $USERNAME Home Directory: $USER_HOME Web Root: $USER_HOME/data/ROOT (symlink to $ROOT_DIRECTORY) For help and support, please contact your system administrator. EOF log_cmd "chown $USERNAME:$USERNAME $USER_HOME/data/welcome.txt" "Setting welcome file ownership" log_cmd "chmod 644 $USER_HOME/data/welcome.txt" "Setting welcome file permissions" log_success "Welcome file created" # Always export variables directly with no console output export CREATED_USERNAME="$USERNAME" export CREATED_PASSWORD="$PASSWORD" # All logging should be to file only log_success "Script completed successfully for user $USERNAME" log "======== SFTP USER SETUP COMPLETE ========" exit 0