Capistrano::Configuration.instance(:must_exist).load do
  cfg = ec2onrails_config
  
  namespace :ec2onrails do
    namespace :server do
      desc <<-DESC
        Tell the servers what roles they are in. This configures them with \
        the appropriate settings for each role, and starts and/or stops the \
        relevant services.
      DESC
      task :set_roles do
        # Create a list of server roles based on the capistrano roles.
        # We treat :db specially because it maps to a different rolename on the server
        # if :primary => true
        server_roles = {}
        roles.keys.delete_if{|r|r == :db}.each do |rolename|
          server_roles[rolename] = hostnames_for_role(rolename)
        end
        server_roles[:db_primary] = hostnames_for_role(:db, :primary => true)

        roles_yml = YAML::dump(server_roles)
        put roles_yml, "/tmp/roles.yml"
        allow_sudo do
          sudo "cp /tmp/roles.yml /etc/ec2onrails"
          #we want everyone to be able to read to it
          sudo "chmod a+r /etc/ec2onrails/roles.yml"
          sudo "/usr/local/ec2onrails/bin/set_roles"
        end
      end
      
      task :init_services do
        allow_sudo do
          #lets pick up the new configuration files
          sudo "/usr/local/ec2onrails/bin/init_services"
        end
      end
      
      desc <<-DESC
        Change the default value of RAILS_ENV on the server.
        The value is specified in :rails_env.
        Be sure to do deploy:restart after this.
      DESC
      task :set_rails_env do
        allow_sudo do
          rails_env = fetch(:rails_env, "production")
          sudo "/usr/local/ec2onrails/bin/set_rails_env #{rails_env}"
        end
      end
      
      desc <<-DESC
        Upgrade to the newest versions of all Ubuntu packages.
      DESC
      task :upgrade_packages do
        allow_sudo do
          sudo "aptitude -q update"
          sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y safe-upgrade'"
        end
      end
      
      desc <<-DESC
        Upgrade to the newest versions of all rubygems.
      DESC
      task :upgrade_gems do
        allow_sudo do
          sudo "gem update --system --no-rdoc --no-ri"
          sudo "gem update --no-rdoc --no-ri"
        end
      end
      
      desc <<-DESC
        Install extra Ubuntu packages. Set ec2onrails_config[:packages], it \
        should be an array of strings.
        NOTE: the package installation will be non-interactive, if the packages \
        require configuration either set ec2onrails_config[:interactive_packages] \
        like you would for ec2onrails_config[:packages] (we'll flood the server \
        with 'Y' inputs), or log in as 'root' and run \
        'dpkg-reconfigure packagename' or replace the package's config files \
        using the 'ec2onrails:server:deploy_files' task.
      DESC
      task :install_packages do
        allow_sudo do
          sudo "aptitude -q update"
          if cfg[:packages] && cfg[:packages].any?
            sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install #{cfg[:packages].join(' ')}'"
          end
          if cfg[:interactive_packages] && cfg[:interactive_packages].any?
            # sudo "aptitude install #{cfg[:interactive_packages].join(' ')}", {:env => {'DEBIAN_FRONTEND' => 'readline'} }
            #trying to pick WHEN to send a Y is a bit tricky...it totally depends on the 
            #interactive package you want to install.  FLOODING it with 'Y'... but not sure how
            #'correct' or robust this is
            cmd = "sudo sh -c 'export DEBIAN_FRONTEND=readline; aptitude -y -q install #{cfg[:interactive_packages].join(' ')}'"
            run(cmd) do |channel, stream, data|
                channel.send_data "Y\n"
            end
          end
        end
      end
      
      task :configure_firewall do
        # TODO
      end
      
    
      desc <<-DESC
        Provide extra security measures.  Set ec2onrails_config[:harden_server] = true \
        to allow the hardening of the server.
        These security measures are those which can make initial setup and playing around
        with Ec2onRails tricky.  For example, you can be logged out of your server forever
      DESC
      task :harden_server do
        #NOTES: for those security features that will get in the way of ease-of-use
        #       hook them in here
        # Like encrypting the mnt directory
        # http://groups.google.com/group/ec2ubuntu/web/encrypting-mnt-using-cryptsetup-on-ubuntu-7-10-gutsy-on-amazon-ec2
        if cfg[:harden_server]
          #lets install some extra packages:
          # denyhosts: sshd security tool.  config file is already installed... 
          #
          security_pkgs = %w{denyhosts}
          allow_sudo do
            sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install #{security_pkgs.join(' ')}'"          
            #lets setup dkim
            setup_email_signing
          end
        end
      end
      
      #based on the recipe here (but which is missing a few key steps!)
      #http://www.howtoforge.com/quick-and-easy-setup-for-domainkeys-using-ubuntu-postfix-and-dkim-filter
      desc <<-DESC
        enables dkim signing of outgoing msgs.  This helps with fightint spam.
        You'll have to update your dns records to take advantage of this, but we'll
        help you out with that 
        NOTE: set ec2onrails_config[:service_domain] = 'yourdomain.com' before running this task
      DESC
      task :setup_email_signing do
        if cfg[:service_domain].nil? || cfg[:service_domain].empty?
          raise "ERROR: missing the :service_domain key.  Please set that in your deploy script if you would like to use this task."
        end
    
        domain = cfg[:service_domain]
        postmaster_email = "postmaster@#{domain}"
    
        #make the selector something that will help us roll over and expire the old key next year
        selector = "mail#{Time.now.year.to_s[-2..-1]}"  #ie, mail09
    
        allow_sudo do
          sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install postfix dkim-filter'"
          #do NOT change the size of the key; making it longer can cause problems with some of the dkim implementations
    
          keys_exist = File.exist?("config/mail/dkim/dkim_#{selector}.private.key") && File.exist?("config/mail/dkim/dkim_#{selector}.public.key")
    
          unless keys_exist
            #lets make them!
            cmds = <<-CMDS
    mkdir -p config/mail/dkim;
    cd config/mail/dkim;
    openssl genrsa -out dkim_#{selector}.private.key 1024;
    openssl rsa -in dkim_#{selector}.private.key -out dkim_#{selector}.public.key -pubout -outform PEM
    CMDS
            system cmds
          end
    
          pub_key = File.read("config/mail/dkim/dkim_#{selector}.public.key")
          pub_key = pub_key.split("\n")[1..-2].join('')
          
          #lets get the private and public keys up to the server
          put File.read("config/mail/dkim/dkim_#{selector}.private.key"), "/tmp/dkim_#{selector}.private.key"
          put File.read("config/mail/dkim/dkim_#{selector}.public.key"), "/tmp/dkim_#{selector}.public.key"
          sudo "mkdir -p /var/dkim-filter"
          sudo "mv /tmp/dkim_#{selector}.p*.key /var/dkim-filter/."
    
          #saw a note that Canonicalization relaxed was helpful for rails applications...
          #haven't tested that yet
          dkim_filter_conf = <<-SCRIPT 
    # Log to syslog
      Syslog      yes
    
    # Sign for example.com with key in /etc/mail/dkim.key using
      Domain      #{domain}   
      KeyFile     /var/dkim-filter/dkim_#{selector}.private.key
      Selector    #{selector} 
    
    # Common settings. See dkim-filter.conf(5) for more information.
      AutoRestart       no
      Background        yes
      SubDomains        no
      Canonicalization  relaxed
    SCRIPT
    
          put dkim_filter_conf, "/tmp/dkim-filter.conf.tmp"
          sudo "mv /etc/dkim-filter.conf /etc/dkim-filter.conf.orig" 
          sudo "mv /tmp/dkim-filter.conf.tmp /etc/dkim-filter.conf" 
          cmds = <<-CMDS
    sudo postconf -e 'myhostname = #{domain}';
    sudo postconf -e 'mydomain = #{domain}';
    sudo postconf -e 'myorigin = $mydomain';
    sudo postconf -e 'mynetworks_style=subnet';
    sudo postconf -e 'biff = no';
    sudo postconf -e 'alias_maps = hash:/etc/aliases';
    sudo postconf -e 'alias_database = hash:/etc/aliases';
    sudo postconf -e 'mydestination = localdomain, localhost, localhost.localdomain, localhost';
    sudo postconf -e 'relay_domains=$mydestination';
    sudo postconf -e 'mynetworks = 127.0.0.0/8';
    sudo postconf -e 'smtpd_milters = inet:localhost:8891';
    sudo postconf -e 'non_smtpd_milters = inet:localhost:8891';
    sudo postconf -e 'milter_protocol = 2';
    sudo postconf -e 'milter_default_action = accept'
    CMDS
          sudo cmds
    
          #lets lock it down
          sudo "chown -R dkim-filter:dkim-filter /var/dkim-filter"
          sudo "chmod 600 /var/dkim-filter/*"
    
          puts "*" * 80
          puts "NOTE: you need to do a few things"
          puts "  * created public and private DKIM keys to config/mail/dkim_#{selector}.*.key" unless keys_exist
          puts "\n"
          msg = <<-MSG
      * Enter these *TWO* records into your DNS record:
          #{selector}._domainkey.#{domain} IN TXT 'k=rsa; t=y; p=#{pub_key}'
          _domainkey.#{domain} IN TXT 't=y; o=~; r=#{postmaster_email}'
    
    I would recommend signing into your ec2 instance and running some test emails.  Gmail is very fast in updating their records, but yahoo (as of this writing) is slow and inconsistent.  But you can run a command like this to various email address to see how it works:
    
    echo 'something searchable so you can find it in your spam filter!  did dkim work?' | mail -s "my dkim email; lets see how it went" adam@someservice.com
    
    
    NOTE: in the near future, when things are looking good, if you take away the 't=y; ' from the above two records, it tells the email services that you are no longer testing the service and to treat your signings with tough love.
    
    
    MSG
          puts msg
    
          #sometimes the dkim-filter restart fails; it seems to be a race condition with some of the postfix changes going in...
          #but a sleep here seems to do the trick.
          sleep(10)
          output = quiet_capture "sudo /etc/init.d/dkim-filter restart"
          if output =~ /smfi_opensocket\(\) failed/
            #ah, if we didn't sleep enough above, lets try it one more time; but this time it will fail if we still get
            #the smfi_opensocket error
            sleep(5)
            sudo "/etc/init.d/dkim-filter restart 2>&1"
          end
          sleep(2)
          sudo "/etc/init.d/postfix restart 2>&1"
        end
    
      end
    
      
      desc <<-DESC
        Install extra rubygems. Set ec2onrails_config[:rubygems], it should \
        be with an array of strings.
      DESC
      task :install_gems do
        if cfg[:rubygems]
          allow_sudo do
            cfg[:rubygems].each do |g|
              sudo "gem install #{g} --no-rdoc --no-ri"
            end
          end
        end        
      end
      
      task :run_rails_rake_gems_install do
        #if running under Rails 2.1, lets trigger 'rake gems:install', but in such a way
        #so it fails gracefully if running rails < 2.1
        # ALSO, this might be the first time rake is run, and running it as sudo means that 
        # if any plugins are loaded and create directories... like what image_science does for 
        # ruby_inline, then the dirs will be created as root.  so trigger the rails loading
        # very quickly before the sudo is called
        # run "cd #{release_path} && rake RAILS_ENV=#{rails_env} -T 1>/dev/null && sudo rake RAILS_ENV=#{rails_env} gems:install"
        allow_sudo do
          output = run "cd #{release_path} && rake RAILS_ENV=#{rails_env} db:version > /dev/null 2>&1 || sudo rake RAILS_ENV=#{rails_env} gems:install"
          puts output
        end
      end
      
      desc <<-DESC
        A convenience task to upgrade existing packages and gems and install \
        specified new ones.
      DESC
      task :upgrade_and_install_all do
        upgrade_packages
        upgrade_gems
        install_packages
        install_gems
      end
      
      desc <<-DESC
        Set the timezone using the value of the variable named timezone. \
        Valid options for timezone can be determined by the contents of \
        /usr/share/zoneinfo, which can be seen here: \
        http://packages.ubuntu.com/cgi-bin/search_contents.pl?searchmode=filelist&word=tzdata&version=gutsy&arch=all&page=1&number=all \
        Remove 'usr/share/zoneinfo/' from the filename, and use the last \
        directory and file as the value. For example 'Africa/Abidjan' or \
        'posix/GMT' or 'Canada/Eastern'.
      DESC
      task :set_timezone do
        if cfg[:timezone]
          allow_sudo do
            sudo "bash -c 'echo #{cfg[:timezone]} > /etc/timezone'"
            sudo "cp /usr/share/zoneinfo/#{cfg[:timezone]} /etc/localtime"
          end
        end
      end
      
      desc <<-DESC
        DEPRECATED. See install_system_files.
      DESC
      task :deploy_files do
        if cfg[:server_config_files_root]
          puts "*****  DEPRECATION WARNING: you're using the deploy_files task which has been deprecated" # TODO pointer to documentation
          begin
            filename = "config_files.tar"
            local_file = "#{Dir.tmpdir}/#{filename}"
            remote_file = "/tmp/#{filename}"
            FileUtils.cd(cfg[:server_config_files_root]) do
              File.open(local_file, 'wb') { |tar| Minitar.pack(".", tar) }
            end
            put File.read(local_file), remote_file
            allow_sudo do
              sudo "tar xvf #{remote_file} -o -C /"
            end
          ensure
            rm_rf local_file
            run "rm -f #{remote_file}"
          end
        end
      end
      
      desc <<-DESC
        Installs files into the system anywhere outside of the Rails app.
        The directory RAILS_ROOT/config/ec2onrails/system_files can contain
        files that will be installed into the server relative to "/", and it
        can contain a manifest file with metadata to change the file owner
        and permissions, and it allows the files to be cleanly uninstalled 
        from the system.
        TODO pointer to full documentation
      DESC
      task :install_system_files do
        allow_sudo do
          sudo "/usr/local/ec2onrails/bin/install_system_files #{release_path}"
        end
      end
      
      desc <<-DESC
        Restart a set of services. Set ec2onrails_config[:services_to_restart]
        to an array of strings. It's assumed that each service has a script
        in /etc/init.d
      DESC
      task :restart_services do
        if cfg[:services_to_restart] && cfg[:services_to_restart].any?
          allow_sudo do
            cfg[:services_to_restart].each do |service|
              run_init_script(service, "restart")
            end
          end
        end
      end
      
      desc <<-DESC
        Set the email address that mail to the app user forwards to.
      DESC
      task :set_mail_forward_address do
        if cfg[:mail_forward_address]
          allow_sudo do
            sudo "sh -c 'echo #{cfg[:mail_forward_address]} > /root/.forward'"
          end
        end
      end
    
      desc <<-DESC
      Enable ssl for the web server. You'll want to replace the default SSL
      certificate and key files, the certificate file is at
      /etc/ec2onrails/ssl/cert/ec2onrails-default.crt
      and a the key file is at
      /etc/ec2onrails/ssl/private/ec2onrails-default.key
      (use the deploy_files task).
      The key file should NOT have a passphrase.
      DESC
      task :enable_ssl, :roles => :web do
        # TODO: enable for nginx
        # run_init_script("nginx", "restart")
      end
      
      desc <<-DESC
        Upload the app user's SSH deploy keys from
        config/ec2onrails/deploy_keys to /home/app/.ssh
        The deploy_keys dir should contain the SSH config files that
        are needed to deploy your app's source code from your SCM repository
        (if you're deploying from an SCM repo). This means an SSH private key
        (named id_dsa), the public key (named id_dsa.pub) and possibly a 
        known_hosts file.
        They can't deployed via install_system_files because the
        app user's SSH credentials might be needed to deploy the
        app itself if it's coming directly from an SCM repository.
      DESC
      task :upload_deploy_keys do
        deploy_keys_dir = "config/ec2onrails/deploy_keys"
        remote_dir = "/home/app/.ssh"
        
        if File.exist? deploy_keys_dir
          run "mkdir -p #{remote_dir}"
          Dir.chdir deploy_keys_dir do
            Dir.glob("*").each do |f|
              remote_file = "#{remote_dir}/#{f}"
              put File.read(f), "#{remote_file}"
            end
          end
          run "chmod -R go-rwx /home/app/.ssh"
        end
      end
      
      desc <<-DESC
        Clear the varnish proxy cache (if caching is enabled, which it isn't
        by default). Purges all documents from the cache.
      DESC
      task :purge_proxy_cache, :roles => :proxy do
        run "varnishadm -T localhost:6082 'url.purge .*'"
      end
      
      desc <<-DESC
        Restrict the app user's sudo access.
        Defaults the user to only be able to \
        sudo to god
      DESC
      task :restrict_sudo_access do
        old_user = fetch(:user)
        is_rootequiv = capture("groups").split.include?("rootequiv") # check groups before changing user
        begin
          set :user, 'root'
          sessions.clear #clear out sessions cache..... this way the ssh connections are reinitialized
          
          # Remove the app user from the "rootequiv" group, this removes full sudo ability
          if is_rootequiv
            run "deluser app rootequiv"
          else
            puts "User 'app' is not a member of group 'rootequiv' (old_user = #{old_user})."
          end
        ensure
          set :user, old_user
          sessions.clear
        end
      end
    
      desc <<-DESC
        Grant *FULL* sudo access to the app user.
        This is NOT RECOMMENDED, it will make the 'app' user the
        equivalent of 'root' until the 'restrict_sudo_access' task is run. 
        Alternatively, a task that requires sudo ability can call the
        allow_sudo method with a block, this will give the app user sudo
        ability only while the block is being run.
      DESC
      task :grant_sudo_access do
        allow_sudo
      end
          
      @within_sudo = 0
      def allow_sudo
        begin
          @within_sudo += 1
          old_user = fetch(:user)
          if @within_sudo > 1
            yield if block_given?
            true
          # The following can break if using multiple instances and a deploy was killed partway.
          # Disabled for now.
          # elsif capture("groups").split.include?("rootequiv")
          #   yield if block_given?
          #   false
          else
            begin
              # need to cheat and temporarily set the user to ROOT so we
              # can temporarily add the app user to the rootequiv group.
              # we can do this because the root and app user have the same
              # ssh login preferences....
              set :user, 'root'
              sessions.clear #clear out sessions cache..... this way the ssh connections are reinitialized
          
              # Temporarily add the app user to the "rootequiv" group, this will give full sudo ability
              run "adduser app rootequiv"
          
              set :user, old_user
              sessions.clear
              yield if block_given?
            ensure
              server.restrict_sudo_access if block_given?
              set :user, old_user
              sessions.clear
              true
            end
          end
        ensure
          @within_sudo -= 1
        end
      end

    end
     
  end

end