12 Apr Enterprise Hazel
Overview
“Hazel” is an American sitcom about a live-in maid named Hazel Burke and her employers. The theme of this blog post, could have been “Alfred the Butler” or a taken to a more dark direction with “Victor the Cleaner“, but I decided to keep things a little light hearted and fun. Many times in our lives we probably wished we could have someone else cleanup after us either being a maid, butler or for those more devious items requiring a hitman or hitwomen.
On our Mac fleet, we had different situations that would require cleanup of files or folders and wanted to move away from a manual reactive process based on user requests or problem tickets to an automated and repeatable proactive process that allowed us to implement based on the scope requirements. We felt that overall this would provide users a better user experience & support and require less time long term from our IT group. This defines being proactive vs reactive, where proactive refers to being prepared even before an incident takes place. Where reactive, could be defined as repeatively being responsive to something or issue.
Cleanup Manager
To aid in the automated cleanup of Mac system files and folders we created a python script called “Cleanup Manager” that is publicly available in our GitHub repository. It provides multiple options on how and when to perform cleanup like age, amount of free space, deletion precedence like oldest vs largest, etc. Cleanup Manager can be implemented with any management solution, and could be run on startup, login, logout or whatever meets your Mac environment or specific deployment scenario requirements. If your environment isn’t using Jamf Pro, you could easily use a similar methodology with tools like Outset or custom launch item to run based on your environment scope needs.
In our environment, we use Jamf Pro and have integrated the “Cleanup Manager” using policies and scoping based on the individual or group systems requirements.
#!/bin/sh # Check to make sure the script is installed if [[ -e /usr/local/bin/cleanup_manager.py ]] ; then # If paramaters not mentioned, they will stay empty target="" after="" freeup="" oldest="" largest="" if [[ -n "$4" ]] ; then target="$4" fi if [[ -n "$5" ]] ; then after="--keep-after $5" fi if [[ -n "$6" ]] ; then freeup="--freeup $6" fi if [[ -n "$7" ]] ; then oldest="--delete-oldest-first $7" fi if [[ -n "$8" ]] ; then largest="--delete-largest-first $8" fi /usr/local/bin/cleanup_manager.py --overflow $after $freeup $oldest $largest $target else exit 1 fi
Scenarios
Below our some examples of scenarios where we use Cleanup Manager in our environment, based some criteria like target age or free disk space.
Student Checkout Laptops
Our student check out project has been very popular and successful. We currently have (128) Mac laptops, (44) PC laptops, and (6) ChromeBooks. On the Mac laptop systems, we use 802.1x, Radius and Active Directory to create a mobile account once the student logins on campus. Mobile accounts are essentially a combination of both local and network accounts, it allows student to take the laptops off-campus, but can still login with their campus Active Director credentials.
Migrating to NoMAD
We are in the process of migrating to NoMAD where users will be local accounts and allow it to manage the interaction with campus Active Directory by allowing them to sign in with their student accounts to get Kerberos tickets, certificates for 802.1X connections and other functions without having to have a mobile account which can sometimes be problematic. We are planning on integrating NoMAD with our campus portal that will allow password modifications via web site, but sync password changes to the Mac client system.
For more details on 802.1X, see our blog post called “Device Based Internet Access” which outlines how to setup NDES, SCEP and Jamf Pro with device based EAP-TLS certificate infrastructure.
We use Cleanup Manager at startup & logout to remove old user folders after 7 days, and make sure there is a minimum of 50 GB free disk space.
High-End Audio & Video Systems
We have two rooms, with professional acoustics & functionality with a high-end audio and video Mac systems attached to large & fast storage. We allow students to work on project for at most 1 month and then after using cleanup manager to remove old users folders and make sure there is at least 50 GB free disk space.
Lost & Found
In our student labs, we provide a “Lost & Found” functionality that allows students to re-log back into a Mac system to recover files saved locally but not to cloud storage options or portable media. At logout, we backup data the students may have saved locally to the “Documents” or “Desktop” folders to a location that is made available on the “Desktop” folder on re-login by the same student. We can use cleanup manager to remove the “Lost & Found” folder older than 7 days and make sure there is at least 50 GB free disk space.For example, here is an old perl script that we use to backup students data on logout that is only accessible by the same student using their university credentials:
#!/usr/bin/perl ################################################################################ # LOL_1_lost_and_found.pl # # The purpose of this script is to cleanup the last user home folder (/Users/<name>). # # Copyright (c) 2002 University of Utah Student Computing Labs. # All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby granted, # provided that the above copyright notice appears in all copies and # that both that copyright notice and this permission notice appear # in supporting documentation, and that the name of The University # of Utah not be used in advertising or publicity pertaining to # distribution of the software without specific, written prior # permission. This software is supplied as is without expressed or # implied warranties of any kind. # ################################################################################ ################################################################################ # This script deals with the old home folder (ex. /Users/guest). # On kiosks, the home folder is deleted. On other computers the old # home folder (ex /Users/guest) is moved to the "lost and found". The owner of # the old home folder is found from the file ~/.username, which is created by # this script (search for $login_name_trigger below). The lost and found folder # is named after the owner (ex. # /Library/Xhooks/Preferences/Caches/edu.utah.scl.home_folder/lost_and_found/joe1234). # And, it does this really bad cleanup job on the folder that was # just moved to /Library/Xhooks/Preferences/Caches/edu.utah.scl.home_folder/lost_and_found # (this code needs to be improved, and there is untested code already to do this, # but we haven't had time to test it--and I think I lost the code... it was years ago). ################################################################################ use lib '/usr/local/lib'; use Xhooks::Common; require '/Library/Xhooks/Preferences/includes/xhooks_vars.perl'; use File::Find; use Carp; my $global_home_folder_lost_and_found = "/Library/Xhooks/Preferences/Caches/edu.utah.scl.home_folder/lost_and_found"; my $template_homefolder_files = "/Library/Xhooks/Preferences/Caches/edu.utah.scl.home_folder/home_folder_files"; my $homefolder_lostandfound = "Desktop/Lost & Found"; my $login_name_trigger = ".username"; my $login_hook_running = "/Library/Xhooks/Preferences/triggerfiles/login_hook_running"; my $startup_early_running = "/Library/Xhooks/Preferences/triggerfiles/startup_early_running"; our $debug ||= 0; #$lostAndFoundDays = "14"; # Number of days items will be saved in a lost and found - NOT IMPLEMENTED YET!!! ################################################################################ # GO: my $rightnow = time; my $homefolder; if ( nonempty_var( $CREATE_LOST_AND_FOUND_DIR ) ) { $homefolder = $CREATE_LOST_AND_FOUND_DIR; } else { confess( "ERROR: CREATE_LOST_AND_FOUND_DIR and ARGV[0] not defined." ) if ! defined $ARGV[0]; confess( "ERROR: CREATE_LOST_AND_FOUND_DIR and CREATE_LOST_AND_FOUND_DIR_GREP not defined." ) if empty_array( @CREATE_LOST_AND_FOUND_DIR_GREP ); my $theuser = $ARGV[0]; exit 0 if $theuser eq $ADMIN_USER_NAME; my @user_record = getpwnam( $theuser ); my $system_homefolder = $user_record[7]; print "$system_homefolder @user_record $theuser\n" if $debug; confess( "ERROR: system_homefolder is empty! (getpwnam didn't return a home dir for user: $theuser)." ) if empty_var( $system_homefolder ); foreach my $dir_grep ( @CREATE_LOST_AND_FOUND_DIR_GREP ) { if ( $system_homefolder =~ /$dir_grep/ and -e $system_homefolder ) { $homefolder = $system_homefolder; last; } } exit 0 if ! defined $homefolder; } confess( "ERROR: \"$homefolder\" does not exist!" ) if ! -e $homefolder; print "$homefolder\n" if $debug; # Get the username of the last user if ( -e "$homefolder/$login_name_trigger" ) { print "$homefolder/$login_name_trigger exists\n" if $debug; # Create the lost and found repository if needed if ( ! -e $global_home_folder_lost_and_found ) { print "mkdir \"$global_home_folder_lost_and_found\"\n" if $debug; system "mkdir \"$global_home_folder_lost_and_found\""; print "/usr/sbin/chown 0:0 \"$global_home_folder_lost_and_found\"\n" if $debug; system "/usr/sbin/chown 0:0 \"$global_home_folder_lost_and_found\""; } $lastUsername = readOneLineFile( "$homefolder/$login_name_trigger" ); # ex "abc1234" (typical university id... no?) # Create the hidden lost and found folder for this user if ( ! -e "$global_home_folder_lost_and_found/$lastUsername" ) { print "mkdir \"$global_home_folder_lost_and_found/$lastUsername\"\n" if $debug; system "mkdir \"$global_home_folder_lost_and_found/$lastUsername\""; print "/usr/sbin/chown -R $lastUsername \"$global_home_folder_lost_and_found/$lastUsername\"\n" if $debug; system "/usr/sbin/chown -R $lastUsername \"$global_home_folder_lost_and_found/$lastUsername\""; } my $newpath; if ( -e "$homefolder/.keep_home_folder_intact" ) { $newpath = "$global_home_folder_lost_and_found/$lastUsername/use_this_folder_next_time"; } else { # Keep a lost and found $lastLoginTimeStamp = getTimeStamp( getFileModTime( "$homefolder/$login_name_trigger" ) ); # /Users/student/.username $newpath = "$global_home_folder_lost_and_found/$lastUsername/$lastLoginTimeStamp"; } print "$newpath\n" if $debug; # create location for lost and found # # Note, in 10.5 (and above probably), the system keeps track of all the dirs, so instead of moving the contents # of the home folder, we got to move the content of the home dirs. That is why there is so much crapping code below. # handle_stupid_homedir_path ( "$newpath/Desktop", "$homefolder/Desktop", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Documents", "$homefolder/Documents", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Downloads", "$homefolder/Downloads", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Library", "$homefolder/Library", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Movies", "$homefolder/Movies", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Music", "$homefolder/Music", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Pictures", "$homefolder/Pictures", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Public", "$homefolder/Public", $lastUsername ); handle_stupid_homedir_path ( "$newpath/Sites", "$homefolder/Sites", $lastUsername ); handle_stupid_homedir_path ( "$newpath", "$homefolder", $lastUsername ); if ( ! -e "$homefolder/.keep_home_folder_intact" and ! -e "$newpath/.dontcleanup" ) { # Cleanup old home folder cleanup_old_home_folder ( "$global_home_folder_lost_and_found/$lastUsername", $lastLoginTimeStamp ); } } elsif ( $debug ) { print "Does not exist: $homefolder/$login_name_trigger.\n"; } $totaltime = time - $rightnow; print "Done. Took $totaltime."; exit 0; sub handle_stupid_homedir_path { my ( $newdir, $olddir, $lastUsername ) = @_; if ( -d $olddir ) { print "/bin/mkdir -p \"$newdir\"\n" if $debug; system "/bin/mkdir", "-p", "$newdir";# or print "Couldn't create $newdir $!\n"; # Move the contents of the old folder to the hidden lost and found print "/bin/mv \"$olddir\"/* \"$newdir\" 2>&1\n" if $debug; system "/bin/mv \"$olddir\"/* \"$newdir\" 2>&1"; # Move the contents of the old folder to the hidden lost and found print "/bin/mv \"$olddir\"/.* \"$newdir\"\n" if $debug; system "/bin/mv \"$olddir\"/.* \"$newdir\""; # Chown new system "/usr/sbin/chown", $lastUsername, $newdir; # Remove old system "/bin/rmdir", $olddir; } else { print "The dir $olddir is missing and well that defeats the whole point of handling it......\n"; } } sub delete_folder { my ( $my_folder ) = @_; print "chflags -R nouchg \"$my_folder\"\n" if $debug; system "chflags -R nouchg \"$my_folder\""; print "rm -rf -- \"$my_folder\"\n" if $debug; system "rm -rf -- \"$my_folder\""; } #################################### # STEP 4 - DEAL WITH LOST AND FOUND # If this doesn't work, it is probably because you are using special characters # (other than " " or "&", which this script will fix). # # THIS SUBROUTINE NEEDS SERIOUS FIXING UP # sub cleanup_old_home_folder { my ( $user_lost_and_found, $my_folder ) = @_; my $path_to_home_folder = "$user_lost_and_found/$my_folder"; #################################### # Move any old timestamped Lost and Found folders out of Desktop/Lost & Found up a level print "do i exist $path_to_home_folder/$homefolder_lostandfound\n" if $debug; if ( -e "$path_to_home_folder/$homefolder_lostandfound" ) { # "~/Desktop/Lost & Found"; # mv ~/Desktop/Lost\ \&\ Found/* ~/..; @dirs = getFileFolderList( "$path_to_home_folder/$homefolder_lostandfound/" ); foreach my $ii ( @dirs ) { print "/bin/mv \"$path_to_home_folder/$homefolder_lostandfound/$ii\" \"$user_lost_and_found/\"\n" if $debug; system "/bin/mv \"$path_to_home_folder/$homefolder_lostandfound/$ii\" \"$user_lost_and_found/\""; } system "/bin/rm -rf \"$path_to_home_folder/$homefolder_lostandfound\""; # rm -rf ~/Desktop/Lost\ \&\ Found } #################################### # CLEAN UP THE OLD HOME FOLDER # Delete Known throw away folders #if ( not test box!! ) { @home_folder_auto_delete_these_folders = ("Library", "Shared/Library", "SorensonMedia/Squeeze", ); foreach $auto_detele ( @home_folder_auto_delete_these_folders ) { put_in_trash( "$path_to_home_folder/$auto_detele", 1 ); } #} #################################### # Everything else if ( chdir $path_to_home_folder ) { if ( open FILE, "< $template_homefolder_files" ) { while ( <FILE> ) { my $line = $_; my @parts = split / /, $line; my $path = unescape_radmind_path ( $parts[0] ); my $test = 1; my $index; foreach $auto_detele ( @home_folder_auto_delete_these_folders ) { $index = index $path, $auto_detele; if ( $index > 0 and $index < 3 ) { $test = 0; last; } } if ( $test and -e $path ) { if ( ! unlink $path ) { system "/usr/bin/chflags -R nouchg \"$path\""; unlink $path or entman_logger ("log", "Couldn't unlink $path\n");# if $debug; } } } close FILE; } else { entman_logger ("log", "Can't open $template_homefolder_files: $!"); return; } # Remove Finder created files (.DS_Store, .localized) # -type l -or system "/usr/bin/find ./ \\( -name \".DS_Store\" -or -name \".localized\" \\) -delete"; # Remove empty directories finddepth(sub{rmdir},'.'); # cleanup Trash, make visible if stuff is in it if ( -e "$path_to_home_folder/.Trash" ) { system "/bin/mv \"$path_to_home_folder/.Trash\" \"$path_to_home_folder/Trash\""; system "/usr/local/bin/SetFile", "-a", "v", "$path_to_home_folder/Trash"; } # cleanup root folder system "/bin/rm -f \"$path_to_home_folder/$login_name_trigger\""; if ( findNumberOfFoldersAndFiles( "$path_to_home_folder" ) < 1 ) { system "/bin/rm -rf \"$path_to_home_folder\""; } # If there is nothing in the lost and found, well, just delete the whole thing. if ( findNumberOfFoldersAndFiles( "$user_lost_and_found" ) < 1 ) { system "/bin/rm -rf \"$user_lost_and_found\""; } } else { entman_logger( "log", "Couldn't chdir $path_to_home_folder: $!"); } }
Pingback:Marriott Library - Apple ITS | 2019 JNUC - Presentation Resources & Links
Posted at 19:54h, 03 December[…] Enterprise Hazel This is a blog post given an in-depth overview and our usage of the Cleanup Manager python script. […]