296 lines
9.3 KiB
Bash
Executable File
296 lines
9.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# dolphin-ftp-share - Manage FTP folder shares for Dolphin with Pure-FTPd
|
|
#
|
|
# Each user gets a personal FTP root with only their shared folders.
|
|
# Virtual FTP users (PureDB) are created automatically.
|
|
# Permissions (ro/rw) are enforced via bind mount options.
|
|
#
|
|
# Usage:
|
|
# dolphin-ftp-share setup Initial Pure-FTPd setup
|
|
# dolphin-ftp-share info List all shares
|
|
# dolphin-ftp-share add <name> <path> <acl> Add/update a share
|
|
# dolphin-ftp-share delete <name> Remove a share
|
|
# dolphin-ftp-share passwd <username> <password> Set FTP password
|
|
|
|
set -e
|
|
|
|
SHARE_DIR="${HOME}/.local/share/dolphin-ftp-shares"
|
|
FTP_ROOT="${HOME}/.local/share/dolphin-ftp-root"
|
|
PASSWD_FILE="/etc/pure-ftpd/pureftpd.passwd"
|
|
PDB_FILE="/etc/pure-ftpd/pureftpd.pdb"
|
|
|
|
CALLER_UID=$(id -u)
|
|
CALLER_GID=$(id -g)
|
|
|
|
# Update home directory for an existing virtual FTP user.
|
|
ensure_virtual_user() {
|
|
local username="$1"
|
|
local user_root="$FTP_ROOT/users/$username"
|
|
|
|
mkdir -p "$user_root"
|
|
|
|
if sudo pure-pw show "$username" -f "$PASSWD_FILE" >/dev/null 2>&1; then
|
|
sudo pure-pw usermod "$username" \
|
|
-d "$user_root" \
|
|
-f "$PASSWD_FILE" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
rebuild_db() {
|
|
sudo pure-pw mkdb "$PDB_FILE" -f "$PASSWD_FILE"
|
|
}
|
|
|
|
# Unmount a share from all user roots
|
|
unmount_share() {
|
|
local name="$1"
|
|
if [ -d "$FTP_ROOT/users" ]; then
|
|
for user_dir in "$FTP_ROOT/users"/*/; do
|
|
[ -d "$user_dir" ] || continue
|
|
local mp="${user_dir}${name}"
|
|
if mountpoint -q "$mp" 2>/dev/null; then
|
|
sudo umount "$mp"
|
|
fi
|
|
rmdir "$mp" 2>/dev/null || true
|
|
done
|
|
fi
|
|
}
|
|
|
|
cmd_setup() {
|
|
echo "Setting up Pure-FTPd for Dolphin FTP sharing..."
|
|
|
|
mkdir -p "$FTP_ROOT/users"
|
|
|
|
# Enable PureDB authentication
|
|
echo "$PDB_FILE" | sudo tee /etc/pure-ftpd/conf/PureDB >/dev/null
|
|
sudo ln -sf /etc/pure-ftpd/conf/PureDB /etc/pure-ftpd/auth/50pure
|
|
|
|
# Chroot all users to their FTP home
|
|
echo "yes" | sudo tee /etc/pure-ftpd/conf/ChrootEveryone >/dev/null
|
|
|
|
# Disable anonymous access — only authenticated virtual users
|
|
echo "yes" | sudo tee /etc/pure-ftpd/conf/NoAnonymous >/dev/null
|
|
|
|
# Remove system auth so only PureDB users can log in
|
|
sudo rm -f /etc/pure-ftpd/auth/65unix /etc/pure-ftpd/auth/70pam
|
|
|
|
# Create empty password DB if needed
|
|
sudo touch "$PASSWD_FILE"
|
|
sudo pure-pw mkdb "$PDB_FILE" -f "$PASSWD_FILE"
|
|
|
|
# Enable logging
|
|
echo "yes" | sudo tee /etc/pure-ftpd/conf/VerboseLog >/dev/null
|
|
|
|
# Create sudoers rule for passwordless mount/umount/pure-pw
|
|
# Use SUDO_USER to get the real user (not root) when run with sudo
|
|
local REAL_USER="${SUDO_USER:-$USER}"
|
|
local REAL_HOME
|
|
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
|
|
local REAL_FTP_ROOT="${REAL_HOME}/.local/share/dolphin-ftp-root"
|
|
|
|
sudo tee /etc/sudoers.d/dolphin-ftp-share >/dev/null <<EOF
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/mount --bind *
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/mount -o remount?ro?bind *
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/umount ${REAL_FTP_ROOT}/*
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/pure-pw *
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/tee /etc/pure-ftpd/conf/*
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/pure-ftpd/conf/PassivePortRange
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/sed -i *pureftpd.passwd*
|
|
${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/systemctl restart pure-ftpd
|
|
EOF
|
|
sudo chmod 440 /etc/sudoers.d/dolphin-ftp-share
|
|
|
|
sudo systemctl restart pure-ftpd
|
|
|
|
echo ""
|
|
echo "Setup complete!"
|
|
echo "Share folders via Dolphin: right-click folder → Properties → FTP tab"
|
|
echo "Default FTP password = username. Change with:"
|
|
echo " dolphin-ftp-share passwd <user> <newpassword>"
|
|
}
|
|
|
|
cmd_info() {
|
|
[ -d "$SHARE_DIR" ] || exit 0
|
|
for conf in "$SHARE_DIR"/*.conf; do
|
|
[ -f "$conf" ] || continue
|
|
name=$(basename "$conf" .conf)
|
|
echo "[$name]"
|
|
cat "$conf"
|
|
echo ""
|
|
done
|
|
}
|
|
|
|
cmd_add() {
|
|
local name="$1"
|
|
local path="$2"
|
|
local acl="$3"
|
|
|
|
if [ -z "$name" ] || [ -z "$path" ]; then
|
|
echo "Error: name and path are required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$SHARE_DIR" "$FTP_ROOT/users"
|
|
|
|
# Unmount any existing mounts for this share (handles permission changes)
|
|
unmount_share "$name"
|
|
|
|
# Save config
|
|
cat > "$SHARE_DIR/$name.conf" <<EOF
|
|
path=$path
|
|
user_acl=$acl
|
|
EOF
|
|
|
|
# Process per-user ACL: "user1:ro,user2:rw"
|
|
if [ -n "$acl" ]; then
|
|
IFS=',' read -ra entries <<< "$acl"
|
|
for entry in "${entries[@]}"; do
|
|
local username="${entry%%:*}"
|
|
local perm="${entry##*:}"
|
|
local user_root="$FTP_ROOT/users/$username"
|
|
local mount_point="$user_root/$name"
|
|
|
|
ensure_virtual_user "$username"
|
|
|
|
mkdir -p "$mount_point"
|
|
|
|
# Bind mount the shared folder into user's personal FTP root
|
|
sudo mount --bind "$path" "$mount_point"
|
|
|
|
# Read-only: remount with ro flag
|
|
if [ "$perm" = "ro" ]; then
|
|
sudo mount -o remount,ro,bind "$mount_point"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
rebuild_db
|
|
echo "Share '$name' added: $path"
|
|
}
|
|
|
|
cmd_delete() {
|
|
local name="$1"
|
|
|
|
if [ -z "$name" ]; then
|
|
echo "Error: name is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
unmount_share "$name"
|
|
rm -f "$SHARE_DIR/$name.conf"
|
|
|
|
# Clean up
|
|
rmdir "$SHARE_DIR" 2>/dev/null || true
|
|
|
|
echo "Share '$name' deleted"
|
|
}
|
|
|
|
cmd_userexists() {
|
|
local username="$1"
|
|
|
|
if [ -z "$username" ]; then
|
|
exit 1
|
|
fi
|
|
|
|
if sudo pure-pw show "$username" -f "$PASSWD_FILE" >/dev/null 2>&1; then
|
|
exit 0
|
|
else
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
cmd_passwd() {
|
|
local username="$1"
|
|
local password="$2"
|
|
|
|
if [ -z "$username" ] || [ -z "$password" ]; then
|
|
echo "Error: username and password are required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local user_root="$FTP_ROOT/users/$username"
|
|
mkdir -p "$user_root"
|
|
|
|
# Generate MD5 password hash
|
|
local hash
|
|
hash=$(openssl passwd -1 "$password")
|
|
|
|
if sudo pure-pw show "$username" -f "$PASSWD_FILE" >/dev/null 2>&1; then
|
|
# User exists — update password in-place
|
|
sudo sed -i "s|^${username}:[^:]*:|${username}:${hash}:|" "$PASSWD_FILE"
|
|
echo "Password updated for '$username'"
|
|
else
|
|
# User doesn't exist — add entry to passwd file
|
|
# Format: username:hash:uid:gid:gecos:home:uploadbw:downloadbw:uploadratio:downloadratio:maxconn:filesquota:sizequota:allowedlocalip:allowedclientip:timerestrictions
|
|
echo "${username}:${hash}:${CALLER_UID}:${CALLER_GID}::${user_root}::::::::::::" | \
|
|
sudo tee -a "$PASSWD_FILE" >/dev/null
|
|
echo "Created FTP user '$username'"
|
|
fi
|
|
|
|
rebuild_db
|
|
}
|
|
|
|
cmd_serverconfig() {
|
|
local action="$1"
|
|
|
|
case "$action" in
|
|
show)
|
|
# Output current mode and ports
|
|
if [ -f /etc/pure-ftpd/conf/PassivePortRange ]; then
|
|
local range
|
|
range=$(cat /etc/pure-ftpd/conf/PassivePortRange)
|
|
echo "mode=passive"
|
|
echo "passive_start=${range%% *}"
|
|
echo "passive_end=${range##* }"
|
|
else
|
|
echo "mode=active"
|
|
fi
|
|
;;
|
|
active)
|
|
# Remove passive port range → only active mode
|
|
sudo rm -f /etc/pure-ftpd/conf/PassivePortRange
|
|
sudo systemctl restart pure-ftpd
|
|
echo "Server set to active mode"
|
|
;;
|
|
passive)
|
|
local start_port="$2"
|
|
local end_port="$3"
|
|
if [ -z "$start_port" ] || [ -z "$end_port" ]; then
|
|
echo "Error: start and end port required" >&2
|
|
exit 1
|
|
fi
|
|
echo "${start_port} ${end_port}" | sudo tee /etc/pure-ftpd/conf/PassivePortRange >/dev/null
|
|
sudo systemctl restart pure-ftpd
|
|
echo "Server set to passive mode (ports ${start_port}-${end_port})"
|
|
;;
|
|
*)
|
|
echo "Usage: dolphin-ftp-share serverconfig {show|active|passive <start> <end>}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
case "${1:-}" in
|
|
setup) cmd_setup ;;
|
|
info) cmd_info ;;
|
|
add) cmd_add "$2" "$3" "${4:-}" ;;
|
|
delete) cmd_delete "$2" ;;
|
|
userexists) cmd_userexists "$2" ;;
|
|
passwd) cmd_passwd "$2" "$3" ;;
|
|
serverconfig) cmd_serverconfig "$2" "$3" "$4" ;;
|
|
*)
|
|
echo "Usage: dolphin-ftp-share {setup|info|add|delete|userexists|passwd|serverconfig}" >&2
|
|
echo "" >&2
|
|
echo "Commands:" >&2
|
|
echo " setup Initial Pure-FTPd setup" >&2
|
|
echo " info List all shares" >&2
|
|
echo " add <name> <path> <acl> Add/update a share" >&2
|
|
echo " delete <name> Remove a share" >&2
|
|
echo " userexists <username> Check if FTP user exists" >&2
|
|
echo " passwd <username> <password> Set/create FTP password" >&2
|
|
echo " serverconfig show Show current server config" >&2
|
|
echo " serverconfig active Set active mode" >&2
|
|
echo " serverconfig passive <start> <end> Set passive mode with port range" >&2
|
|
exit 1
|
|
;;
|
|
esac
|