init commit

This commit is contained in:
mk
2025-07-27 18:55:43 -03:00
commit 33f16af889
6 changed files with 192 additions and 0 deletions

25
Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM alpine:latest
# Install dependencies
RUN apk update && apk add --no-cache \
bash \
rsync \
coreutils \
cronie \
&& rm -rf /var/cache/apk/*
# Copy backup and list scripts
COPY backup.sh /usr/local/bin/backup.sh
COPY list.sh /usr/local/bin/list.sh
# Mark them executables
RUN chmod +x /usr/local/bin/backup.sh
RUN chmod +x /usr/local/bin/list.sh
# Add crontab file and needed cache directory
RUN mkdir -p /root/.cache/crontab && chmod 700 /root/.cache/crontab
COPY crontab.txt /etc/crontabs/root
RUN crontab /etc/crontabs/root
# Start cron in foreground
CMD ["/usr/sbin/crond", "-f"]

33
README Normal file
View File

@@ -0,0 +1,33 @@
#What is this
This is a docker for a cron job that runs rsync to backup folders.
the backup.sh is the backup script but the cronjob call list.sh which thenc calls backup.sh
this is to make listing multiple folders easier.
#Setup
#Source and destination
On docker compose you'll find volumes
```
volumes:
- /path/to/sources/:/source/
- /path/to/backups/:/backup/
```
change `path/to/source` and `path/to/backups` to the paths where the folders you'll want to backup are and where the backup is going to
##List
```
/usr/local/bin/backup.sh /source/project1 /backup/project1-backup --keep-trash=false --rsync-delete=true
```
this is the list the cronjob will run.
`/usr/local/bin/backup.sh` is just the name of the backup script, does not need change
`/source/project1` Param 1 is the folder you want a backup of, if need change
`/backup/project1-backup` Param 2 is the folder where the backup will go
`--keep-trash=false` Param 3 is if a "trash" folder will be kept, this means that any file deleted from the source folder will have it's latest backup moved to a "trash" folder where it'll stay for 30 days, after that, it too will be deleted
`--rsync-delete=true` Param 4 if used, delete flag will be called for rsync, deleting any file on backup folder that has been deleted from souce
if both are false, then any deleted files will be kept indefinitely in the backup folder.
#Building and running
build with
`docker build -t cron-backuper .`
and use `docker compose up` to bring it up(it's easier to config volumes that way)

122
backup.sh Executable file
View File

@@ -0,0 +1,122 @@
#!/bin/bash
############################################
#
# SETUP
#
############################################
# Logs to file and stdout
log() {
local now
now=$(date "+%Y/%m/%d %H:%M:%S")
echo "$now - $1" | tee -a "$LOGFILE"
}
############################################
#
# ARGUMENT CHECK
#
############################################
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <source_dir> <destination_dir> [--keep-trash=true|false]"
exit 1
fi
SOURCE_DIR="$1"
DESTINATION_ROOT="$2"
DESTINATION_DIR="$DESTINATION_ROOT/backup"
TRASH_DIR="$DESTINATION_ROOT/trash"
LOGFILE="$DESTINATION_ROOT/backup.log"
KEEP_TRASH=false
RSYNC_DELETE=false
shift 2
while [[ $# -gt 0 ]]; do
case "$1" in
--keep-trash=true) KEEP_TRASH=true ;;
--keep-trash=false) KEEP_TRASH=false ;;
--rsync-delete=true) RSYNC_DELETE=true ;;
--rsync-delete=false) RSYNC_DELETE=false ;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
shift
done
############################################
#
# STARTUP
#
############################################
# Create directories if they don't exist
mkdir -p "$SOURCE_DIR" "$DESTINATION_DIR" "$TRASH_DIR"
#Create log file if it doesn't exist
[ -e "$LOGFILE" ] || touch "$LOGFILE"
log "SOURCE_DIR: $SOURCE_DIR"
log "DESTINATION_DIR: $DESTINATION_DIR"
log "TRASH_DIR: $TRASH_DIR"
log "LOGFILE: $LOGFILE"
############################################
#
# BACKUP
#
############################################
log "$SOURCE_DIR $DESTINATION_DIR"
if rsync -ah "$SOURCE_DIR/" "$DESTINATION_DIR/" 2>>"$LOGFILE"; then
log "Backup completed successfully"
else
log "Backup failed!"
exit 1
fi
if [ "$KEEP_TRASH" != "true" ]; then
log "Skipping trash operations"
exit 0
fi
############################################
#
# MOVE DELETED FILES TO TRASH
#
############################################
# Iterate over all files in DESTINATION and look for files not in SOURCE to move to TRASH
find "$DESTINATION_DIR" -type f | while read -r dest_file; do
# Construct the relative path of the file inside DESTINATION
rel_path="${dest_file#$DESTINATION_DIR/}"
#log "REL_PATH: $rel_path"
# Construct the corresponding SOURCE file path
source_file="$SOURCE_DIR/$rel_path"
#log "SOURCE_FILE: $source_file"
# If the file doesn't exist in SOURCE, move it to TRASH preserving folder structure
if [ ! -f "$source_file" ]; then
# Create the directory structure inside TRASH
dest_dir=$(dirname "$TRASH_DIR/$rel_path")
#log "DEST_DIR: $dest_dir"
mkdir -p "$dest_dir"
# Move the file to TRASH
mv "$dest_file" "$TRASH_DIR/${rel_path}_$(date +%s)"
# Updates last changed timestamp to now so 30 days counter starts on move
touch "$TRASH_DIR/${rel_path}_$(date +%s)"
#log "Moved: $dest_file -> $TRASH_DIR/$rel_path"
fi
done
############################################
#
# CLEANUP
#
############################################
# Remove empty directories from DESTINATION_DIR
find "$DESTINATION_DIR" -type d -empty -delete
# Deleting files older than 30 days from trash and cleaning up empty directories.
find "$TRASH_DIR" -type f -mtime +30 -exec rm -f {} \;
find "$TRASH_DIR" -type d -empty -delete
log "Done!!"

2
crontab.txt Normal file
View File

@@ -0,0 +1,2 @@
0 3 * * * /usr/local/bin/list.sh >> /proc/1/fd/1 2>&1

8
docker-compose.yml Normal file
View File

@@ -0,0 +1,8 @@
services:
backup:
image: cron-backuper
container_name: cron-backuper
volumes:
- /path/to/sources/:/source/
- /path/to/backups/:/backup/
command: ["/bin/sh", "-c", "crontab /etc/crontabs/root && crond -f"]

2
list.sh Normal file
View File

@@ -0,0 +1,2 @@
/usr/local/bin/backup.sh /source/project1 /backup/project1-backup --keep-trash=false --rsync-delete=true
/usr/local/bin/backup.sh /source/project2 /backup/project2-backup --keep-trash=true --rsync-delete=false