363 lines
14 KiB
Bash
363 lines
14 KiB
Bash
#!/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 |