require 'log4r' require 'vagrant' require 'thread' require_relative 'unison_paths' require_relative 'ssh_command' require_relative 'shell_command' require_relative 'unison_sync' module VagrantPlugins module Unison # Command Sync class CommandSync < Vagrant.plugin('2', :command) include UnisonSync attr_accessor :bg_thread def self.synopsis 'sync the unison shared folder forever, by polling for changes' end def execute parse_options! sync_once sync_polling unless options[:no_polling] end def sync_once status = nil with_target_vms do |machine| execute_sync_command(machine, true) do |command| command.batch = true command.terse = true command = command.to_s @env.ui.info 'Started main sync process' if options[:verbose] @env.ui.info ' Verbose mode: enabled' @env.ui.info " Command: #{command}" end status = system(command) @env.ui.info 'unison exited with a error' unless status end end return 0 if status 1 end def sync_polling status = nil with_target_vms do |machine| @bg_thread = watch_vm_for_memory_leak(machine) execute_sync_command(machine) do |command| command.repeat = true command.terse = true command = command.to_s @env.ui.info 'Polling process started' if options[:verbose] @env.ui.info ' Verbose mode: enabled' @env.ui.info " Memory limit: #{machine.config.unison.mem_cap_mb}MB" @env.ui.info " Command: #{command}" if options[:verbose] end # Re-run on a crash. # On a sigint, wait 2 seconds before respawning command. # If INT comes in again while waiting, program exits. # If INT comes in after we've respanwned, # will bring us back to this trap handler. exit_on_next_sigint = false loop do begin sleep 2 if exit_on_next_sigint exit_on_next_sigint = false status = system(command) @env.ui.info "**** unison exited. success: #{status} ****" rescue Interrupt if exit_on_next_sigint Thread.kill(@bg_thread) if @bg_thread exit 1 end @env.ui.info '** Hit Ctrl + C again to kill. **' exit_on_next_sigint = true rescue StandardError @env.ui.info '** Sync crashed. Restarting. Hit Ctrl + C twice to kill. **' end end end end return 0 if status 1 end def watch_vm_for_memory_leak(machine) ssh_command = SshCommand.new(machine, options) Thread.new(ssh_command.ssh, machine.config.unison.mem_cap_mb) do |ssh_command_text, mem_cap_mb| loop do sleep 15 total_mem = `#{ssh_command_text} 'free -m | egrep "^Mem:" | awk "{print \\$2}"' 2>/dev/null` unison_proc_returnval = `#{ssh_command_text} 'ps aux | grep "[u]nison -server" | awk "{print \\$2, \\$4}"' 2>/dev/null` if unison_proc_returnval == '' puts 'Unison is idling, status OK' next end pid, mem_pct_unison = unison_proc_returnval.strip.split(' ') mem_unison = (total_mem.to_f * mem_pct_unison.to_f / 100) mem_unison = mem_unison.round(1) if mem_unison > mem_cap_mb puts "Unison using #{mem_unison}MB memory is over limit of #{mem_cap_mb}MB, restarting" `#{ssh_command_text} kill -HUP #{pid} 2>/dev/null` end end end end end # Command Cleanup class CommandCleanup < Vagrant.plugin('2', :command) include UnisonSync def self.synopsis 'Remove archives and configuration from host and guest. Keeps files in guest (need to remove manually if needed)' end def execute with_target_vms do |machine| ssh_user = machine.config.unison.ssh_user command = "rm -rf ~/Library/'Application Support'/Unison/*" @env.ui.info "Running #{command} on host" if options[:verbose] system(command) command = "sudo -H -u #{ssh_user} sh -c 'rm -rf ~/.unison'" @env.ui.info "Running #{command} on guest VM" if options[:verbose] machine.communicate.execute(command) end 0 end end # Command Interact class CommandInteract < Vagrant.plugin('2', :command) include UnisonSync def self.synopsis 'run unison in interactive mode, to resolve conflicts' end def execute with_target_vms do |machine| execute_sync_command(machine) do |command| command.terse = true command = command.to_s @env.ui.info "Running #{command}" if options[:verbose] system(command) end end 0 end end end end