diff --git a/backup_script.sh b/backup_script.sh new file mode 100644 index 0000000..da4d6aa --- /dev/null +++ b/backup_script.sh @@ -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 <.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'"