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
Instance Attribute Summary collapse
-
#env ⇒ Object
readonly
Returns the value of attribute env.
Instance Method Summary collapse
- #allow_in_place_file_restore? ⇒ Boolean
- #allow_production_restore? ⇒ Boolean
- #allow_restore? ⇒ Boolean
- #allow_suspicious_backup_paths? ⇒ Boolean
- #app_name ⇒ Object
- #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) ⇒ Config
constructor
A new instance of Config.
- #last_check_path ⇒ Object
- #production_like_target?(target) ⇒ Boolean
- #required!(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_paths! ⇒ Object
- #validate_database_backup! ⇒ Object
- #validate_database_restore_target!(target) ⇒ Object
- #validate_file_restore_target!(target) ⇒ Object
- #validate_for_backup! ⇒ Object
- #validate_for_restic! ⇒ Object
- #validate_restore_allowed! ⇒ Object
- #value(key) ⇒ Object
Constructor Details
#initialize(env: ENV) ⇒ Config
Returns a new instance of Config.
21 22 23 |
# File 'lib/kamal_backup/config.rb', line 21 def initialize(env: ENV) @env = env.to_h end |
Instance Attribute Details
#env ⇒ Object (readonly)
Returns the value of attribute env.
19 20 21 |
# File 'lib/kamal_backup/config.rb', line 19 def env @env end |
Instance Method Details
#allow_in_place_file_restore? ⇒ Boolean
65 66 67 |
# File 'lib/kamal_backup/config.rb', line 65 def allow_in_place_file_restore? truthy?("KAMAL_BACKUP_ALLOW_IN_PLACE_FILE_RESTORE") end |
#allow_production_restore? ⇒ Boolean
61 62 63 |
# File 'lib/kamal_backup/config.rb', line 61 def allow_production_restore? truthy?("KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE") end |
#allow_restore? ⇒ Boolean
57 58 59 |
# File 'lib/kamal_backup/config.rb', line 57 def allow_restore? truthy?("KAMAL_BACKUP_ALLOW_RESTORE") end |
#allow_suspicious_backup_paths? ⇒ Boolean
69 70 71 |
# File 'lib/kamal_backup/config.rb', line 69 def allow_suspicious_backup_paths? truthy?("KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS") end |
#app_name ⇒ Object
25 26 27 |
# File 'lib/kamal_backup/config.rb', line 25 def app_name value("APP_NAME") end |
#app_name! ⇒ Object
29 30 31 |
# File 'lib/kamal_backup/config.rb', line 29 def app_name! required!("APP_NAME") end |
#backup_path_label(path) ⇒ Object
94 95 96 97 |
# File 'lib/kamal_backup/config.rb', line 94 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
89 90 91 92 |
# File 'lib/kamal_backup/config.rb', line 89 def backup_paths raw = value("BACKUP_PATHS").to_s raw.split(/[\n:]+/).map(&:strip).reject(&:empty?) end |
#backup_schedule_seconds ⇒ Object
73 74 75 |
# File 'lib/kamal_backup/config.rb', line 73 def backup_schedule_seconds integer("BACKUP_SCHEDULE_SECONDS", 86_400, minimum: 1) end |
#backup_start_delay_seconds ⇒ Object
77 78 79 |
# File 'lib/kamal_backup/config.rb', line 77 def backup_start_delay_seconds integer("BACKUP_START_DELAY_SECONDS", 0, minimum: 0) end |
#check_after_backup? ⇒ Boolean
45 46 47 |
# File 'lib/kamal_backup/config.rb', line 45 def check_after_backup? truthy?("RESTIC_CHECK_AFTER_BACKUP") end |
#check_read_data_subset ⇒ Object
53 54 55 |
# File 'lib/kamal_backup/config.rb', line 53 def check_read_data_subset value("RESTIC_CHECK_READ_DATA_SUBSET") end |
#database_adapter ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/kamal_backup/config.rb', line 99 def database_adapter explicit = value("DATABASE_ADAPTER") return normalize_adapter(explicit) if explicit url = value("DATABASE_URL") if url scheme = URI.parse(url).scheme rescue nil detected = normalize_adapter(scheme) return detected if detected end return "sqlite" if value("SQLITE_DATABASE_PATH") nil end |
#falsey?(key) ⇒ Boolean
244 245 246 |
# File 'lib/kamal_backup/config.rb', line 244 def falsey?(key) %w[0 false no n off].include?(value(key).to_s.downcase) end |
#forget_after_backup? ⇒ Boolean
49 50 51 |
# File 'lib/kamal_backup/config.rb', line 49 def forget_after_backup? !falsey?("RESTIC_FORGET_AFTER_BACKUP") end |
#last_check_path ⇒ Object
85 86 87 |
# File 'lib/kamal_backup/config.rb', line 85 def last_check_path File.join(state_dir, "last_check.json") end |
#production_like_target?(target) ⇒ Boolean
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/kamal_backup/config.rb', line 210 def production_like_target?(target) target = target.to_s source_targets = [ value("DATABASE_URL"), value("SQLITE_DATABASE_PATH"), value("PGDATABASE"), value("MYSQL_DATABASE"), value("MARIADB_DATABASE") ].compact return true if source_targets.include?(target) lowered = target.downcase lowered.include?("production") || lowered.match?(%r{(^|[/_.:-])prod([/_.:-]|$)}) || lowered.match?(%r{(^|[/_.:-])live([/_.:-]|$)}) end |
#required!(key) ⇒ Object
236 237 238 |
# File 'lib/kamal_backup/config.rb', line 236 def required!(key) value(key) || raise(ConfigurationError, "#{key} is required") end |
#restic_init_if_missing? ⇒ Boolean
41 42 43 |
# File 'lib/kamal_backup/config.rb', line 41 def restic_init_if_missing? truthy?("RESTIC_INIT_IF_MISSING") end |
#restic_password ⇒ Object
37 38 39 |
# File 'lib/kamal_backup/config.rb', line 37 def restic_password value("RESTIC_PASSWORD") end |
#restic_repository ⇒ Object
33 34 35 |
# File 'lib/kamal_backup/config.rb', line 33 def restic_repository value("RESTIC_REPOSITORY") end |
#retention ⇒ Object
115 116 117 118 119 |
# File 'lib/kamal_backup/config.rb', line 115 def retention DEFAULT_RETENTION.each_with_object({}) do |(key, default), result| result[key] = value(key) || default end end |
#retention_args ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/kamal_backup/config.rb', line 121 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
81 82 83 |
# File 'lib/kamal_backup/config.rb', line 81 def state_dir value("KAMAL_BACKUP_STATE_DIR") || "/var/lib/kamal-backup" end |
#truthy?(key) ⇒ Boolean
240 241 242 |
# File 'lib/kamal_backup/config.rb', line 240 def truthy?(key) %w[1 true yes y on].include?(value(key).to_s.downcase) end |
#validate_backup_paths! ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/kamal_backup/config.rb', line 165 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
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/kamal_backup/config.rb', line 147 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!("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
202 203 204 205 206 207 208 |
# File 'lib/kamal_backup/config.rb', line 202 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
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/kamal_backup/config.rb', line 184 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 == "/" in_place = backup_paths.any? do |path| = File.(path) == || .start_with?( + "/") || .start_with?( + "/") end if in_place && !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_for_backup! ⇒ Object
141 142 143 144 145 |
# File 'lib/kamal_backup/config.rb', line 141 def validate_for_backup! validate_for_restic! validate_database_backup! validate_backup_paths! end |
#validate_for_restic! ⇒ Object
135 136 137 138 139 |
# File 'lib/kamal_backup/config.rb', line 135 def validate_for_restic! app_name! required!("RESTIC_REPOSITORY") required!("RESTIC_PASSWORD") end |
#validate_restore_allowed! ⇒ Object
178 179 180 181 182 |
# File 'lib/kamal_backup/config.rb', line 178 def validate_restore_allowed! return if allow_restore? raise ConfigurationError, "restore commands require KAMAL_BACKUP_ALLOW_RESTORE=true" end |
#value(key) ⇒ Object
228 229 230 231 232 233 234 |
# File 'lib/kamal_backup/config.rb', line 228 def value(key) raw = env[key] return nil if raw.nil? stripped = raw.to_s.strip stripped.empty? ? nil : stripped end |