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.
48 49 50 51 |
# File 'lib/kamal_backup/config.rb', line 48 def initialize(env: ENV, cwd: Dir.pwd, defaults: {}) raw_env = env.to_h @env = project_defaults(cwd: cwd).merge(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.
46 47 48 |
# File 'lib/kamal_backup/config.rb', line 46 def env @env end |
Instance Method Details
#accessory_name ⇒ Object
61 62 63 |
# File 'lib/kamal_backup/config.rb', line 61 def accessory_name value("KAMAL_BACKUP_ACCESSORY") end |
#allow_in_place_file_restore? ⇒ Boolean
93 94 95 |
# File 'lib/kamal_backup/config.rb', line 93 def allow_in_place_file_restore? truthy?("KAMAL_BACKUP_ALLOW_IN_PLACE_FILE_RESTORE") end |
#allow_production_restore? ⇒ Boolean
89 90 91 |
# File 'lib/kamal_backup/config.rb', line 89 def allow_production_restore? truthy?("KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE") end |
#allow_suspicious_backup_paths? ⇒ Boolean
97 98 99 |
# File 'lib/kamal_backup/config.rb', line 97 def allow_suspicious_backup_paths? truthy?("KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS") end |
#app_name ⇒ Object
53 54 55 |
# File 'lib/kamal_backup/config.rb', line 53 def app_name value("APP_NAME") end |
#backup_path_label(path) ⇒ Object
144 145 146 147 |
# File 'lib/kamal_backup/config.rb', line 144 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
121 122 123 |
# File 'lib/kamal_backup/config.rb', line 121 def backup_paths split_paths(value("BACKUP_PATHS")) end |
#backup_schedule_seconds ⇒ Object
101 102 103 |
# File 'lib/kamal_backup/config.rb', line 101 def backup_schedule_seconds integer("BACKUP_SCHEDULE_SECONDS", 86_400, minimum: 1) end |
#backup_start_delay_seconds ⇒ Object
105 106 107 |
# File 'lib/kamal_backup/config.rb', line 105 def backup_start_delay_seconds integer("BACKUP_START_DELAY_SECONDS", 0, minimum: 0) end |
#check_after_backup? ⇒ Boolean
77 78 79 |
# File 'lib/kamal_backup/config.rb', line 77 def check_after_backup? truthy?("RESTIC_CHECK_AFTER_BACKUP") end |
#check_read_data_subset ⇒ Object
85 86 87 |
# File 'lib/kamal_backup/config.rb', line 85 def check_read_data_subset value("RESTIC_CHECK_READ_DATA_SUBSET") end |
#database_adapter ⇒ Object
149 150 151 152 153 154 155 156 157 |
# File 'lib/kamal_backup/config.rb', line 149 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
282 283 284 |
# File 'lib/kamal_backup/config.rb', line 282 def falsey?(key) %w[0 false no n off].include?(value(key).to_s.downcase) end |
#forget_after_backup? ⇒ Boolean
81 82 83 |
# File 'lib/kamal_backup/config.rb', line 81 def forget_after_backup? !falsey?("RESTIC_FORGET_AFTER_BACKUP") end |
#last_check_path ⇒ Object
113 114 115 |
# File 'lib/kamal_backup/config.rb', line 113 def last_check_path File.join(state_dir, "last_check.json") end |
#last_restore_drill_path ⇒ Object
117 118 119 |
# File 'lib/kamal_backup/config.rb', line 117 def last_restore_drill_path File.join(state_dir, "last_restore_drill.json") end |
#local_restore_path_pairs ⇒ Object
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/kamal_backup/config.rb', line 133 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
125 126 127 128 129 130 131 |
# File 'lib/kamal_backup/config.rb', line 125 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
256 257 258 259 260 261 262 263 264 |
# File 'lib/kamal_backup/config.rb', line 256 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
57 58 59 |
# File 'lib/kamal_backup/config.rb', line 57 def required_app_name required_value("APP_NAME") end |
#required_value(key) ⇒ Object
274 275 276 |
# File 'lib/kamal_backup/config.rb', line 274 def required_value(key) value(key) || raise(ConfigurationError, "#{key} is required") end |
#restic_init_if_missing? ⇒ Boolean
73 74 75 |
# File 'lib/kamal_backup/config.rb', line 73 def restic_init_if_missing? truthy?("RESTIC_INIT_IF_MISSING") end |
#restic_password ⇒ Object
69 70 71 |
# File 'lib/kamal_backup/config.rb', line 69 def restic_password value("RESTIC_PASSWORD") end |
#restic_repository ⇒ Object
65 66 67 |
# File 'lib/kamal_backup/config.rb', line 65 def restic_repository value("RESTIC_REPOSITORY") end |
#retention ⇒ Object
159 160 161 162 163 |
# File 'lib/kamal_backup/config.rb', line 159 def retention DEFAULT_RETENTION.each_with_object({}) do |(key, default), result| result[key] = value(key) || default end end |
#retention_args ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/kamal_backup/config.rb', line 165 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
109 110 111 |
# File 'lib/kamal_backup/config.rb', line 109 def state_dir value("KAMAL_BACKUP_STATE_DIR") || "/var/lib/kamal-backup" end |
#truthy?(key) ⇒ Boolean
278 279 280 |
# File 'lib/kamal_backup/config.rb', line 278 def truthy?(key) %w[1 true yes y on].include?(value(key).to_s.downcase) end |
#validate_backup ⇒ Object
185 186 187 188 189 |
# File 'lib/kamal_backup/config.rb', line 185 def validate_backup validate_restic validate_database_backup validate_backup_paths end |
#validate_backup_paths ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/kamal_backup/config.rb', line 214 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
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/kamal_backup/config.rb', line 196 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
248 249 250 251 252 253 254 |
# File 'lib/kamal_backup/config.rb', line 248 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
235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/kamal_backup/config.rb', line 235 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
227 228 229 230 231 232 233 |
# File 'lib/kamal_backup/config.rb', line 227 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
191 192 193 194 |
# File 'lib/kamal_backup/config.rb', line 191 def validate_local_machine_restore validate_local_machine_environment validate_local_machine_paths end |
#validate_restic ⇒ Object
179 180 181 182 183 |
# File 'lib/kamal_backup/config.rb', line 179 def validate_restic required_app_name required_value("RESTIC_REPOSITORY") required_value("RESTIC_PASSWORD") end |
#value(key) ⇒ Object
266 267 268 269 270 271 272 |
# File 'lib/kamal_backup/config.rb', line 266 def value(key) raw = env[key] return nil if raw.nil? stripped = raw.to_s.strip stripped.empty? ? nil : stripped end |