Add files via upload

This commit is contained in:
Outis
2026-03-28 08:13:32 -04:00
committed by GitHub
parent 4eee783af0
commit c446329b44
+501
View File
@@ -0,0 +1,501 @@
#!/bin/bash
# ─── Config ───────────────────────────────────────────────────────────────────
CONFIG_DIR="$HOME/.config/backup_project"
INSTALL_PATH="/usr/local/bin/backup_project"
# ─── Help ─────────────────────────────────────────────────────────────────────
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
cat <<EOF
backup_project — Incremental folder backup with versioned naming
USAGE:
backup_project [folder] Back up a folder (or current dir)
backup_project --set-version X.Y.Z [folder] Set the next version number (respects existing folders)
backup_project --force X.Y.Z [folder] Force exact version number (ignores existing folders, one-time)
backup_project --install Install script system-wide to /usr/local/bin
backup_project --help Show this help message
EXAMPLES:
backup_project # backs up current directory
backup_project ~/Documents/this-project # backs up specified folder
backup_project --set-version 2.0.15 # next backup will be 2.0.15 (or higher if folders exist)
backup_project --force 2.0.15 # next backup will be exactly 2.0.15
backup_project --force 2.0.15 ~/Documents/this-project
NOTES:
- On first run for any folder, you will be prompted for a starting version (e.g. 2.0.12)
- Each folder has its own config stored in ~/.config/backup_project/
- Backups are created in the same parent directory as the source folder
- To reset a folder's config: rm ~/.config/backup_project/<config>.cfg
- To view all configs: ls ~/.config/backup_project/
- To uninstall: sudo rm /usr/local/bin/backup_project
EOF
exit 0
fi
# ─── Self-install ─────────────────────────────────────────────────────────────
if [[ "$1" == "--install" ]]; then
echo "Installing backup_project to $INSTALL_PATH ..."
sudo cp "$0" "$INSTALL_PATH"
sudo chmod +x "$INSTALL_PATH"
echo "Done! You can now run 'backup_project [folder]' from anywhere."
exit 0
fi
# ─── Set version override ─────────────────────────────────────────────────────
# Usage: backup_project --set-version 2.0.15 [folder]
if [[ "$1" == "--set-version" ]]; then
NEW_VERSION="$2"
SOURCE="$(realpath "${3:-$(pwd)}")"
if ! echo "$NEW_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+
# If no argument given, use current directory
if [ -z "$1" ]; then
SOURCE="$(pwd)"
else
SOURCE="$1"
fi
# Strip trailing slash, resolve to absolute path
SOURCE="$(realpath "${SOURCE%/}")"
if [ ! -d "$SOURCE" ]; then
echo "Error: '$SOURCE' is not a directory."
exit 1
fi
# ─── Load or create config for this source folder ────────────────────────────
mkdir -p "$CONFIG_DIR"
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "First time backing up '$(basename "$SOURCE")'."
read -rp "Enter starting version (e.g. 2.0.12): " FULL_VERSION
# Validate format: must be X.Y.Z (digits and dots, at least one dot, ends in digits)
if ! echo "$FULL_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+$'; then
echo "Error: version must be in format like 2.0.12 or 1.0.0"
exit 1
fi
# Split into prefix (everything before last dot) and starting minor (last number)
BASE_VERSION="${FULL_VERSION%.*}" # e.g. 2.0
START_MINOR="${FULL_VERSION##*.}" # e.g. 12
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "Saved: prefix='$BASE_VERSION', starting minor='$START_MINOR' for '$(basename "$SOURCE")'."
else
source "$FOLDER_CONFIG"
fi
# ─── Find next version number ─────────────────────────────────────────────────
BASE_NAME=$(basename "$SOURCE")
PARENT_DIR=$(dirname "$SOURCE")
if [ -n "$FORCE_MINOR" ]; then
# --force: use exactly this number, ignore existing folders, then clear the force
NEXT="$FORCE_MINOR"
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$FORCE_MINOR" >> "$FOLDER_CONFIG"
echo "FORCE_MINOR=" >> "$FOLDER_CONFIG"
else
# Find highest existing minor version >= START_MINOR
LAST=$(ls -d "${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}".* 2>/dev/null \
| grep -oP '\d+
DEST="${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}.${NEXT}"
# ─── Copy ─────────────────────────────────────────────────────────────────────
cp -r "$SOURCE" "$DEST"
echo "✓ Backed up '$(basename "$SOURCE")' → '$DEST'"
; then
echo "Error: version must be in format like 2.0.15"
exit 1
fi
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "Error: no config found for '$SOURCE'. Run a backup first."
exit 1
fi
BASE_VERSION="${NEW_VERSION%.*}"
START_MINOR="${NEW_VERSION##*.}"
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "FORCE_MINOR=" >> "$FOLDER_CONFIG"
echo "✓ Next backup of '$(basename "$SOURCE")' will be '$NEW_VERSION'"
exit 0
fi
# ─── Force version override ───────────────────────────────────────────────────
# Usage: backup_project --force 2.0.15 [folder]
if [[ "$1" == "--force" ]]; then
NEW_VERSION="$2"
SOURCE="$(realpath "${3:-$(pwd)}")"
if ! echo "$NEW_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+
# ─── Resolve source folder ────────────────────────────────────────────────────
# If no argument given, use current directory
if [ -z "$1" ]; then
SOURCE="$(pwd)"
else
SOURCE="$1"
fi
# Strip trailing slash, resolve to absolute path
SOURCE="$(realpath "${SOURCE%/}")"
if [ ! -d "$SOURCE" ]; then
echo "Error: '$SOURCE' is not a directory."
exit 1
fi
# ─── Load or create config for this source folder ────────────────────────────
mkdir -p "$CONFIG_DIR"
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "First time backing up '$(basename "$SOURCE")'."
read -rp "Enter starting version (e.g. 2.0.12): " FULL_VERSION
# Validate format: must be X.Y.Z (digits and dots, at least one dot, ends in digits)
if ! echo "$FULL_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+$'; then
echo "Error: version must be in format like 2.0.12 or 1.0.0"
exit 1
fi
# Split into prefix (everything before last dot) and starting minor (last number)
BASE_VERSION="${FULL_VERSION%.*}" # e.g. 2.0
START_MINOR="${FULL_VERSION##*.}" # e.g. 12
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "Saved: prefix='$BASE_VERSION', starting minor='$START_MINOR' for '$(basename "$SOURCE")'."
else
source "$FOLDER_CONFIG"
fi
# ─── Find next version number ─────────────────────────────────────────────────
BASE_NAME=$(basename "$SOURCE")
PARENT_DIR=$(dirname "$SOURCE")
# Find highest existing minor version, but only consider numbers >= START_MINOR
LAST=$(ls -d "${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}".* 2>/dev/null \
| grep -oP '\d+$' \
| awk -v start="$START_MINOR" '$1 >= start' \
| sort -n \
| tail -1)
if [ -z "$LAST" ]; then
NEXT="$START_MINOR"
else
NEXT=$((LAST + 1))
fi
DEST="${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}.${NEXT}"
# ─── Copy ─────────────────────────────────────────────────────────────────────
cp -r "$SOURCE" "$DEST"
echo "✓ Backed up '$(basename "$SOURCE")' → '$DEST'"
; then
echo "Error: version must be in format like 2.0.15"
exit 1
fi
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "Error: no config found for '$SOURCE'. Run a backup first."
exit 1
fi
BASE_VERSION="${NEW_VERSION%.*}"
FORCE_MINOR="${NEW_VERSION##*.}"
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$FORCE_MINOR" >> "$FOLDER_CONFIG"
echo "FORCE_MINOR=$FORCE_MINOR" >> "$FOLDER_CONFIG"
echo "✓ Next backup of '$(basename "$SOURCE")' will be forced to '$NEW_VERSION' (ignoring existing folders)"
exit 0
fi
# ─── Resolve source folder ────────────────────────────────────────────────────
# If no argument given, use current directory
if [ -z "$1" ]; then
SOURCE="$(pwd)"
else
SOURCE="$1"
fi
# Strip trailing slash, resolve to absolute path
SOURCE="$(realpath "${SOURCE%/}")"
if [ ! -d "$SOURCE" ]; then
echo "Error: '$SOURCE' is not a directory."
exit 1
fi
# ─── Load or create config for this source folder ────────────────────────────
mkdir -p "$CONFIG_DIR"
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "First time backing up '$(basename "$SOURCE")'."
read -rp "Enter starting version (e.g. 2.0.12): " FULL_VERSION
# Validate format: must be X.Y.Z (digits and dots, at least one dot, ends in digits)
if ! echo "$FULL_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+$'; then
echo "Error: version must be in format like 2.0.12 or 1.0.0"
exit 1
fi
# Split into prefix (everything before last dot) and starting minor (last number)
BASE_VERSION="${FULL_VERSION%.*}" # e.g. 2.0
START_MINOR="${FULL_VERSION##*.}" # e.g. 12
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "Saved: prefix='$BASE_VERSION', starting minor='$START_MINOR' for '$(basename "$SOURCE")'."
else
source "$FOLDER_CONFIG"
fi
# ─── Find next version number ─────────────────────────────────────────────────
BASE_NAME=$(basename "$SOURCE")
PARENT_DIR=$(dirname "$SOURCE")
# Find highest existing minor version, but only consider numbers >= START_MINOR
LAST=$(ls -d "${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}".* 2>/dev/null \
| grep -oP '\d+$' \
| awk -v start="$START_MINOR" '$1 >= start' \
| sort -n \
| tail -1)
if [ -z "$LAST" ]; then
NEXT="$START_MINOR"
else
NEXT=$((LAST + 1))
fi
DEST="${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}.${NEXT}"
# ─── Copy ─────────────────────────────────────────────────────────────────────
cp -r "$SOURCE" "$DEST"
echo "✓ Backed up '$(basename "$SOURCE")' → '$DEST'"
\
| awk -v start="$START_MINOR" '$1 >= start' \
| sort -n \
| tail -1)
if [ -z "$LAST" ]; then
NEXT="$START_MINOR"
else
NEXT=$((LAST + 1))
fi
fi
DEST="${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}.${NEXT}"
# ─── Copy ─────────────────────────────────────────────────────────────────────
cp -r "$SOURCE" "$DEST"
echo "✓ Backed up '$(basename "$SOURCE")' → '$DEST'"
; then
echo "Error: version must be in format like 2.0.15"
exit 1
fi
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "Error: no config found for '$SOURCE'. Run a backup first."
exit 1
fi
BASE_VERSION="${NEW_VERSION%.*}"
START_MINOR="${NEW_VERSION##*.}"
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "FORCE_MINOR=" >> "$FOLDER_CONFIG"
echo "✓ Next backup of '$(basename "$SOURCE")' will be '$NEW_VERSION'"
exit 0
fi
# ─── Force version override ───────────────────────────────────────────────────
# Usage: backup_project --force 2.0.15 [folder]
if [[ "$1" == "--force" ]]; then
NEW_VERSION="$2"
SOURCE="$(realpath "${3:-$(pwd)}")"
if ! echo "$NEW_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+
# ─── Resolve source folder ────────────────────────────────────────────────────
# If no argument given, use current directory
if [ -z "$1" ]; then
SOURCE="$(pwd)"
else
SOURCE="$1"
fi
# Strip trailing slash, resolve to absolute path
SOURCE="$(realpath "${SOURCE%/}")"
if [ ! -d "$SOURCE" ]; then
echo "Error: '$SOURCE' is not a directory."
exit 1
fi
# ─── Load or create config for this source folder ────────────────────────────
mkdir -p "$CONFIG_DIR"
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "First time backing up '$(basename "$SOURCE")'."
read -rp "Enter starting version (e.g. 2.0.12): " FULL_VERSION
# Validate format: must be X.Y.Z (digits and dots, at least one dot, ends in digits)
if ! echo "$FULL_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+$'; then
echo "Error: version must be in format like 2.0.12 or 1.0.0"
exit 1
fi
# Split into prefix (everything before last dot) and starting minor (last number)
BASE_VERSION="${FULL_VERSION%.*}" # e.g. 2.0
START_MINOR="${FULL_VERSION##*.}" # e.g. 12
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "Saved: prefix='$BASE_VERSION', starting minor='$START_MINOR' for '$(basename "$SOURCE")'."
else
source "$FOLDER_CONFIG"
fi
# ─── Find next version number ─────────────────────────────────────────────────
BASE_NAME=$(basename "$SOURCE")
PARENT_DIR=$(dirname "$SOURCE")
# Find highest existing minor version, but only consider numbers >= START_MINOR
LAST=$(ls -d "${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}".* 2>/dev/null \
| grep -oP '\d+$' \
| awk -v start="$START_MINOR" '$1 >= start' \
| sort -n \
| tail -1)
if [ -z "$LAST" ]; then
NEXT="$START_MINOR"
else
NEXT=$((LAST + 1))
fi
DEST="${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}.${NEXT}"
# ─── Copy ─────────────────────────────────────────────────────────────────────
cp -r "$SOURCE" "$DEST"
echo "✓ Backed up '$(basename "$SOURCE")' → '$DEST'"
; then
echo "Error: version must be in format like 2.0.15"
exit 1
fi
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "Error: no config found for '$SOURCE'. Run a backup first."
exit 1
fi
BASE_VERSION="${NEW_VERSION%.*}"
FORCE_MINOR="${NEW_VERSION##*.}"
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$FORCE_MINOR" >> "$FOLDER_CONFIG"
echo "FORCE_MINOR=$FORCE_MINOR" >> "$FOLDER_CONFIG"
echo "✓ Next backup of '$(basename "$SOURCE")' will be forced to '$NEW_VERSION' (ignoring existing folders)"
exit 0
fi
# ─── Resolve source folder ────────────────────────────────────────────────────
# If no argument given, use current directory
if [ -z "$1" ]; then
SOURCE="$(pwd)"
else
SOURCE="$1"
fi
# Strip trailing slash, resolve to absolute path
SOURCE="$(realpath "${SOURCE%/}")"
if [ ! -d "$SOURCE" ]; then
echo "Error: '$SOURCE' is not a directory."
exit 1
fi
# ─── Load or create config for this source folder ────────────────────────────
mkdir -p "$CONFIG_DIR"
CONFIG_KEY=$(echo "$SOURCE" | tr '/' '_' | tr ' ' '_')
FOLDER_CONFIG="$CONFIG_DIR/${CONFIG_KEY}.cfg"
if [ ! -f "$FOLDER_CONFIG" ]; then
echo "First time backing up '$(basename "$SOURCE")'."
read -rp "Enter starting version (e.g. 2.0.12): " FULL_VERSION
# Validate format: must be X.Y.Z (digits and dots, at least one dot, ends in digits)
if ! echo "$FULL_VERSION" | grep -qP '^\d+(\.\d+)*\.\d+$'; then
echo "Error: version must be in format like 2.0.12 or 1.0.0"
exit 1
fi
# Split into prefix (everything before last dot) and starting minor (last number)
BASE_VERSION="${FULL_VERSION%.*}" # e.g. 2.0
START_MINOR="${FULL_VERSION##*.}" # e.g. 12
echo "BASE_VERSION=$BASE_VERSION" > "$FOLDER_CONFIG"
echo "START_MINOR=$START_MINOR" >> "$FOLDER_CONFIG"
echo "Saved: prefix='$BASE_VERSION', starting minor='$START_MINOR' for '$(basename "$SOURCE")'."
else
source "$FOLDER_CONFIG"
fi
# ─── Find next version number ─────────────────────────────────────────────────
BASE_NAME=$(basename "$SOURCE")
PARENT_DIR=$(dirname "$SOURCE")
# Find highest existing minor version, but only consider numbers >= START_MINOR
LAST=$(ls -d "${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}".* 2>/dev/null \
| grep -oP '\d+$' \
| awk -v start="$START_MINOR" '$1 >= start' \
| sort -n \
| tail -1)
if [ -z "$LAST" ]; then
NEXT="$START_MINOR"
else
NEXT=$((LAST + 1))
fi
DEST="${PARENT_DIR}/${BASE_NAME}-${BASE_VERSION}.${NEXT}"
# ─── Copy ─────────────────────────────────────────────────────────────────────
cp -r "$SOURCE" "$DEST"
echo "✓ Backed up '$(basename "$SOURCE")' → '$DEST'"