Class: GrimReaper::Installer

Inherits:
Object
  • Object
show all
Defined in:
lib/grim_reaper/installer.rb

Overview

Ruby-specific installer and dependency manager

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeInstaller

Returns a new instance of Installer.



13
14
15
16
17
18
# File 'lib/grim_reaper/installer.rb', line 13

def initialize
  @grim_root = find_grim_root
  @backup_dir = ENV["HOME"] + "/.graveyard"
  @install_dir = ENV["HOME"] + "/reaper"
  setup_environment_variables
end

Instance Attribute Details

#backup_dirObject (readonly)

Returns the value of attribute backup_dir.



11
12
13
# File 'lib/grim_reaper/installer.rb', line 11

def backup_dir
  @backup_dir
end

#grim_rootObject (readonly)

Returns the value of attribute grim_root.



11
12
13
# File 'lib/grim_reaper/installer.rb', line 11

def grim_root
  @grim_root
end

#install_dirObject (readonly)

Returns the value of attribute install_dir.



11
12
13
# File 'lib/grim_reaper/installer.rb', line 11

def install_dir
  @install_dir
end

Class Method Details

.post_installObject

Post-install hook



609
610
611
# File 'lib/grim_reaper/installer.rb', line 609

def self.post_install
  new.setup_complete_environment
end

.post_updateObject

Post-update hook



614
615
616
# File 'lib/grim_reaper/installer.rb', line 614

def self.post_update
  new.verify_installation
end

Instance Method Details

#check_ruby_versionObject

Check Ruby version



399
400
401
402
403
404
405
406
407
# File 'lib/grim_reaper/installer.rb', line 399

def check_ruby_version
  ruby_version = `ruby -e "puts RUBY_VERSION"`.strip
  puts "๐Ÿ’Ž Ruby Version: #{ruby_version}"

  major, minor = ruby_version.split(".").map(&:to_i)
  if major < 2 || (major == 2 && minor < 7)
    raise Error, "Ruby 2.7+ required, found #{ruby_version}"
  end
end

#cleanupObject

Cleanup temporary files



600
601
602
603
604
605
606
# File 'lib/grim_reaper/installer.rb', line 600

def cleanup
  temp_dir = File.join(@grim_root, "temp")
  if Dir.exist?(temp_dir)
    FileUtils.rm_rf(temp_dir)
    puts "๐Ÿงน Cleaned temporary directory"
  end
end

#create_directoriesObject

Create necessary directories



535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/grim_reaper/installer.rb', line 535

def create_directories
  # Setup .scythe structure first
  setup_scythe_directories
  
  dirs = [
    File.join(@grim_root, "logs"),
    File.join(@grim_root, "cache"),
    @backup_dir,
    File.join(@grim_root, "temp"),
    File.join(@grim_root, "py_grim", "venv"),
    File.join(@grim_root, "go_grim", "build"),
    File.join(@grim_root, "scythe", "logs")
  ]

  dirs.each do |dir|
    FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
    puts "๐Ÿ“ Created directory: #{dir}"
  end
end

#download_latest_grimObject

Download and install latest Grim Reaper from get.grim.so



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/grim_reaper/installer.rb', line 46

def download_latest_grim
  puts "๐Ÿ“ฅ Downloading latest Grim Reaper from get.grim.so..."
  
  install_dir = ENV["GRIM_ROOT"] || (ENV["HOME"] + "/.grim")
  FileUtils.mkdir_p(install_dir)
  
  temp_file = File.join(install_dir, "grim-latest.tar.gz")
  
  begin
    # Download latest.tar.gz
    uri = URI("https://get.grim.so/latest.tar.gz")
    Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
      request = Net::HTTP::Get.new(uri)
      response = http.request(request)
      
      if response.code == "200"
        File.open(temp_file, "wb") { |file| file.write(response.body) }
        puts "โœ… Download complete"
      else
        raise "Failed to download: HTTP #{response.code}"
      end
    end
    
    # Extract with proper graveyard/reaper/ handling (strip 2 components)
    puts "๐Ÿ“ฆ Extracting Grim Reaper..."
    success = system("tar", "-xzf", temp_file, "--strip-components=2", "-C", install_dir)
    
    unless success
      raise "Failed to extract tarball"
    end
    
    # Remove temp file
    File.delete(temp_file) if File.exist?(temp_file)
    
    # Make scripts executable
    make_scripts_executable(install_dir)
    
    # Setup environment file
    setup_environment_file(install_dir)
    
    puts "โœ… Grim Reaper installation complete!"
    puts "๐Ÿ“ Installed to: #{install_dir}"
    
    return true
    
  rescue => e
    puts "โŒ Download failed: #{e.message}"
    File.delete(temp_file) if File.exist?(temp_file)
    return false
  end
end

#find_grim_rootObject

Find Grim Reaper root directory

Raises:



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/grim_reaper/installer.rb', line 166

def find_grim_root
  # Try to find from current directory
  current_dir = Dir.pwd
  10.times do
    if File.exist?(File.join(current_dir, "throne", "grim_throne.sh")) ||
       File.exist?(File.join(current_dir, "throne", "rb_grim_throne.sh")) ||
       File.exist?(File.join(current_dir, "tsk_flask", "grim_admin_server.py"))
      return current_dir
    end

    parent_dir = File.dirname(current_dir)
    break if parent_dir == current_dir
    current_dir = parent_dir
  end

  # Try common installation paths
  possible_paths = [
    ENV["HOME"] + "/reaper",
    ENV["HOME"] + "/.reaper",
    "/root/reaper",
    "/root/.reaper",
    "/usr/local/reaper",
    "/usr/share/reaper",
    Dir.pwd
  ]

  possible_paths.each do |path|
    if Dir.exist?(path) && (
      File.exist?(File.join(path, "throne", "grim_throne.sh")) ||
      File.exist?(File.join(path, "throne", "rb_grim_throne.sh")) ||
      File.exist?(File.join(path, "tsk_flask", "grim_admin_server.py"))
    )
      return path
    end
  end

  raise Error, "Could not find Grim Reaper root directory"
end

#install_bundlerObject

Install Bundler



410
411
412
413
414
415
416
417
# File 'lib/grim_reaper/installer.rb', line 410

def install_bundler
  unless system("gem list bundler -i > /dev/null 2>&1")
    puts "๐Ÿ“ฆ Installing Bundler..."
    unless system("gem install bundler")
      raise Error, "Failed to install Bundler"
    end
  end
end

#install_go_componentsObject

Install Go components



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/grim_reaper/installer.rb', line 299

def install_go_components
  puts "๐Ÿน Installing Go components..."
  
  # Check if Go is available
  unless system("go version > /dev/null 2>&1")
    puts "๐Ÿ“ฆ Installing Go..."
    unless system("apt-get update && apt-get install -y golang-go") ||
           system("yum install -y golang")
      puts "โš ๏ธ  Could not install Go"
    end
    return
  end
  
  # Create Go build directory
  go_build_dir = File.join(@grim_root, "go_grim", "build")
  FileUtils.mkdir_p(go_build_dir) unless Dir.exist?(go_build_dir)
  
  # Build Go binaries if source exists
  go_src_dir = File.join(@grim_root, "go_grim")
  if Dir.exist?(go_src_dir) && File.exist?(File.join(go_src_dir, "go.mod"))
    puts "๐Ÿน Building Go binaries..."
    Dir.chdir(go_src_dir) do
      unless system("go mod tidy")
        puts "โš ๏ธ  Failed to tidy Go modules"
      end
      unless system("go build -o build/grim-compression .")
        puts "โš ๏ธ  Failed to build Go binary"
      end
    end
  end
  
  puts "โœ… Go components setup complete"
end

#install_python_componentsObject

Install Python components (like pip package)



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/grim_reaper/installer.rb', line 257

def install_python_components
  puts "๐Ÿ Installing Python components..."
  
  # Check if Python is available
  unless system("python3 --version > /dev/null 2>&1")
    puts "โš ๏ธ  Python3 not found, skipping Python components"
    return
  end
  
  # Install pip if not available
  unless system("pip3 --version > /dev/null 2>&1")
    puts "๐Ÿ“ฆ Installing pip3..."
    unless system("apt-get update && apt-get install -y python3-pip") || 
           system("yum install -y python3-pip")
      puts "โš ๏ธ  Could not install pip3"
    end
  end
  
  # Install Python dependencies
  python_packages = %w[flask requests psutil cryptography]
  python_packages.each do |package|
    unless system("pip3 list | grep -q #{package}")
      puts "๐Ÿ“ฆ Installing Python package: #{package}"
      unless system("pip3 install #{package}")
        puts "โš ๏ธ  Failed to install #{package}"
      end
    end
  end
  
  # Create Python virtual environment if needed
  venv_path = File.join(@grim_root, "py_grim", "venv")
  unless Dir.exist?(venv_path)
    puts "๐Ÿ Creating Python virtual environment..."
    unless system("python3 -m venv #{venv_path}")
      puts "โš ๏ธ  Failed to create virtual environment"
    end
  end
  
  puts "โœ… Python components setup complete"
end

#install_ruby_gemsObject

Install common Ruby gems



420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/grim_reaper/installer.rb', line 420

def install_ruby_gems
  gems = %w[rake rspec rubocop yard reek brakeman bundle-audit]
  
  gems.each do |gem_name|
    unless system("gem list #{gem_name} -i > /dev/null 2>&1")
      puts "๐Ÿ“ฆ Installing #{gem_name}..."
      unless system("gem install #{gem_name}")
        warn "Failed to install #{gem_name}"
      end
    end
  end
end

#install_scythe_componentsObject

Install Scythe components



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/grim_reaper/installer.rb', line 372

def install_scythe_components
  puts "๐Ÿ—ก๏ธ  Installing Scythe components..."
  
  # Check if scythe directory exists
  scythe_dir = File.join(@grim_root, "scythe")
  unless Dir.exist?(scythe_dir)
    puts "โš ๏ธ  Scythe directory not found, creating..."
    FileUtils.mkdir_p(scythe_dir)
  end
  
  # Install Python dependencies for scythe
  if system("python3 --version > /dev/null 2>&1")
    scythe_packages = %w[flask requests cryptography psutil]
    scythe_packages.each do |package|
      unless system("pip3 list | grep -q #{package}")
        puts "๐Ÿ“ฆ Installing Scythe dependency: #{package}"
        unless system("pip3 install #{package}")
          puts "โš ๏ธ  Failed to install #{package}"
        end
      end
    end
  end
  
  puts "โœ… Scythe components setup complete"
end

#install_shell_componentsObject

Install Shell components



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/grim_reaper/installer.rb', line 334

def install_shell_components
  puts "๐Ÿš Installing Shell components..."
  
  # Make shell scripts executable
  shell_scripts = [
    "sh_grim/backup.sh",
    "sh_grim/restore.sh", 
    "sh_grim/monitor.sh",
    "sh_grim/scan.sh",
    "sh_grim/security.sh",
    "throne/grim_throne.sh",
    "throne/rb_grim_throne.sh"
  ]
  
  shell_scripts.each do |script|
    script_path = File.join(@grim_root, script)
    if File.exist?(script_path)
      File.chmod(0o755, script_path)
      puts "๐Ÿ”ง Made executable: #{script}"
    end
  end
  
  # Install system dependencies
  system_deps = %w[rsync tar gzip bzip2 xz-utils]
  system_deps.each do |dep|
    unless system("which #{dep} > /dev/null 2>&1")
      puts "๐Ÿ“ฆ Installing system dependency: #{dep}"
      unless system("apt-get update && apt-get install -y #{dep}") ||
             system("yum install -y #{dep}")
        puts "โš ๏ธ  Could not install #{dep}"
      end
    end
  end
  
  puts "โœ… Shell components setup complete"
end

#make_scripts_executable(install_dir) ⇒ Object

Make scripts executable



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/grim_reaper/installer.rb', line 99

def make_scripts_executable(install_dir)
  puts "๐Ÿ”ง Making scripts executable..."
  
  scripts = [
    "throne/grim_throne.sh",
    "throne/sh_grim_throne.sh", 
    "throne/py_grim_throne.sh",
    "throne/rb_grim_throne.sh",
    "throne/go_grim_throne.sh",
    "install.sh",
    "master-install.sh",
    "sh_grim/*.sh",
    "py_grim/*.py",
    "go_grim/build/*",
    ".rip/*.sh",
    "auto_backups/*.sh"
  ]
  
  scripts.each do |script_pattern|
    Dir.glob(File.join(install_dir, script_pattern)).each do |script|
      begin
        File.chmod(0755, script)
      rescue => e
        puts "โš ๏ธ  Could not make #{script} executable: #{e.message}"
      end
    end
  end
end

#setup_complete_environmentObject

Setup complete Grim Reaper environment (installs Python, Go, Shell, Scythe components)



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/grim_reaper/installer.rb', line 206

def setup_complete_environment
  puts "๐Ÿ—ก๏ธ  Setting up complete Grim Reaper environment..."
  
  # First download the latest version
  unless download_latest_grim
    puts "โŒ Failed to download Grim Reaper"
    return false
  end
  
  # Setup Ruby environment
  setup_ruby_environment
  
  # Install Python components
  install_python_components
  
  # Install Go components
  install_go_components
  
  # Install Shell components
  install_shell_components
  
  # Install Scythe components
  install_scythe_components
  
  # Create necessary directories
  create_directories
  
  puts "โœ… Complete Grim Reaper environment setup finished"
  return true
end

#setup_environment_file(install_dir) ⇒ Object

Setup environment file for persistence



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/grim_reaper/installer.rb', line 129

def setup_environment_file(install_dir)
  puts "๐Ÿ“ Setting up environment file..."
  
  env_file = File.join(install_dir, ".grim_env")
  
  env_content = "    # Grim Reaper Environment Configuration\n    export GRIM_ROOT=\"\#{ENV['GRIM_ROOT']}\"\n    export GRIM_LICENSE=\"\#{ENV['GRIM_LICENSE']}\"\n    export GRIM_REAPER=\"\#{ENV['GRIM_REAPER']}\"\n    export PATH=\"$PATH:\#{install_dir}/throne:\#{install_dir}/sh_grim:\#{install_dir}/py_grim\"\n    \n    # Load Grim functions\n    if [ -f \"\#{install_dir}/throne/grim_throne.sh\" ]; then\n      source \"\#{install_dir}/throne/grim_throne.sh\"\n    fi\n  ENV\n  \n  File.write(env_file, env_content)\n  \n  # Add to bashrc if possible\n  bashrc_path = ENV[\"HOME\"] + \"/.bashrc\"\n  if File.exist?(bashrc_path) && File.writable?(bashrc_path)\n    source_line = \"[ -f \#{env_file} ] && source \#{env_file}\"\n    bashrc_content = File.read(bashrc_path)\n    \n    unless bashrc_content.include?(source_line)\n      File.open(bashrc_path, \"a\") do |f|\n        f.puts \"\\n# Grim Reaper Environment\"\n        f.puts source_line\n      end\n      puts \"\u2705 Added Grim environment to ~/.bashrc\"\n    end\n  end\nend\n"

#setup_environment_variablesObject

Setup environment variables



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/grim_reaper/installer.rb', line 21

def setup_environment_variables
  # Set GRIM_ROOT if not already set
  unless ENV["GRIM_ROOT"]
    if File.writable?("/root/")
      ENV["GRIM_ROOT"] = "/root/.grim"
    elsif File.writable?(ENV["HOME"])
      ENV["GRIM_ROOT"] = ENV["HOME"] + "/.grim"
    else
      ENV["GRIM_ROOT"] = Dir.pwd + "/.grim"
    end
  end
  
  # Set default license tier to FREE
  ENV["GRIM_LICENSE"] ||= "FREE"
  
  # Set GRIM_REAPER to FALSE for normal install
  ENV["GRIM_REAPER"] ||= "FALSE"
  
  puts "๐Ÿ—ก๏ธ  Grim environment configured:"
  puts "   GRIM_ROOT=#{ENV['GRIM_ROOT']}"
  puts "   GRIM_LICENSE=#{ENV['GRIM_LICENSE']}"
  puts "   GRIM_REAPER=#{ENV['GRIM_REAPER']}"
end

#setup_ruby_environmentObject

Setup Ruby environment



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/grim_reaper/installer.rb', line 238

def setup_ruby_environment
  puts "๐Ÿ’Ž Setting up Ruby environment..."

  # Check Ruby version
  check_ruby_version

  # Install Bundler if not present
  install_bundler

  # Install common Ruby gems
  install_ruby_gems

  # Setup RVM/rbenv if available
  setup_ruby_version_manager

  puts "โœ… Ruby environment setup complete"
end

#setup_ruby_version_managerObject

Setup Ruby version manager



434
435
436
437
438
439
440
441
442
# File 'lib/grim_reaper/installer.rb', line 434

def setup_ruby_version_manager
  if system("which rvm > /dev/null 2>&1")
    puts "๐Ÿ”„ RVM detected, setting up..."
    system("rvm use --default")
  elsif system("which rbenv > /dev/null 2>&1")
    puts "๐Ÿ”„ rbenv detected, setting up..."
    system("rbenv rehash")
  end
end

#setup_scythe_directoriesObject

Setup .scythe directory structure



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/grim_reaper/installer.rb', line 445

def setup_scythe_directories
  puts "๐Ÿ—ก๏ธ  Setting up .scythe directory structure..."
  
  scythe_dir = File.join(@grim_root, ".graveyard", ".rip", ".scythe")
  
  # Create main scythe directories
  scythe_subdirs = [
    "config",
    "db", 
    "logs",
    "run",
    "integrations"
  ]
  
  scythe_subdirs.each do |subdir|
    dir_path = File.join(scythe_dir, subdir)
    FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
  end
  
  # Create log subdirectories
  log_subdirs = ["orchestration", "components", "integrations", "security"]
  log_subdirs.each do |subdir|
    dir_path = File.join(scythe_dir, "logs", subdir)
    FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
  end
  
  # Create integration subdirectories
  integration_subdirs = ["discovered", "configs", "scripts"]
  integration_subdirs.each do |subdir|
    dir_path = File.join(scythe_dir, "integrations", subdir)
    FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
  end
  
  # Create scythe configuration file
  config_file = File.join(scythe_dir, "config", "scythe.yaml")
  unless File.exist?(config_file)
    config_content = "      # Scythe Configuration\n      # Central orchestrator settings for Grim Reaper System\n\n      scythe:\n        version: \"1.0.5\"\n        install_date: \#{Time.now.iso8601}\n        \n      database:\n        path: \"../db/scythe.db\"\n        auto_backup: true\n        backup_interval: \"24h\"\n        \n      logging:\n        level: \"info\"\n        path: \"../logs\"\n        max_size: \"100MB\"\n        max_files: 10\n        \n      orchestration:\n        enabled: true\n        heartbeat_interval: \"30s\"\n        max_concurrent_jobs: 5\n        \n      integrations:\n        enabled: true\n        scan_interval: \"5m\"\n        auto_discover: true\n        \n      security:\n        encryption: true\n        key_rotation: \"30d\"\n        audit_logs: true\n    CONFIG\n    \n    File.write(config_file, config_content)\n    puts \"\u2705 Created scythe configuration: \#{config_file}\"\n  end\n  \n  # Try to run the universal setup script if available\n  setup_script = File.join(@grim_root, \"scripts\", \"setup_scythe_dirs.sh\")\n  if File.exist?(setup_script)\n    begin\n      system(\"bash '\#{setup_script}' setup '\#{@grim_root}' auto\")\n      puts \"\u2705 Initialized scythe database\"\n    rescue\n      puts \"\u26A0\uFE0F  Could not initialize scythe database - basic structure created\"\n    end\n  end\n  \n  puts \"\u2705 .scythe directory structure created at: \#{scythe_dir}\"\nend\n"

#statusObject

Get installation status



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/grim_reaper/installer.rb', line 582

def status
  {
    ruby_version: `ruby -e "puts RUBY_VERSION"`.strip,
    bundler_version: `bundle --version 2>/dev/null`.strip,
    python_version: `python3 --version 2>/dev/null | cut -d' ' -f2` || 'Not installed',
    go_version: `go version 2>/dev/null | cut -d' ' -f3` || 'Not installed',
    grim_root: @grim_root,
    backup_dir: @backup_dir,
    install_dir: @install_dir,
    ruby_throne_exists: File.exist?(File.join(@grim_root, "throne", "rb_grim_throne.sh")),
    python_throne_exists: File.exist?(File.join(@grim_root, "throne", "py_grim_throne.sh")),
    go_throne_exists: File.exist?(File.join(@grim_root, "throne", "go_grim_throne.sh")),
    shell_throne_exists: File.exist?(File.join(@grim_root, "throne", "sh_grim_throne.sh")),
    backup_dir_exists: Dir.exist?(@backup_dir)
  }
end

#verify_installationObject

Verify installation



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/grim_reaper/installer.rb', line 556

def verify_installation
  puts "๐Ÿ” Verifying installation..."

  checks = [
    { name: "Ruby", check: -> { system("ruby --version > /dev/null 2>&1") } },
    { name: "Bundler", check: -> { system("bundle --version > /dev/null 2>&1") } },
    { name: "Python3", check: -> { system("python3 --version > /dev/null 2>&1") } },
    { name: "Go", check: -> { system("go version > /dev/null 2>&1") } },
    { name: "Grim Root", check: -> { Dir.exist?(@grim_root) } },
    { name: "Ruby Throne", check: -> { File.exist?(File.join(@grim_root, "throne", "rb_grim_throne.sh")) } },
    { name: "Python Throne", check: -> { File.exist?(File.join(@grim_root, "throne", "py_grim_throne.sh")) } },
    { name: "Go Throne", check: -> { File.exist?(File.join(@grim_root, "throne", "go_grim_throne.sh")) } },
    { name: "Shell Throne", check: -> { File.exist?(File.join(@grim_root, "throne", "sh_grim_throne.sh")) } },
    { name: "Backup Directory", check: -> { Dir.exist?(@backup_dir) } }
  ]

  checks.each do |check|
    if check[:check].call
      puts "โœ… #{check[:name]}: OK"
    else
      puts "โŒ #{check[:name]}: FAILED"
    end
  end
end