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
- SHARED_CONFIG_PATH =
"config/kamal-backup.yml"- LOCAL_CONFIG_PATH =
"config/kamal-backup.local.yml"- DEFAULT_CONFIG_PATHS =
[SHARED_CONFIG_PATH, LOCAL_CONFIG_PATH].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_repository_file" => "RESTIC_REPOSITORY_FILE", "restic_password" => "RESTIC_PASSWORD", "restic_password_file" => "RESTIC_PASSWORD_FILE", "restic_password_command" => "RESTIC_PASSWORD_COMMAND", "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_paths: nil, load_project_defaults: true) ⇒ 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_password_command ⇒ Object
- #restic_password_file ⇒ Object
- #restic_repository ⇒ Object
- #restic_repository_file ⇒ Object
- #retention ⇒ Object
- #retention_args ⇒ Object
- #state_dir ⇒ Object
- #truthy?(key) ⇒ Boolean
- #validate_backup(check_files: true) ⇒ Object
- #validate_backup_paths(check_files: true) ⇒ Object
- #validate_database_backup(check_files: true) ⇒ 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(check_files: true) ⇒ Object
- #value(key) ⇒ Object
Constructor Details
#initialize(env: ENV, cwd: Dir.pwd, defaults: {}, config_paths: nil, load_project_defaults: true) ⇒ Config
Returns a new instance of Config.
53 54 55 56 57 |
# File 'lib/kamal_backup/config.rb', line 53 def initialize(env: ENV, cwd: Dir.pwd, defaults: {}, config_paths: nil, load_project_defaults: true) raw_env = env.to_h base = load_project_defaults ? project_defaults(cwd: cwd) : {} @env = base.merge(defaults.to_h).merge(load_config_files(raw_env, cwd: cwd, paths: config_paths)).merge(raw_env) end |
Instance Attribute Details
#env ⇒ Object (readonly)
Returns the value of attribute env.
51 52 53 |
# File 'lib/kamal_backup/config.rb', line 51 def env @env end |
Instance Method Details
#accessory_name ⇒ Object
67 68 69 |
# File 'lib/kamal_backup/config.rb', line 67 def accessory_name value("KAMAL_BACKUP_ACCESSORY") end |
#allow_in_place_file_restore? ⇒ Boolean
111 112 113 |
# File 'lib/kamal_backup/config.rb', line 111 def allow_in_place_file_restore? truthy?("KAMAL_BACKUP_ALLOW_IN_PLACE_FILE_RESTORE") end |
#allow_production_restore? ⇒ Boolean
107 108 109 |
# File 'lib/kamal_backup/config.rb', line 107 def allow_production_restore? truthy?("KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE") end |
#allow_suspicious_backup_paths? ⇒ Boolean
115 116 117 |
# File 'lib/kamal_backup/config.rb', line 115 def allow_suspicious_backup_paths? truthy?("KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS") end |
#app_name ⇒ Object
59 60 61 |
# File 'lib/kamal_backup/config.rb', line 59 def app_name value("APP_NAME") end |
#backup_path_label(path) ⇒ Object
162 163 164 165 |
# File 'lib/kamal_backup/config.rb', line 162 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
139 140 141 |
# File 'lib/kamal_backup/config.rb', line 139 def backup_paths split_paths(value("BACKUP_PATHS")) end |
#backup_schedule_seconds ⇒ Object
119 120 121 |
# File 'lib/kamal_backup/config.rb', line 119 def backup_schedule_seconds integer("BACKUP_SCHEDULE_SECONDS", 86_400, minimum: 1) end |
#backup_start_delay_seconds ⇒ Object
123 124 125 |
# File 'lib/kamal_backup/config.rb', line 123 def backup_start_delay_seconds integer("BACKUP_START_DELAY_SECONDS", 0, minimum: 0) end |
#check_after_backup? ⇒ Boolean
95 96 97 |
# File 'lib/kamal_backup/config.rb', line 95 def check_after_backup? truthy?("RESTIC_CHECK_AFTER_BACKUP") end |
#check_read_data_subset ⇒ Object
103 104 105 |
# File 'lib/kamal_backup/config.rb', line 103 def check_read_data_subset value("RESTIC_CHECK_READ_DATA_SUBSET") end |
#database_adapter ⇒ Object
167 168 169 170 171 172 173 174 175 |
# File 'lib/kamal_backup/config.rb', line 167 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
300 301 302 |
# File 'lib/kamal_backup/config.rb', line 300 def falsey?(key) %w[0 false no n off].include?(value(key).to_s.downcase) end |
#forget_after_backup? ⇒ Boolean
99 100 101 |
# File 'lib/kamal_backup/config.rb', line 99 def forget_after_backup? !falsey?("RESTIC_FORGET_AFTER_BACKUP") end |
#last_check_path ⇒ Object
131 132 133 |
# File 'lib/kamal_backup/config.rb', line 131 def last_check_path File.join(state_dir, "last_check.json") end |
#last_restore_drill_path ⇒ Object
135 136 137 |
# File 'lib/kamal_backup/config.rb', line 135 def last_restore_drill_path File.join(state_dir, "last_restore_drill.json") end |
#local_restore_path_pairs ⇒ Object
151 152 153 154 155 156 157 158 159 160 |
# File 'lib/kamal_backup/config.rb', line 151 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
143 144 145 146 147 148 149 |
# File 'lib/kamal_backup/config.rb', line 143 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
274 275 276 277 278 279 280 281 282 |
# File 'lib/kamal_backup/config.rb', line 274 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
63 64 65 |
# File 'lib/kamal_backup/config.rb', line 63 def required_app_name required_value("APP_NAME") end |
#required_value(key) ⇒ Object
292 293 294 |
# File 'lib/kamal_backup/config.rb', line 292 def required_value(key) value(key) || raise(ConfigurationError, "#{key} is required") end |
#restic_init_if_missing? ⇒ Boolean
91 92 93 |
# File 'lib/kamal_backup/config.rb', line 91 def restic_init_if_missing? truthy?("RESTIC_INIT_IF_MISSING") end |
#restic_password ⇒ Object
79 80 81 |
# File 'lib/kamal_backup/config.rb', line 79 def restic_password value("RESTIC_PASSWORD") end |
#restic_password_command ⇒ Object
87 88 89 |
# File 'lib/kamal_backup/config.rb', line 87 def restic_password_command value("RESTIC_PASSWORD_COMMAND") end |
#restic_password_file ⇒ Object
83 84 85 |
# File 'lib/kamal_backup/config.rb', line 83 def restic_password_file value("RESTIC_PASSWORD_FILE") end |
#restic_repository ⇒ Object
71 72 73 |
# File 'lib/kamal_backup/config.rb', line 71 def restic_repository value("RESTIC_REPOSITORY") end |
#restic_repository_file ⇒ Object
75 76 77 |
# File 'lib/kamal_backup/config.rb', line 75 def restic_repository_file value("RESTIC_REPOSITORY_FILE") end |
#retention ⇒ Object
177 178 179 180 181 |
# File 'lib/kamal_backup/config.rb', line 177 def retention DEFAULT_RETENTION.each_with_object({}) do |(key, default), result| result[key] = value(key) || default end end |
#retention_args ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/kamal_backup/config.rb', line 183 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
127 128 129 |
# File 'lib/kamal_backup/config.rb', line 127 def state_dir value("KAMAL_BACKUP_STATE_DIR") || "/var/lib/kamal-backup" end |
#truthy?(key) ⇒ Boolean
296 297 298 |
# File 'lib/kamal_backup/config.rb', line 296 def truthy?(key) %w[1 true yes y on].include?(value(key).to_s.downcase) end |
#validate_backup(check_files: true) ⇒ Object
203 204 205 206 207 |
# File 'lib/kamal_backup/config.rb', line 203 def validate_backup(check_files: true) validate_restic(check_files: check_files) validate_database_backup(check_files: check_files) validate_backup_paths(check_files: check_files) end |
#validate_backup_paths(check_files: true) ⇒ Object
232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/kamal_backup/config.rb', line 232 def validate_backup_paths(check_files: true) 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}" if check_files && !File.exist?(path) end end |
#validate_database_backup(check_files: true) ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/kamal_backup/config.rb', line 214 def validate_database_backup(check_files: true) 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}" if check_files && !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
266 267 268 269 270 271 272 |
# File 'lib/kamal_backup/config.rb', line 266 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
253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/kamal_backup/config.rb', line 253 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
245 246 247 248 249 250 251 |
# File 'lib/kamal_backup/config.rb', line 245 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
209 210 211 212 |
# File 'lib/kamal_backup/config.rb', line 209 def validate_local_machine_restore validate_local_machine_environment validate_local_machine_paths end |
#validate_restic(check_files: true) ⇒ Object
197 198 199 200 201 |
# File 'lib/kamal_backup/config.rb', line 197 def validate_restic(check_files: true) required_app_name validate_restic_repository(check_files: check_files) validate_restic_password(check_files: check_files) end |
#value(key) ⇒ Object
284 285 286 287 288 289 290 |
# File 'lib/kamal_backup/config.rb', line 284 def value(key) raw = env[key] return nil if raw.nil? stripped = raw.to_s.strip stripped.empty? ? nil : stripped end |