Class: KamalBackup::Config
- Inherits:
-
Object
- Object
- KamalBackup::Config
- Defined in:
- lib/kamal_backup/config.rb
Constant Summary collapse
- DEFAULT_RETENTION =
{ "RESTIC_KEEP_LAST" => "7", "RESTIC_KEEP_DAILY" => "7", "RESTIC_KEEP_WEEKLY" => "4", "RESTIC_KEEP_MONTHLY" => "6", "RESTIC_KEEP_YEARLY" => "2" }.freeze
- SUSPICIOUS_BACKUP_PATHS =
%w[/ /var /etc /root /usr /bin /sbin /boot /dev /proc /sys /run].freeze
- DEFAULT_CONFIG_PATHS =
%w[config/kamal-backup.yml config/kamal-backup.local.yml].freeze
- YAML_KEY_ALIASES =
{ "app_name" => "APP_NAME", "database_adapter" => "DATABASE_ADAPTER", "database_url" => "DATABASE_URL", "sqlite_database_path" => "SQLITE_DATABASE_PATH", "backup_paths" => "BACKUP_PATHS", "local_restore_source_paths" => "LOCAL_RESTORE_SOURCE_PATHS", "accessory" => "KAMAL_BACKUP_ACCESSORY", "restic_repository" => "RESTIC_REPOSITORY", "restic_password" => "RESTIC_PASSWORD", "restic_init_if_missing" => "RESTIC_INIT_IF_MISSING", "restic_check_after_backup" => "RESTIC_CHECK_AFTER_BACKUP", "restic_check_read_data_subset" => "RESTIC_CHECK_READ_DATA_SUBSET", "restic_forget_after_backup" => "RESTIC_FORGET_AFTER_BACKUP", "restic_keep_last" => "RESTIC_KEEP_LAST", "restic_keep_daily" => "RESTIC_KEEP_DAILY", "restic_keep_weekly" => "RESTIC_KEEP_WEEKLY", "restic_keep_monthly" => "RESTIC_KEEP_MONTHLY", "restic_keep_yearly" => "RESTIC_KEEP_YEARLY", "backup_schedule_seconds" => "BACKUP_SCHEDULE_SECONDS", "backup_start_delay_seconds" => "BACKUP_START_DELAY_SECONDS", "state_dir" => "KAMAL_BACKUP_STATE_DIR", "allow_production_restore" => "KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE", "allow_suspicious_paths" => "KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS", "pgpassword" => "PGPASSWORD", "mysql_pwd" => "MYSQL_PWD" }.freeze
Instance Attribute Summary collapse
-
#env ⇒ Object
readonly
Returns the value of attribute env.
Instance Method Summary collapse
- #accessory_name ⇒ Object
- #allow_in_place_file_restore? ⇒ Boolean
- #allow_production_restore? ⇒ Boolean
- #allow_suspicious_backup_paths? ⇒ Boolean
- #app_name ⇒ Object
- #backup_path_label(path) ⇒ Object
- #backup_paths ⇒ Object
- #backup_schedule_seconds ⇒ Object
- #backup_start_delay_seconds ⇒ Object
- #check_after_backup? ⇒ Boolean
- #check_read_data_subset ⇒ Object
- #database_adapter ⇒ Object
- #falsey?(key) ⇒ Boolean
- #forget_after_backup? ⇒ Boolean
-
#initialize(env: ENV, cwd: Dir.pwd, defaults: {}) ⇒ Config
constructor
A new instance of Config.
- #last_check_path ⇒ Object
- #last_restore_drill_path ⇒ Object
- #local_restore_path_pairs ⇒ Object
- #local_restore_source_paths ⇒ Object
- #production_like_target?(target) ⇒ Boolean
- #required_app_name ⇒ Object
- #required_value(key) ⇒ Object
- #restic_init_if_missing? ⇒ Boolean
- #restic_password ⇒ Object
- #restic_repository ⇒ Object
- #retention ⇒ Object
- #retention_args ⇒ Object
- #state_dir ⇒ Object
- #truthy?(key) ⇒ Boolean
- #validate_backup ⇒ Object
- #validate_backup_paths ⇒ Object
- #validate_database_backup ⇒ Object
- #validate_database_restore_target(target) ⇒ Object
- #validate_file_restore_target(target) ⇒ Object
- #validate_local_database_restore_target(target) ⇒ Object
- #validate_local_machine_restore ⇒ Object
- #validate_restic ⇒ Object
- #value(key) ⇒ Object
Constructor Details
#initialize(env: ENV, cwd: Dir.pwd, defaults: {}) ⇒ Config
Returns a new instance of Config.
47 48 49 50 |
# File 'lib/kamal_backup/config.rb', line 47 def initialize(env: ENV, cwd: Dir.pwd, defaults: {}) raw_env = env.to_h @env = defaults.to_h.merge(load_config_files(raw_env, cwd: cwd)).merge(raw_env) end |
Instance Attribute Details
#env ⇒ Object (readonly)
Returns the value of attribute env.
45 46 47 |
# File 'lib/kamal_backup/config.rb', line 45 def env @env end |
Instance Method Details
#accessory_name ⇒ Object
60 61 62 |
# File 'lib/kamal_backup/config.rb', line 60 def accessory_name value("KAMAL_BACKUP_ACCESSORY") end |
#allow_in_place_file_restore? ⇒ Boolean
92 93 94 |
# File 'lib/kamal_backup/config.rb', line 92 def allow_in_place_file_restore? truthy?("KAMAL_BACKUP_ALLOW_IN_PLACE_FILE_RESTORE") end |
#allow_production_restore? ⇒ Boolean
88 89 90 |
# File 'lib/kamal_backup/config.rb', line 88 def allow_production_restore? truthy?("KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE") end |
#allow_suspicious_backup_paths? ⇒ Boolean
96 97 98 |
# File 'lib/kamal_backup/config.rb', line 96 def allow_suspicious_backup_paths? truthy?("KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS") end |
#app_name ⇒ Object
52 53 54 |
# File 'lib/kamal_backup/config.rb', line 52 def app_name value("APP_NAME") end |
#backup_path_label(path) ⇒ Object
143 144 145 146 |
# File 'lib/kamal_backup/config.rb', line 143 def backup_path_label(path) label = path.to_s.sub(%r{\A/+}, "").gsub(%r{[^A-Za-z0-9_.-]+}, "-") label.empty? ? "root" : label end |
#backup_paths ⇒ Object
120 121 122 |
# File 'lib/kamal_backup/config.rb', line 120 def backup_paths split_paths(value("BACKUP_PATHS")) end |
#backup_schedule_seconds ⇒ Object
100 101 102 |
# File 'lib/kamal_backup/config.rb', line 100 def backup_schedule_seconds integer("BACKUP_SCHEDULE_SECONDS", 86_400, minimum: 1) end |
#backup_start_delay_seconds ⇒ Object
104 105 106 |
# File 'lib/kamal_backup/config.rb', line 104 def backup_start_delay_seconds integer("BACKUP_START_DELAY_SECONDS", 0, minimum: 0) end |
#check_after_backup? ⇒ Boolean
76 77 78 |
# File 'lib/kamal_backup/config.rb', line 76 def check_after_backup? truthy?("RESTIC_CHECK_AFTER_BACKUP") end |
#check_read_data_subset ⇒ Object
84 85 86 |
# File 'lib/kamal_backup/config.rb', line 84 def check_read_data_subset value("RESTIC_CHECK_READ_DATA_SUBSET") end |
#database_adapter ⇒ Object
148 149 150 151 152 153 154 155 156 |
# File 'lib/kamal_backup/config.rb', line 148 def database_adapter if explicit = value("DATABASE_ADAPTER") normalize_adapter(explicit) elsif adapter = adapter_from_database_url adapter elsif value("SQLITE_DATABASE_PATH") "sqlite" end end |
#falsey?(key) ⇒ Boolean
281 282 283 |
# File 'lib/kamal_backup/config.rb', line 281 def falsey?(key) %w[0 false no n off].include?(value(key).to_s.downcase) end |
#forget_after_backup? ⇒ Boolean
80 81 82 |
# File 'lib/kamal_backup/config.rb', line 80 def forget_after_backup? !falsey?("RESTIC_FORGET_AFTER_BACKUP") end |
#last_check_path ⇒ Object
112 113 114 |
# File 'lib/kamal_backup/config.rb', line 112 def last_check_path File.join(state_dir, "last_check.json") end |
#last_restore_drill_path ⇒ Object
116 117 118 |
# File 'lib/kamal_backup/config.rb', line 116 def last_restore_drill_path File.join(state_dir, "last_restore_drill.json") end |
#local_restore_path_pairs ⇒ Object
132 133 134 135 136 137 138 139 140 141 |
# File 'lib/kamal_backup/config.rb', line 132 def local_restore_path_pairs source_paths = local_restore_source_paths target_paths = backup_paths if source_paths.size == target_paths.size source_paths.zip(target_paths) else raise ConfigurationError, "LOCAL_RESTORE_SOURCE_PATHS must contain the same number of paths as BACKUP_PATHS" end end |
#local_restore_source_paths ⇒ Object
124 125 126 127 128 129 130 |
# File 'lib/kamal_backup/config.rb', line 124 def local_restore_source_paths if raw = value("LOCAL_RESTORE_SOURCE_PATHS") split_paths(raw) else backup_paths end end |
#production_like_target?(target) ⇒ Boolean
255 256 257 258 259 260 261 262 263 |
# File 'lib/kamal_backup/config.rb', line 255 def production_like_target?(target) target = target.to_s if source_database_targets.include?(target) true else production_named_target?(target.downcase) end end |
#required_app_name ⇒ Object
56 57 58 |
# File 'lib/kamal_backup/config.rb', line 56 def required_app_name required_value("APP_NAME") end |
#required_value(key) ⇒ Object
273 274 275 |
# File 'lib/kamal_backup/config.rb', line 273 def required_value(key) value(key) || raise(ConfigurationError, "#{key} is required") end |
#restic_init_if_missing? ⇒ Boolean
72 73 74 |
# File 'lib/kamal_backup/config.rb', line 72 def restic_init_if_missing? truthy?("RESTIC_INIT_IF_MISSING") end |
#restic_password ⇒ Object
68 69 70 |
# File 'lib/kamal_backup/config.rb', line 68 def restic_password value("RESTIC_PASSWORD") end |
#restic_repository ⇒ Object
64 65 66 |
# File 'lib/kamal_backup/config.rb', line 64 def restic_repository value("RESTIC_REPOSITORY") end |
#retention ⇒ Object
158 159 160 161 162 |
# File 'lib/kamal_backup/config.rb', line 158 def retention DEFAULT_RETENTION.each_with_object({}) do |(key, default), result| result[key] = value(key) || default end end |
#retention_args ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/kamal_backup/config.rb', line 164 def retention_args retention.each_with_object([]) do |(key, raw), args| next if raw.to_s.empty? number = Integer(raw) next if number <= 0 flag = "--#{key.sub("RESTIC_KEEP_", "keep-").downcase.tr("_", "-")}" args.concat([flag, number.to_s]) rescue ArgumentError raise ConfigurationError, "#{key} must be an integer" end end |
#state_dir ⇒ Object
108 109 110 |
# File 'lib/kamal_backup/config.rb', line 108 def state_dir value("KAMAL_BACKUP_STATE_DIR") || "/var/lib/kamal-backup" end |
#truthy?(key) ⇒ Boolean
277 278 279 |
# File 'lib/kamal_backup/config.rb', line 277 def truthy?(key) %w[1 true yes y on].include?(value(key).to_s.downcase) end |
#validate_backup ⇒ Object
184 185 186 187 188 |
# File 'lib/kamal_backup/config.rb', line 184 def validate_backup validate_restic validate_database_backup validate_backup_paths end |
#validate_backup_paths ⇒ Object
213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/kamal_backup/config.rb', line 213 def validate_backup_paths paths = backup_paths raise ConfigurationError, "BACKUP_PATHS must contain at least one path" if paths.empty? paths.each do |path| = File.(path) if SUSPICIOUS_BACKUP_PATHS.include?() && !allow_suspicious_backup_paths? raise ConfigurationError, "refusing suspicious backup path #{}; set KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS=true to override" end raise ConfigurationError, "backup path does not exist: #{path}" unless File.exist?(path) end end |
#validate_database_backup ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/kamal_backup/config.rb', line 195 def validate_database_backup case database_adapter when "postgres" unless value("DATABASE_URL") || value("PGDATABASE") raise ConfigurationError, "PostgreSQL backup requires DATABASE_URL or PGDATABASE/libpq environment" end when "mysql" unless value("DATABASE_URL") || value("MYSQL_DATABASE") || value("MARIADB_DATABASE") raise ConfigurationError, "MySQL backup requires DATABASE_URL or MYSQL_DATABASE/MARIADB_DATABASE" end when "sqlite" path = required_value("SQLITE_DATABASE_PATH") raise ConfigurationError, "SQLITE_DATABASE_PATH does not exist: #{path}" unless File.file?(path) else raise ConfigurationError, "DATABASE_ADAPTER is required or must be detectable from DATABASE_URL/SQLITE_DATABASE_PATH" end end |
#validate_database_restore_target(target) ⇒ Object
247 248 249 250 251 252 253 |
# File 'lib/kamal_backup/config.rb', line 247 def validate_database_restore_target(target) raise ConfigurationError, "restore database target is required" if target.to_s.strip.empty? if production_like_target?(target) && !allow_production_restore? raise ConfigurationError, "refusing production-looking restore target #{target}; set KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE=true to override" end end |
#validate_file_restore_target(target) ⇒ Object
234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/kamal_backup/config.rb', line 234 def validate_file_restore_target(target) raise ConfigurationError, "restore target cannot be empty" if target.to_s.strip.empty? = File.(target) raise ConfigurationError, "refusing to restore files to /" if == "/" if in_place_file_restore?() && !allow_in_place_file_restore? raise ConfigurationError, "refusing in-place file restore to #{}; set KAMAL_BACKUP_ALLOW_IN_PLACE_FILE_RESTORE=true to override" end end |
#validate_local_database_restore_target(target) ⇒ Object
226 227 228 229 230 231 232 |
# File 'lib/kamal_backup/config.rb', line 226 def validate_local_database_restore_target(target) raise ConfigurationError, "local restore database target is required" if target.to_s.strip.empty? if production_named_target?(target) && !allow_production_restore? raise ConfigurationError, "refusing production-looking local restore target #{target}; set KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE=true to override" end end |
#validate_local_machine_restore ⇒ Object
190 191 192 193 |
# File 'lib/kamal_backup/config.rb', line 190 def validate_local_machine_restore validate_local_machine_environment validate_local_machine_paths end |
#validate_restic ⇒ Object
178 179 180 181 182 |
# File 'lib/kamal_backup/config.rb', line 178 def validate_restic required_app_name required_value("RESTIC_REPOSITORY") required_value("RESTIC_PASSWORD") end |
#value(key) ⇒ Object
265 266 267 268 269 270 271 |
# File 'lib/kamal_backup/config.rb', line 265 def value(key) raw = env[key] return nil if raw.nil? stripped = raw.to_s.strip stripped.empty? ? nil : stripped end |