--- /dev/null
+# Actually run the thing.
+
+require 'src/janitor'
+require 'optparse'
+
+# Set up the default options
+options = { :apache_vhost_directory => '/etc/apache2/vhosts.d',
+ :maximum_file_age => 7 }
+
+# And parse any that were given on the command line.
+OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [options]"
+
+ opts.on('-a', '--apache_vhosts_directory VHOSTS_DIR',
+ 'Apache vhost conf file directory') do |vhost_dir|
+ options[:apache_vhost_directory] = vhost_dir
+ end
+
+ opts.on('-m', '--maximum_file_age MAX_DAYS_OLD', Integer,
+ 'Maximum file age (in days)') do |max_age|
+ options[:maximum_file_age] = max_age
+ end
+
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+
+end.parse!
+
+
+# Print an informational header every time the program is run.
+puts "Command line: #{$0 + ' ' + ARGV.join(' ')}"
+
+current_time = Time.now()
+if current_time.respond_to?(:iso8601)
+ # Somehow this method is missing on some machines.
+ puts "Time: #{current_time.iso8601}"
+else
+ # Fall back to whatever this looks like.
+ puts "Time: #{current_time}"
+end
+
+puts "Apache vhost directory: #{options[:apache_vhost_directory]}"
+puts "Max File Age: #{options[:maximum_file_age]}"
+
+j = Janitor.new()
+j.apache_vhosts_directory = options[:apache_vhost_directory]
+
+temp_dirs = j.get_temporary_directories()
+
+puts "Temporary directories: #{temp_dirs.join(', ')}\n\n"
+
+temp_dirs.each do |dir|
+ j.clean_directory(dir, options[:maximum_file_age])
+end
--- /dev/null
+.PHONY : test
+
+test:
+ ruby test/test_suite.rb
--- /dev/null
+class ApacheConfFile
+
+ def initialize(conf_file_path)
+ if conf_file_path.nil? or not File.file?(conf_file_path)
+ raise(ArgumentError.new('How about passing in a real file?'))
+ end
+
+ # Read each line of the conf file into an array.
+ # Apache config directives are all one-line, right?
+ @file_lines = []
+
+ File::open(conf_file_path) do |f|
+ @file_lines = f.readlines()
+ end
+
+ if @file_lines.length < 1
+ raise(ArgumentError.new('Your file sucks.'))
+ end
+ end
+
+
+
+ def get_php_admin_values(directive_name)
+ # First, we have to regex-ize the directive name,
+ # since they can contain periods (at the least!).
+ directive_name.sub!('.', '\.')
+
+ # Our return variable, an array of matching directive values.
+ values = []
+
+ # Loop through each line in the conf file looking a
+ # matching pattern.
+ @file_lines.each do |line|
+ matches = line.match(/php_admin_value[[:space:]]+#{directive_name}[[:space:]](.*)$/)
+
+ if not matches.nil?
+ # If there's a match, there should be only one
+ # (in addition to [0], the entire matched string).
+ if matches.length > 2
+ raise(StandardError.new('Matched the Regex more than once?'))
+ end
+
+ # If there's only one, add it to our list.
+ # These are probably likely to have some extra
+ # whitespace around them one way or another.
+ values << matches[1].strip
+ end
+ end
+
+ return values
+ end
+
+
+ def get_php_temporary_directory_list(remove_duplicates = true)
+ # Return a list of all PHP "temporary" directories.
+ # I.e. upload_tmp_dir, session.save_path
+ temp_dirs = []
+ temp_dirs += self.get_php_admin_values('upload_tmp_dir')
+ temp_dirs += self.get_php_admin_values('session.save_path')
+
+ # We expect these to have been strip()ed already
+ if remove_duplicates
+ return temp_dirs.uniq
+ else
+ return temp_dirs
+ end
+ end
+
+end
--- /dev/null
+require('src/apache_conf_file')
+
+class Janitor
+
+ attr_accessor :apache_vhosts_directory
+
+ SECONDS_PER_MINUTE = 60
+ MINUTES_PER_HOUR = 60
+ HOURS_PER_DAY = 24
+ SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY
+
+
+ def initialize()
+ @apache_vhosts_directory = nil
+ end
+
+
+ protected;
+
+ def seconds_to_whole_days(seconds)
+ # Given some number of seconds, computer how many
+ # entire days are contained in that number of seconds.
+ # Fractions of days don't count!
+
+ if (seconds < 0)
+ raise(ArgumentException.new('Negative seconds?'))
+ end
+
+ return (seconds / SECONDS_PER_DAY).floor
+ end
+
+ public;
+
+ # Get a list of all the temporary directories that
+ # we'd like to clean out.
+ def get_temporary_directories(remove_duplicates = true)
+ temp_dirs = []
+
+ # Get the PHP temp dirs if we've been given a vhosts path
+ if !@apache_vhosts_directory.nil? && File.directory?(@apache_vhosts_directory)
+ Dir.glob(@apache_vhosts_directory + '/*.conf').each do |conf_file|
+ acf = ApacheConfFile.new(conf_file)
+
+ # Don't remove the duplicates here since we have
+ # to do it afterwards anyway.
+ temp_dirs += acf.get_php_temporary_directory_list(false)
+ end
+ end
+
+ # We expect these to have been strip()ed already
+ if remove_duplicates
+ return temp_dirs.uniq
+ else
+ return temp_dirs
+ end
+ end
+
+
+ def clean_directory(directory, max_age, file_pattern = '*')
+ glob_pattern = File.join(directory, file_pattern)
+
+ Dir.glob(glob_pattern).each do |f|
+ # Obviously, we should bail if it's not a file.
+ next if not File.file?(f)
+
+ # Calculate how old it is
+ days_old = seconds_to_whole_days(Time.now - File.mtime(f))
+
+ # And then delete the file (with a notice) if it's too old.
+ if days_old > max_age
+ puts "Deleting file: #{f} (#{days_old} days old)"
+ File.delete(f)
+ end
+ end
+
+ end
+end
--- /dev/null
+require('src/apache_conf_file')
+require('test/unit')
+
+class ApacheConfFileTest < Test::Unit::TestCase
+
+ def test_bad_conf_file_path_raises_error
+ assert_raise(ArgumentError) do
+ acf = ApacheConfFile.new(nil)
+ end
+
+ assert_raise(ArgumentError) do
+ acf = ApacheConfFile.new('/does/not/exist')
+ end
+
+ assert_raise(ArgumentError) do
+ # This raises a "different" error than the one above,
+ # but quit giving me empty files!
+ acf = ApacheConfFile.new('test/fixtures/empty_file.txt')
+ end
+ end
+
+
+ def test_good_conf_file_loads
+ acf = ApacheConfFile.new('test/fixtures/example.com-boring.conf')
+ # That's all..
+ end
+
+
+ def test_conf_file_with_no_directives
+ acf = ApacheConfFile.new('test/fixtures/example.com-boring.conf')
+ temp_files = acf.get_php_temporary_directory_list()
+ assert_kind_of(Array, temp_files)
+ assert_equal(0, temp_files.length)
+ end
+
+
+ def test_get_php_temporary_dirs_non_unique
+ acf = ApacheConfFile.new('test/fixtures/example.com-typical.conf')
+ # There should be two (identical) directories.
+ temp_files = acf.get_php_temporary_directory_list(false)
+ assert_equal(2, temp_files.length)
+ assert_equal(temp_files[0], temp_files[1])
+ end
+
+
+ def test_get_php_temporary_dirs_unique
+ acf = ApacheConfFile.new('test/fixtures/example.com-typical.conf')
+ # There should be just one directory, since the
+ # upload_tmp and session.save paths are identical.
+ temp_files = acf.get_php_temporary_directory_list()
+ assert_equal(1, temp_files.length)
+ assert_not_equal(0, temp_files[0].length)
+ end
+
+
+ def test_get_php_temporary_dirs_different_unique
+ acf = ApacheConfFile.new('test/fixtures/example.com-different.conf')
+ temp_files = acf.get_php_temporary_directory_list()
+ assert_equal(2, temp_files.length)
+ assert_not_equal(temp_files[0], temp_files[1])
+ end
+
+
+ def test_get_php_temporary_dirs_multi_unique
+ # Two different vhosts (one SSL), both configured
+ # with the same two (different) tmp directories.
+ acf = ApacheConfFile.new('test/fixtures/example.com-multi.conf')
+ temp_files = acf.get_php_temporary_directory_list()
+ assert_equal(2, temp_files.length)
+ assert_not_equal(temp_files[0], temp_files[1])
+ end
+
+
+ def test_get_php_temporary_dirs_multi_non_unique
+ # Two different vhosts (one SSL), both configured
+ # with the same two (different) tmp directories.
+ acf = ApacheConfFile.new('test/fixtures/example.com-multi.conf')
+ temp_files = acf.get_php_temporary_directory_list(false)
+ assert_equal(4, temp_files.length)
+ end
+
+end
--- /dev/null
+<VirtualHost 127.0.0.1:80>
+ ServerName www.example.com
+ ServerAlias example.com
+ DocumentRoot "/var/www/example.com/www/public"
+</VirtualHost>
--- /dev/null
+<VirtualHost 127.0.0.1:80>
+ ServerName www.example.com
+ ServerAlias example.com
+ DocumentRoot "/var/www/example.com/www/public"
+
+ php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+ php_admin_value upload_tmp_dir /tmp
+ php_admin_value session.save_path /var/www/example.com/www/tmp
+
+ LogLevel warn
+ ErrorLog /var/log/apache2/example.com/www/error.log
+ CustomLog /var/log/apache2/example.com/www/access.log combined
+
+ <Directory "/var/www/example.com/www/public">
+ Options IncludesNoExec
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+ </Directory>
+
+ DirectoryIndex default.shtml
+
+ Include /etc/apache2/vhosts.d/include/php.conf
+
+ DirectoryIndex default.php index.php
+
+</VirtualHost>
--- /dev/null
+<VirtualHost 127.0.0.1:80>
+ ServerName www.example.com
+ ServerAlias example.com
+ DocumentRoot "/var/www/example.com/www/public"
+
+ php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+ php_admin_value upload_tmp_dir /tmp
+ php_admin_value session.save_path /var/www/example.com/www/tmp
+
+ LogLevel warn
+ ErrorLog /var/log/apache2/example.com/www/error.log
+ CustomLog /var/log/apache2/example.com/www/access.log combined
+
+ <Directory "/var/www/example.com/www/public">
+ Options IncludesNoExec
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+ </Directory>
+
+ DirectoryIndex default.shtml
+
+ Include /etc/apache2/vhosts.d/include/php.conf
+
+ DirectoryIndex default.php index.php
+
+</VirtualHost>
+
+
+<VirtualHost 127.0.0.1:443>
+ ServerName secure.example.com
+ DocumentRoot "/var/www/example.com/www/secure"
+
+ php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+ php_admin_value upload_tmp_dir /tmp
+ php_admin_value session.save_path /var/www/example.com/www/tmp
+
+ LogLevel warn
+ ErrorLog /var/log/apache2/example.com/www/error.log
+ CustomLog /var/log/apache2/example.com/www/access.log combined
+
+ <Directory "/var/www/example.com/www/secure">
+ Options IncludesNoExec
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+ </Directory>
+
+ DirectoryIndex default.shtml
+
+ Include /etc/apache2/vhosts.d/include/php.conf
+
+ DirectoryIndex default.php index.php
+
+</VirtualHost>
--- /dev/null
+<VirtualHost 127.0.0.1:80>
+ ServerName www.example.com
+ ServerAlias example.com
+ DocumentRoot "/var/www/example.com/www/public"
+
+ php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+ php_admin_value upload_tmp_dir /var/www/example.com/www/tmp
+ php_admin_value session.save_path /var/www/example.com/www/tmp
+
+ LogLevel warn
+ ErrorLog /var/log/apache2/example.com/www/error.log
+ CustomLog /var/log/apache2/example.com/www/access.log combined
+
+ <Directory "/var/www/example.com/www/public">
+ Options IncludesNoExec
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+ </Directory>
+
+ DirectoryIndex default.shtml
+
+ Include /etc/apache2/vhosts.d/include/php.conf
+
+ DirectoryIndex default.php index.php
+
+</VirtualHost>
--- /dev/null
+require('src/janitor')
+require('test/unit')
+
+class JanitorTest < Test::Unit::TestCase
+
+ def test_new_files_dont_get_cleaned
+ j = Janitor.new()
+
+ File.open('test/fixtures/dummy/dummy-1.txt', 'w') do |f|
+ f << 'dfhdsgfhsdywerhdf'
+ end
+
+ File.open('test/fixtures/dummy/dummy-2.txt', 'w') do |f|
+ f << 'gjkh983yfhufg'
+ end
+
+ assert(true, File.file?('test/fixtures/dummy/dummy-1.txt'))
+ assert(true, File.file?('test/fixtures/dummy/dummy-2.txt'))
+
+ j.clean_directory('test/fixtures/dummy', 1)
+
+ assert(true, File.file?('test/fixtures/dummy/dummy-1.txt'))
+ assert(true, File.file?('test/fixtures/dummy/dummy-2.txt'))
+
+ File.delete('test/fixtures/dummy/dummy-1.txt')
+ File.delete('test/fixtures/dummy/dummy-2.txt')
+ end
+
+
+ def test_get_temporary_directories_unique
+ j = Janitor.new()
+ j.apache_vhosts_directory = 'test/fixtures/'
+ tmp_dirs = j.get_temporary_directories()
+ assert(true, tmp_dirs.include?('/tmp'))
+ assert(true, tmp_dirs.include?('/var/www/example.com/www/tmp'))
+ assert_equal(2, tmp_dirs.length)
+ end
+
+
+ def test_get_temporary_directories_non_unique
+ j = Janitor.new()
+ j.apache_vhosts_directory = 'test/fixtures/'
+ tmp_dirs = j.get_temporary_directories(false)
+ assert(true, tmp_dirs.include?('/tmp'))
+ assert(true, tmp_dirs.include?('/var/www/example.com/www/tmp'))
+ assert_equal(8, tmp_dirs.length)
+ end
+
+
+ def test_non_directory_returns_no_paths
+ j = Janitor.new()
+ j.apache_vhosts_directory = 'test/fixtures/WHARRGHARBL'
+ tmp_dirs = j.get_temporary_directories()
+ assert_equal(0, tmp_dirs.length)
+ end
+
+end
--- /dev/null
+require 'test/apache_conf_file_test'
+require 'test/janitor_test'