diff --git a/Dockerfile b/Dockerfile index c8873fd..96a29a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,6 +78,8 @@ EOF RUN rm -rf /tmp/* COPY samba.sh /usr/bin/ +COPY recycle-cleanup.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/recycle-cleanup.sh EXPOSE 137/udp 138/udp 139 445 diff --git a/README.md b/README.md index b13cd4f..fd80d42 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,9 @@ docker run -it --rm upagge/samba -h | `USERID` | UID for smbuser | | `GROUPID` | GID for smb group | | `INCLUDE` | Path to additional config file | +| `RECYCLE_AGE` | Auto-cleanup files older than N days from recycle bin | +| `RECYCLE_CRON_HOUR` | Hour for cleanup job (0-23, default: 3) | +| `RECYCLE_CRON_MINUTE` | Minute for cleanup job (0-59, default: 0) | ## Examples @@ -332,6 +335,61 @@ This enables: **Note**: Some older clients may not support these features. Only enable if all clients are compatible. +## Recycle Bin + +By default, the image includes a recycle bin feature. When files are deleted, they are moved to a `.deleted` folder in each share instead of being permanently removed. + +### Default Configuration + +```ini +vfs objects = catia recycle +recycle:keeptree = yes # Preserve directory structure +recycle:maxsize = 0 # No file size limit (0 = unlimited) +recycle:repository = .deleted # Folder for deleted files +recycle:versions = yes # Keep versions when deleting same filename +``` + +### How It Works + +1. When a file is deleted, it moves to `.deleted` in the share root +2. Directory structure is preserved: `/share/docs/file.txt` → `/share/.deleted/docs/file.txt` +3. Deleting a file with the same name creates versions: `file.txt`, `Copy #1 of file.txt`, etc. + +### Automatic Cleanup + +**Important**: Without cleanup, deleted files accumulate indefinitely and consume disk space. + +Enable automatic cleanup with the `RECYCLE_AGE` environment variable: + +```yaml +services: + samba: + image: upagge/samba + environment: + RECYCLE_AGE: "30" # Delete files older than 30 days + RECYCLE_CRON_HOUR: "4" # Run at 4:00 AM (optional, default: 3) + RECYCLE_CRON_MINUTE: "30" # Run at XX:30 (optional, default: 0) + # ... other configuration +``` + +The cleanup job: +- Runs daily at the specified time (default: 3:00 AM) +- Only activates if recycle bin is enabled (no `-r` flag) +- Logs to `/var/log/recycle-cleanup.log` + +### Disabling Recycle Bin + +Use the `-r` flag or `RECYCLE=true` to disable the recycle bin entirely: + +```bash +docker run -d upagge/samba -r -p -s "share;/data" +``` + +Recommended when: +- Storing large media files (videos, music) +- Limited disk space +- Temporary or cache directories + ## Feedback If you have any problems or questions, please create an [issue on GitHub](https://github.com/upagge/samba/issues). diff --git a/recycle-cleanup.sh b/recycle-cleanup.sh new file mode 100644 index 0000000..8b070cb --- /dev/null +++ b/recycle-cleanup.sh @@ -0,0 +1,39 @@ +#!/bin/sh +#=============================================================================== +# FILE: recycle-cleanup.sh +# +# USAGE: ./recycle-cleanup.sh +# +# DESCRIPTION: Cleanup old files from Samba recycle bins +# +# OPTIONS: --- +# NOTES: Runs via cron when RECYCLE_AGE is set +# AUTHOR: Struchkov Mark (mark@struchkov.dev) +# CREATED: 2025-01-08 +#=============================================================================== + +set -e + +SMB_CONF="/etc/samba/smb.conf" +DAYS="${RECYCLE_AGE:-30}" + +# Get recycle repository name from config (default: .deleted) +RECYCLE_DIR=$(awk -F' = ' '/recycle:repository/ {print $2}' "$SMB_CONF" | tr -d ' ') +RECYCLE_DIR="${RECYCLE_DIR:-.deleted}" + +# Get all share paths from smb.conf +for share_path in $(awk -F' = ' '/^[[:space:]]*path = / {print $2}' "$SMB_CONF"); do + recycle_path="$share_path/$RECYCLE_DIR" + + if [ -d "$recycle_path" ]; then + echo "$(date '+%Y-%m-%d %H:%M:%S') Cleaning $recycle_path (files older than $DAYS days)" + + # Delete old files + find "$recycle_path" -type f -mtime +"$DAYS" -delete 2>/dev/null || true + + # Delete empty directories + find "$recycle_path" -type d -empty -delete 2>/dev/null || true + fi +done + +echo "$(date '+%Y-%m-%d %H:%M:%S') Recycle cleanup completed" diff --git a/samba.sh b/samba.sh index a8e0e66..f0cf7ba 100755 --- a/samba.sh +++ b/samba.sh @@ -287,6 +287,53 @@ secure() { smb encrypt = desired' "$SMB_CONF" } +### setup_recycle_cron: configure cron job for recycle bin cleanup +# Arguments: +# none) +# Return: cron job configured if recycle is active +setup_recycle_cron() { + # Check if recycle bin is enabled (not disabled by -r flag) + if ! grep -q 'vfs objects.*recycle' "$SMB_CONF"; then + echo "INFO: Recycle bin is disabled, skipping cron setup" >&2 + return 0 + fi + + local age="${RECYCLE_AGE}" + local hour="${RECYCLE_CRON_HOUR:-3}" + local minute="${RECYCLE_CRON_MINUTE:-0}" + + # Validate RECYCLE_AGE is a positive integer + if ! [[ "$age" =~ ^[0-9]+$ ]] || [ "$age" -lt 1 ]; then + echo "ERROR: RECYCLE_AGE must be a positive integer (got: $age)" >&2 + return 1 + fi + + # Validate hour (0-23) + if ! [[ "$hour" =~ ^[0-9]+$ ]] || [ "$hour" -lt 0 ] || [ "$hour" -gt 23 ]; then + echo "ERROR: RECYCLE_CRON_HOUR must be 0-23 (got: $hour)" >&2 + return 1 + fi + + # Validate minute (0-59) + if ! [[ "$minute" =~ ^[0-9]+$ ]] || [ "$minute" -lt 0 ] || [ "$minute" -gt 59 ]; then + echo "ERROR: RECYCLE_CRON_MINUTE must be 0-59 (got: $minute)" >&2 + return 1 + fi + + echo "INFO: Setting up recycle cleanup cron (age: ${age} days, schedule: ${hour}:${minute})" >&2 + + # Create crontab with environment variable + cat > /etc/crontabs/root <> /var/log/recycle-cleanup.log 2>&1 +EOF + + # Start crond in background + crond -b -l 8 + echo "INFO: Crond started for recycle bin cleanup" >&2 +} + ### usage: Help # Arguments: # none) @@ -340,6 +387,13 @@ Options (fields in '[]' are optional, '<>' are required): -E Enable enhanced security (signing and encryption) Enables: server signing, client signing, SMB encryption +Environment only options: + RECYCLE_AGE= Enable automatic recycle bin cleanup + Files older than will be deleted daily + Requires recycle bin to be active (no -r flag) + RECYCLE_CRON_HOUR=<0-23> Hour for cleanup job (default: 3) + RECYCLE_CRON_MINUTE=<0-59> Minute for cleanup job (default: 0) + The 'command' (if provided and valid) will be run instead of samba " >&2 exit $RC @@ -395,6 +449,7 @@ done < <(env | awk '/^USER[0-9=_]/ {sub (/^[^=]*=/, "", $0); print}') [[ "${SECURE:-""}" ]] && secure [[ "${INCLUDE:-""}" ]] && include "$INCLUDE" [[ "${PERMISSIONS:-""}" ]] && perms & +[[ "${RECYCLE_AGE:-""}" ]] && setup_recycle_cron if [[ $# -ge 1 ]]; then # Validate command exists and is executable