Umbau auf Pure-FTPd mit PureDB Virtual Users und Passwort-Dialog
- Komplett auf Pure-FTPd umgestellt (weg von vsftpd) - PureDB virtuelle Benutzer: jeder User sieht nur seine Freigaben - Per-User Bind-Mounts mit Read-Only/Read-Write Durchsetzung - Passwort-Dialog beim Anlegen neuer FTP-Benutzer - Change-Password UI: Dropdown + Button zum Passwort ändern - Setup-Kommando für automatische Pure-FTPd Einrichtung - Anonymous-Checkbox entfernt (nur authentifizierte User) - sudoers-Fix: $SUDO_USER statt $USER bei sudo-Ausführung - openssl passwd statt pure-pw stdin für GUI-Kompatibilität Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+181
-33
@@ -1,21 +1,108 @@
|
||||
#!/bin/bash
|
||||
# dolphin-ftp-share - Manage FTP folder shares for Dolphin
|
||||
# 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> <anon> <acl> Add/update a share
|
||||
# dolphin-ftp-share add <name> <path> <acl> Add/update a share
|
||||
# dolphin-ftp-share delete <name> Remove a share
|
||||
#
|
||||
# Share configs are stored in ~/.local/share/dolphin-ftp-shares/
|
||||
# Shares are bind-mounted into ~/.local/share/dolphin-ftp-root/
|
||||
#
|
||||
# The FTP root directory can be served by any FTP server (e.g. vsftpd).
|
||||
# Each share appears as a real subdirectory under the FTP root via bind mount.
|
||||
# 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 *
|
||||
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
|
||||
@@ -31,33 +118,49 @@ cmd_info() {
|
||||
cmd_add() {
|
||||
local name="$1"
|
||||
local path="$2"
|
||||
local anon="$3"
|
||||
local acl="$4"
|
||||
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"
|
||||
mkdir -p "$SHARE_DIR" "$FTP_ROOT/users"
|
||||
|
||||
# Write share config
|
||||
# Unmount any existing mounts for this share (handles permission changes)
|
||||
unmount_share "$name"
|
||||
|
||||
# Save config
|
||||
cat > "$SHARE_DIR/$name.conf" <<EOF
|
||||
path=$path
|
||||
anonymous=$anon
|
||||
user_acl=$acl
|
||||
EOF
|
||||
|
||||
# Create mount point directory
|
||||
mkdir -p "$FTP_ROOT/$name"
|
||||
# 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"
|
||||
|
||||
# Unmount if already mounted
|
||||
mountpoint -q "$FTP_ROOT/$name" 2>/dev/null && sudo umount "$FTP_ROOT/$name" 2>/dev/null || true
|
||||
ensure_virtual_user "$username"
|
||||
|
||||
# Bind-mount the shared folder into the FTP root
|
||||
sudo mount --bind "$path" "$FTP_ROOT/$name"
|
||||
mkdir -p "$mount_point"
|
||||
|
||||
echo "Share '$name' added: $path -> $FTP_ROOT/$name"
|
||||
# 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() {
|
||||
@@ -68,32 +171,77 @@ cmd_delete() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Unmount bind mount
|
||||
if mountpoint -q "$FTP_ROOT/$name" 2>/dev/null; then
|
||||
sudo umount "$FTP_ROOT/$name"
|
||||
fi
|
||||
|
||||
unmount_share "$name"
|
||||
rm -f "$SHARE_DIR/$name.conf"
|
||||
rmdir "$FTP_ROOT/$name" 2>/dev/null || true
|
||||
|
||||
# Clean up empty directories
|
||||
# Clean up
|
||||
rmdir "$SHARE_DIR" 2>/dev/null || true
|
||||
rmdir "$FTP_ROOT" 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
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
info) cmd_info ;;
|
||||
add) cmd_add "$2" "$3" "${4:-n}" "${5:-}" ;;
|
||||
delete) cmd_delete "$2" ;;
|
||||
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" ;;
|
||||
*)
|
||||
echo "Usage: dolphin-ftp-share {info|add|delete}" >&2
|
||||
echo "Usage: dolphin-ftp-share {setup|info|add|delete|userexists|passwd}" >&2
|
||||
echo "" >&2
|
||||
echo "Commands:" >&2
|
||||
echo " setup Initial Pure-FTPd setup" >&2
|
||||
echo " info List all shares" >&2
|
||||
echo " add <name> <path> <anon> <acl> Add/update a share" >&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
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user