diff --git a/app/assets/javascripts/simple_configurations.js b/app/assets/javascripts/simple_configurations.js new file mode 100644 index 0000000..101c226 --- /dev/null +++ b/app/assets/javascripts/simple_configurations.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + if ($('select#simple_configuration_included_files').size() > 0) { + $('#simple_configuration_included_files').chosen(); + } +}); diff --git a/app/controllers/simple_configs_controller.rb b/app/controllers/simple_configs_controller.rb index b01d04d..8efb7e7 100644 --- a/app/controllers/simple_configs_controller.rb +++ b/app/controllers/simple_configs_controller.rb @@ -1,28 +1,31 @@ class SimpleConfigsController < ApplicationController before_action :require_logged_in before_action :fetch_host # GET /hosts/1/simple_configs/new def new @simple_config = @host.simple_configurations.new @simple_config.randomize end # POST /hosts/1/simple_configs def create @simple_config = @host.simple_configurations.new(fetch_config_params) - @simple_config.save - @simple_config.create_config - redirect_to host_path(@host, anchor: :jobs) + if @simple_config.save + @simple_config.create_config + redirect_to host_path(@host, anchor: :jobs) + else + render :new + end end private def fetch_host @host = current_user.hosts.find(params[:host_id]) end def fetch_config_params - params.require(:simple_configuration).permit(:name, :day, :hour, :minute) + params.require(:simple_configuration).permit(:name, :day, :hour, :minute, included_files: []) end end diff --git a/app/models/fileset.rb b/app/models/fileset.rb index 0b50436..d8ccf48 100644 --- a/app/models/fileset.rb +++ b/app/models/fileset.rb @@ -1,131 +1,131 @@ # Fileset model is the application representation of Bacula's Fileset. # It has references to a host and job templates. class Fileset < ActiveRecord::Base establish_connection ARCHIVING_CONF serialize :exclude_directions, Array serialize :include_directions, JSON attr_accessor :include_files belongs_to :host has_many :job_templates validates :name, presence: true, uniqueness: { scope: :host } validate :has_included_files, on: :create validates_with NameValidator before_save :sanitize_exclude_directions, :sanitize_include_directions DEFAULT_EXCLUDED = %w{/var/lib/bacula /proc /tmp /.journal /.fsck /bacula} DEFAULT_INCLUDE_OPTIONS = { signature: :SHA1, compression: :GZIP } DEFAULT_INCLUDE_FILE_LIST = ['/'] # Constructs an array where each element is a line for the Fileset's bacula config # # @return [Array] def to_bacula_config_array ['FileSet {'] + [" Name = \"#{name_for_config}\""] + include_directions_to_config_array + exclude_directions_to_config_array + ['}'] end # Provides a human readable projection of the fileset # # @return [String] def human_readable result = "Directories:\n" result << "\t* " << include_directions['file'].join("\n\t* ") if exclude_directions.present? result << "\n\nExcluded:\n" result << "\t* " << exclude_directions.join("\n\t*") end result end # Generates a name that will be used for the configuration file. # It is the name that will be sent to Bacula through the configuration # files. # # @return [String] def name_for_config [host.name, name].join(' ') end # Returns the hosts that have enabled jobs that use this fileset # # @return [ActiveRecord::Relation] the participating hosts def participating_hosts Host.joins(:job_templates).where(job_templates: { enabled: true, fileset_id: id }).uniq end # Creates a default fileset resource for a simple config - def default_resource(name, time_hex) - @include_files = DEFAULT_INCLUDE_FILE_LIST + def default_resource(name, time_hex, opts = {}) + @include_files = opts[:files].presence || DEFAULT_INCLUDE_FILE_LIST self.name = "files_#{name}_#{time_hex}" self.exclude_directions = DEFAULT_EXCLUDED save! self end private def has_included_files if include_files.blank? || include_files.all?(&:blank?) errors.add(:include_files, :cant_be_blank) end end def sanitize_include_directions files = include_files.compact.uniq.keep_if(&:present?) return false if files.blank? self.include_directions = { options: DEFAULT_INCLUDE_OPTIONS, file: files } end def sanitize_exclude_directions self.exclude_directions = exclude_directions.keep_if(&:present?).uniq rescue nil end def exclude_directions_to_config_array return [] if exclude_directions.empty? [' Exclude {'] + exclude_directions.map { |x| " File = \"#{x}\"" } + [' }'] end def include_directions_to_config_array return [] if include_directions.blank? [" Include {"] + included_options + included_files + [' }'] end def included_options formatted = [" Options {"] options = include_directions.deep_symbolize_keys[:options]. reverse_merge(DEFAULT_INCLUDE_OPTIONS) options.each do |k,v| if not [:wildfile].include? k formatted << " #{k} = #{v}" else formatted << v.map { |f| " #{k} = \"#{f}\"" } end end formatted << " }" formatted end def included_files include_directions['file'].map { |f| " File = #{f}" } end def included_wildfile include_directions['wildfile'].map { |f| " wildfile = \"#{f}\"" }.join("\n") end end diff --git a/app/models/simple_configuration.rb b/app/models/simple_configuration.rb index c656c41..af81afa 100644 --- a/app/models/simple_configuration.rb +++ b/app/models/simple_configuration.rb @@ -1,54 +1,67 @@ class SimpleConfiguration < ActiveRecord::Base establish_connection ARCHIVING_CONF + attr_accessor :included_files + + INCLUDED_FILE_OPTIONS = + %w{/ /bin /boot /etc /home /lib /media /mnt /opt /root /run /sbin /srv /usr /var} + DAYS = { monday: :mon, tuesday: :tue, wednesday: :wed, thursday: :thu, friday: :fri, saturday: :sat, sunday: :sun } enum day: { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 } belongs_to :host validates :host, :day, :hour, :minute, presence: true validates :hour, numericality: { greater_than_or_equal: 0, less_then: 24 } validates :minute, numericality: { greater_than_or_equal: 0, less_then: 60 } validates_with NameValidator + before_save :sanitize_included_files + # Initializes the configuration's 3 parameters randomnly. # Default configurations must be randomized in order to distribute the backup server's # load. def randomize self.day = SimpleConfiguration.days.keys.sample self.hour = rand(24) self.minute = rand(60) end # The day abbreviation # # @return [Symbol] def day_short DAYS[day.to_sym] end # Creates a default config, by adding resources for: # # * schedule # * fileset # * job # # Each resource handles its own defaults. def create_config time_hex = Digest::MD5.hexdigest(Time.now.to_f.to_s).first(4) schedule = host.schedules.new.default_resource(time_hex, day_short, hour, minute) - fileset = host.filesets.new.default_resource(name, time_hex) + fileset = host.filesets.new.default_resource(name, time_hex, files: included_files) host.job_templates.new(fileset_id: fileset.id, schedule_id: schedule.id). default_resource(name, time_hex) end + + private + + def sanitize_included_files + self.included_files = included_files.keep_if(&:present?).uniq.presence + end end diff --git a/app/views/simple_configs/new.html.erb b/app/views/simple_configs/new.html.erb index f3d78ce..2998dd1 100644 --- a/app/views/simple_configs/new.html.erb +++ b/app/views/simple_configs/new.html.erb @@ -1,36 +1,44 @@
This the basic Backup configuration.
Your server is going to receive daily backups, on the selected hour and minute.
The backup plan is optimized to have the minimum overhead to your system, while maintaining a high performance restore system.
-The most time consuming backup will be on the selected day.
+The most time consuming backups will be on the selected day.
+You can specify a set of files you want to backup from a list of preselected Fileset
+You can alter this configuration later on according to your specific needs.