Skip to content

Instantly share code, notes, and snippets.

@magenbrot
Created August 7, 2025 09:12
Show Gist options
  • Select an option

  • Save magenbrot/08ca4b49865374ce9a8deabc0057bf0d to your computer and use it in GitHub Desktop.

Select an option

Save magenbrot/08ca4b49865374ce9a8deabc0057bf0d to your computer and use it in GitHub Desktop.

Revisions

  1. magenbrot created this gist Aug 7, 2025.
    160 changes: 160 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,160 @@
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-

    """
    This script cleans up a specified directory by deleting:
    1. Files older than one year.
    2. Empty subdirectories.
    Home directories of system users are explicitly excluded from deletion
    if they are empty, but their empty subdirectories will be removed.
    A dry-run mode is available via the --check flag to display potential
    deletions without actually executing them.
    """

    import os
    import sys
    import time
    import argparse
    import pwd
    from pathlib import Path
    from datetime import datetime

    # One year in seconds for time comparison
    ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60

    def get_user_home_dirs():
    """
    Retrieves the home directories of all users on the system.
    This is done by reading the password database. This list is used
    to prevent the deletion of empty home directories.
    Returns:
    set: A set containing the absolute paths of the home directories.
    """
    home_dirs = set()
    try:
    for user in pwd.getpwall():
    # Only add valid and existing home directories
    if user.pw_dir and os.path.isdir(user.pw_dir):
    home_dirs.add(os.path.realpath(user.pw_dir))
    except (ImportError, KeyError):
    print("Warning: The 'pwd' module is not available. "
    "Cannot protect user home directories.", file=sys.stderr)
    return home_dirs

    def delete_old_files(directory: Path, check_mode: bool):
    """
    Deletes all files in the specified directory that are older than one year.
    Args:
    directory (Path): The target directory to be searched.
    check_mode (bool): If True, actions are only simulated (dry run).
    """
    print("\n--- Searching for files older than one year ---")
    now = time.time()
    prefix = "[DRY RUN] Would delete: " if check_mode else "[DELETING] "

    for dirpath, _, filenames in os.walk(directory):
    for filename in filenames:
    file_path = Path(dirpath) / filename
    try:
    # Skip symbolic links to avoid issues
    if file_path.is_symlink():
    continue

    file_stat = file_path.stat()
    file_mod_time = file_stat.st_mtime
    if now - file_mod_time > ONE_YEAR_IN_SECONDS:
    # Format timestamp for human-readable output
    timestamp_str = datetime.fromtimestamp(file_mod_time).strftime('%Y-%m-%d %H:%M:%S')
    print(f"{prefix}{file_path} (Last modified: {timestamp_str})")
    if not check_mode:
    os.remove(file_path)
    except FileNotFoundError:
    # File might have been deleted already by another process
    continue
    except OSError as e:
    print(f"Error deleting {file_path}: {e}", file=sys.stderr)

    def delete_empty_folders(directory: Path, check_mode: bool):
    """
    Deletes all empty folders, excluding user home directories.
    Args:
    directory (Path): The target directory to be cleaned.
    check_mode (bool): If True, actions are only simulated (dry run).
    """
    print("\n--- Searching for empty folders ---")
    home_dirs = get_user_home_dirs()
    prefix = "[DRY RUN] Would delete empty folder: " if check_mode else "[DELETING] Empty folder: "

    # Use os.walk with topdown=False to process subdirectories before their parents
    for dirpath, _, _ in os.walk(directory, topdown=False):
    dir_path_obj = Path(dirpath)
    real_dir_path = os.path.realpath(dir_path_obj)

    try:
    # Check if the directory is empty
    if not os.listdir(dir_path_obj):
    # Check if it is a protected home directory
    if real_dir_path in home_dirs:
    print(f"[INFO] Skipping empty home directory: {dir_path_obj}")
    continue

    # Format timestamp for human-readable output
    dir_mod_time = dir_path_obj.stat().st_mtime
    timestamp_str = datetime.fromtimestamp(dir_mod_time).strftime('%Y-%m-%d %H:%M:%S')
    print(f"{prefix}{dir_path_obj} (Last modified: {timestamp_str})")
    if not check_mode:
    os.rmdir(dir_path_obj)
    except FileNotFoundError:
    # Directory might have been deleted already by another process
    continue
    except OSError as e:
    print(f"Error deleting {dir_path_obj}: {e}", file=sys.stderr)


    def main():
    """
    Main function to parse arguments and start the cleanup process.
    """
    parser = argparse.ArgumentParser(
    description="Cleans a directory of old files and empty folders.",
    epilog="Example: python3 cleanup_folders.py /srv --check"
    )
    parser.add_argument(
    "directory",
    type=Path,
    help="The target directory to clean (e.g., /srv)."
    )
    parser.add_argument(
    "--check",
    action="store_true",
    help="Perform a dry run without deleting any files or folders."
    )

    args = parser.parse_args()
    target_dir = args.directory
    is_check_mode = args.check

    if not target_dir.is_dir():
    print(f"Error: The specified directory '{target_dir}' does not exist.",
    file=sys.stderr)
    sys.exit(1)

    if is_check_mode:
    print("*** DRY RUN MODE ACTIVE: No changes will be made. ***")
    else:
    print("--- WARNING: The script will now perform actual deletions. ---")
    time.sleep(3) # A short pause for safety

    delete_old_files(target_dir, is_check_mode)
    delete_empty_folders(target_dir, is_check_mode)

    print("\nCleanup complete.")

    if __name__ == "__main__":
    main()