Class: Clacky::Server::BackupManager
- Inherits:
-
Object
- Object
- Clacky::Server::BackupManager
- Defined in:
- lib/clacky/server/backup_manager.rb
Overview
Backs up the user’s ~/.clacky directory to a safe location.
Design notes:
* Regenerable caches/logs are always excluded to keep archives small.
* On WSL, the default destination is a Windows drive (/mnt/c|d|e) so
backups survive a WSL distro reset.
* Session history (sessions/ + snapshots/) is optional — it is the
bulk of the data and the user may not want it in every archive.
* Config lives in ~/.clacky/backup.yml, separate from config.yml so
it never mixes with API keys.
Constant Summary collapse
- CLACKY_DIR =
File.("~/.clacky")
- CONFIG_FILE =
File.join(CLACKY_DIR, "backup.yml")
- ALWAYS_EXCLUDE =
Always excluded — regenerable or disposable.
%w[ ocr_cache parsers parsers-1 logger safety_logs trash .DS_Store backup.yml ].freeze
- HEAVY_EXCLUDE =
Excluded unless the user opts into a full backup.
%w[sessions snapshots].freeze
- DEFAULT_CONFIG =
{ "enabled" => false, "cron" => "0 3 * * *", "dest_dir" => nil, "keep" => 7, "include_sessions" => true, "last_run_at" => nil, "last_status" => nil, "last_error" => nil, "last_archive" => nil }.freeze
Class Method Summary collapse
-
.build_download! ⇒ Object
Build a one-off archive for direct download (not written to dest_dir, not pruned, not recorded).
- .config ⇒ Object
-
.default_dest ⇒ Object
Where archives go when the user hasn’t set an explicit dest_dir.
-
.list ⇒ Object
List existing backup archives at the resolved destination.
-
.run! ⇒ Object
Run a backup now.
-
.status ⇒ Object
Resolved destination + whether we’re on WSL (for UI display).
- .update_config(enabled: nil, cron: nil, dest_dir: nil, keep: nil, include_sessions: nil) ⇒ Object
- .wsl? ⇒ Boolean
Class Method Details
.build_download! ⇒ Object
Build a one-off archive for direct download (not written to dest_dir, not pruned, not recorded). Always includes session history so the downloaded file is a complete snapshot. Caller is responsible for deleting the returned temp file after streaming it.
91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/clacky/server/backup_manager.rb', line 91 def build_download! stamp = Time.now.strftime("%Y%m%d-%H%M%S") filename = "clacky-backup-#{stamp}.tar.gz" archive = File.join(Dir.tmpdir, filename) ok = build_archive(archive, ALWAYS_EXCLUDE.dup) unless ok && File.exist?(archive) FileUtils.rm_f(archive) raise "Backup failed: tar did not produce an archive" end { path: archive, filename: filename, size: File.size(archive) } end |
.config ⇒ Object
45 46 47 |
# File 'lib/clacky/server/backup_manager.rb', line 45 def config DEFAULT_CONFIG.merge(load_raw) end |
.default_dest ⇒ Object
Where archives go when the user hasn’t set an explicit dest_dir.
143 144 145 146 147 148 149 150 151 |
# File 'lib/clacky/server/backup_manager.rb', line 143 def default_dest if wsl? %w[d c e].each do |drive| mount = "/mnt/#{drive}" return File.join(mount, "clacky_backups") if Dir.exist?(mount) && File.writable?(mount) end end File.("~/clacky_backups") end |
.list ⇒ Object
List existing backup archives at the resolved destination.
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/clacky/server/backup_manager.rb', line 106 def list dest = resolve_dest(config["dest_dir"]) return [] unless Dir.exist?(dest) Dir.glob(File.join(dest, "clacky-backup-*.tar.gz")).map do |path| { "name" => File.basename(path), "path" => path, "size" => File.size(path), "created_at" => File.mtime(path).iso8601 } end.sort_by { |b| b["created_at"] }.reverse end |
.run! ⇒ Object
Run a backup now. Returns a hash describing the result.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/clacky/server/backup_manager.rb', line 61 def run! cfg = config dest = resolve_dest(cfg["dest_dir"]) FileUtils.mkdir_p(dest) stamp = Time.now.strftime("%Y%m%d-%H%M%S") archive = File.join(dest, "clacky-backup-#{stamp}.tar.gz") excludes = ALWAYS_EXCLUDE.dup excludes.concat(HEAVY_EXCLUDE) unless cfg["include_sessions"] ok = build_archive(archive, excludes) unless ok && File.exist?(archive) record_result(cfg, status: "error", error: "tar failed", archive: nil) raise "Backup failed: tar did not produce an archive" end prune(dest, cfg["keep"]) record_result(cfg, status: "success", error: nil, archive: archive) { archive: archive, size: File.size(archive), dest_dir: dest } rescue => e Clacky::Logger.error("backup_run_error", error: e) if defined?(Clacky::Logger) record_result(config, status: "error", error: e., archive: nil) raise end |
.status ⇒ Object
Resolved destination + whether we’re on WSL (for UI display).
121 122 123 124 125 126 127 128 129 |
# File 'lib/clacky/server/backup_manager.rb', line 121 def status dest = resolve_dest(config["dest_dir"]) { "config" => config, "dest_dir" => dest, "is_wsl" => wsl?, "backups" => list } end |
.update_config(enabled: nil, cron: nil, dest_dir: nil, keep: nil, include_sessions: nil) ⇒ Object
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/clacky/server/backup_manager.rb', line 49 def update_config(enabled: nil, cron: nil, dest_dir: nil, keep: nil, include_sessions: nil) cfg = config cfg["enabled"] = !!enabled unless enabled.nil? cfg["cron"] = cron.to_s unless cron.nil? cfg["dest_dir"] = normalize_dest(dest_dir) unless dest_dir.nil? cfg["keep"] = [keep.to_i, 1].max unless keep.nil? cfg["include_sessions"] = !!include_sessions unless include_sessions.nil? save_raw(cfg) cfg end |
.wsl? ⇒ Boolean
131 132 133 134 135 136 137 138 |
# File 'lib/clacky/server/backup_manager.rb', line 131 def wsl? @wsl ||= begin File.exist?("/proc/version") && File.read("/proc/version").match?(/microsoft|wsl/i) rescue StandardError false end end |