Commit c3b9774f4bd0499d1f3d629c4b9bd110e4106b66
1 parent
71266ad2
Exists in
master
and in
4 other branches
Update and add checks
Showing
2 changed files
with
903 additions
and
70 deletions
Show diff stats
lib/tasks/gitlab/check.rake
| 1 | namespace :gitlab do | 1 | namespace :gitlab do |
| 2 | desc "GITLAB | Check the configuration of GitLab and its environment" | 2 | desc "GITLAB | Check the configuration of GitLab and its environment" |
| 3 | task check: %w{gitlab:env:check | 3 | task check: %w{gitlab:env:check |
| 4 | - gitlab:app:check | ||
| 5 | gitlab:gitolite:check | 4 | gitlab:gitolite:check |
| 6 | - gitlab:resque:check} | 5 | + gitlab:resque:check |
| 6 | + gitlab:app:check} | ||
| 7 | + | ||
| 8 | + | ||
| 7 | 9 | ||
| 8 | namespace :app do | 10 | namespace :app do |
| 9 | desc "GITLAB | Check the configuration of the GitLab Rails app" | 11 | desc "GITLAB | Check the configuration of the GitLab Rails app" |
| 10 | task check: :environment do | 12 | task check: :environment do |
| 13 | + warn_user_is_not_gitlab | ||
| 14 | + start_checking "GitLab" | ||
| 15 | + | ||
| 16 | + check_database_config_exists | ||
| 17 | + check_database_is_not_sqlite | ||
| 18 | + check_migrations_are_up | ||
| 19 | + check_gitlab_config_exists | ||
| 20 | + check_gitlab_config_not_outdated | ||
| 21 | + check_log_writable | ||
| 22 | + check_tmp_writable | ||
| 23 | + check_init_script_exists | ||
| 24 | + check_init_script_up_to_date | ||
| 25 | + check_satellites_exist | ||
| 26 | + | ||
| 27 | + finished_checking "GitLab" | ||
| 28 | + end | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + # Checks | ||
| 32 | + ######################## | ||
| 33 | + | ||
| 34 | + def check_database_config_exists | ||
| 35 | + print "Database config exists? ... " | ||
| 36 | + | ||
| 37 | + database_config_file = Rails.root.join("config", "database.yml") | ||
| 38 | + | ||
| 39 | + if File.exists?(database_config_file) | ||
| 40 | + puts "yes".green | ||
| 41 | + else | ||
| 42 | + puts "no".red | ||
| 43 | + try_fixing_it( | ||
| 44 | + "Copy config/database.yml.<your db> to config/database.yml", | ||
| 45 | + "Check that the information in config/database.yml is correct" | ||
| 46 | + ) | ||
| 47 | + for_more_information( | ||
| 48 | + see_database_guide, | ||
| 49 | + "http://guides.rubyonrails.org/getting_started.html#configuring-a-database" | ||
| 50 | + ) | ||
| 51 | + check_failed | ||
| 52 | + end | ||
| 53 | + end | ||
| 54 | + | ||
| 55 | + def check_database_is_not_sqlite | ||
| 56 | + print "Database is not SQLite ... " | ||
| 57 | + | ||
| 58 | + database_config_file = Rails.root.join("config", "database.yml") | ||
| 59 | + | ||
| 60 | + unless File.read(database_config_file) =~ /sqlite/ | ||
| 61 | + puts "yes".green | ||
| 62 | + else | ||
| 63 | + puts "no".red | ||
| 64 | + for_more_information( | ||
| 65 | + "https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL", | ||
| 66 | + see_database_guide | ||
| 67 | + ) | ||
| 68 | + check_failed | ||
| 69 | + end | ||
| 70 | + end | ||
| 71 | + | ||
| 72 | + def check_gitlab_config_exists | ||
| 73 | + print "GitLab config exists? ... " | ||
| 74 | + | ||
| 75 | + gitlab_config_file = Rails.root.join("config", "gitlab.yml") | ||
| 76 | + | ||
| 77 | + if File.exists?(gitlab_config_file) | ||
| 78 | + puts "yes".green | ||
| 79 | + else | ||
| 80 | + puts "no".red | ||
| 81 | + try_fixing_it( | ||
| 82 | + "Copy config/gitlab.yml.example to config/gitlab.yml", | ||
| 83 | + "Update config/gitlab.yml to match your setup" | ||
| 84 | + ) | ||
| 85 | + for_more_information( | ||
| 86 | + see_installation_guide_section "GitLab" | ||
| 87 | + ) | ||
| 88 | + check_failed | ||
| 89 | + end | ||
| 90 | + end | ||
| 91 | + | ||
| 92 | + def check_gitlab_config_not_outdated | ||
| 93 | + print "GitLab config not outdated? ... " | ||
| 94 | + | ||
| 95 | + gitlab_config_file = Rails.root.join("config", "gitlab.yml") | ||
| 96 | + unless File.exists?(gitlab_config_file) | ||
| 97 | + puts "can't check because of previous errors".magenta | ||
| 98 | + end | ||
| 99 | + | ||
| 100 | + # omniauth or ldap could have been deleted from the file | ||
| 101 | + if File.read(gitlab_config_file) =~ /^web:/ | ||
| 102 | + puts "yes".green | ||
| 103 | + else | ||
| 104 | + puts "no".red | ||
| 105 | + try_fixing_it( | ||
| 106 | + "Copy config/gitlab.yml.example to config/gitlab.yml", | ||
| 107 | + "Update config/gitlab.yml to match your setup" | ||
| 108 | + ) | ||
| 109 | + for_more_information( | ||
| 110 | + see_installation_guide_section "GitLab" | ||
| 111 | + ) | ||
| 112 | + check_failed | ||
| 113 | + end | ||
| 114 | + end | ||
| 11 | 115 | ||
| 12 | - print "config/database.yml............" | ||
| 13 | - if File.exists?(Rails.root.join "config", "database.yml") | ||
| 14 | - puts "exists".green | 116 | + def check_init_script_exists |
| 117 | + print "Init script exists? ... " | ||
| 118 | + | ||
| 119 | + script_path = "/etc/init.d/gitlab" | ||
| 120 | + | ||
| 121 | + if File.exists?(script_path) | ||
| 122 | + puts "yes".green | ||
| 15 | else | 123 | else |
| 16 | - puts "missing".red | 124 | + puts "no".red |
| 125 | + try_fixing_it( | ||
| 126 | + "Install the init script" | ||
| 127 | + ) | ||
| 128 | + for_more_information( | ||
| 129 | + see_installation_guide_section "Install Init Script" | ||
| 130 | + ) | ||
| 131 | + check_failed | ||
| 132 | + end | ||
| 133 | + end | ||
| 134 | + | ||
| 135 | + def check_init_script_up_to_date | ||
| 136 | + print "Init script up-to-date? ... " | ||
| 137 | + | ||
| 138 | + script_path = "/etc/init.d/gitlab" | ||
| 139 | + unless File.exists?(script_path) | ||
| 140 | + puts "can't check because of previous errors".magenta | ||
| 17 | return | 141 | return |
| 18 | end | 142 | end |
| 19 | 143 | ||
| 20 | - print "config/gitlab.yml............" | ||
| 21 | - if File.exists?(Rails.root.join "config", "gitlab.yml") | ||
| 22 | - puts "exists".green | 144 | + recipe_content = `curl https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab 2>/dev/null` |
| 145 | + script_content = File.read(script_path) | ||
| 146 | + | ||
| 147 | + if recipe_content == script_content | ||
| 148 | + puts "yes".green | ||
| 149 | + else | ||
| 150 | + puts "no".red | ||
| 151 | + try_fixing_it( | ||
| 152 | + "Redownload the init script" | ||
| 153 | + ) | ||
| 154 | + for_more_information( | ||
| 155 | + see_installation_guide_section "Install Init Script" | ||
| 156 | + ) | ||
| 157 | + check_failed | ||
| 158 | + end | ||
| 159 | + end | ||
| 160 | + | ||
| 161 | + def check_migrations_are_up | ||
| 162 | + print "All migrations up? ... " | ||
| 163 | + | ||
| 164 | + migration_status = `bundle exec rake db:migrate:status` | ||
| 165 | + | ||
| 166 | + unless migration_status =~ /down\s+\d{14}/ | ||
| 167 | + puts "yes".green | ||
| 23 | else | 168 | else |
| 24 | - puts "missing".red | 169 | + puts "no".red |
| 170 | + try_fixing_it( | ||
| 171 | + "sudo -u gitlab -H bundle exec rake db:migrate" | ||
| 172 | + ) | ||
| 173 | + check_failed | ||
| 174 | + end | ||
| 175 | + end | ||
| 176 | + | ||
| 177 | + def check_satellites_exist | ||
| 178 | + print "Projects have satellites? ... " | ||
| 179 | + | ||
| 180 | + unless Project.count > 0 | ||
| 181 | + puts "can't check, you have no projects".magenta | ||
| 25 | return | 182 | return |
| 26 | end | 183 | end |
| 184 | + puts "" | ||
| 185 | + | ||
| 186 | + Project.find_each(batch_size: 100) do |project| | ||
| 187 | + print "#{project.name.yellow} ... " | ||
| 188 | + | ||
| 189 | + if project.satellite.exists? | ||
| 190 | + puts "yes".green | ||
| 191 | + else | ||
| 192 | + puts "no".red | ||
| 193 | + try_fixing_it( | ||
| 194 | + "sudo -u gitlab -H bundle exec rake gitlab:app:enable_automerge" | ||
| 195 | + ) | ||
| 196 | + for_more_information( | ||
| 197 | + "doc/raketasks/maintenance.md " | ||
| 198 | + ) | ||
| 199 | + check_failed | ||
| 200 | + end | ||
| 201 | + end | ||
| 202 | + end | ||
| 203 | + | ||
| 204 | + def check_log_writable | ||
| 205 | + print "Log directory writable? ... " | ||
| 206 | + | ||
| 207 | + log_path = Rails.root.join("log") | ||
| 208 | + | ||
| 209 | + if File.writable?(log_path) | ||
| 210 | + puts "yes".green | ||
| 211 | + else | ||
| 212 | + puts "no".red | ||
| 213 | + try_fixing_it( | ||
| 214 | + "sudo chown -R gitlab #{log_path}", | ||
| 215 | + "sudo chmod -R rwX #{log_path}" | ||
| 216 | + ) | ||
| 217 | + for_more_information( | ||
| 218 | + see_installation_guide_section "GitLab" | ||
| 219 | + ) | ||
| 220 | + check_failed | ||
| 221 | + end | ||
| 222 | + end | ||
| 223 | + | ||
| 224 | + def check_tmp_writable | ||
| 225 | + print "Tmp directory writable? ... " | ||
| 226 | + | ||
| 227 | + tmp_path = Rails.root.join("tmp") | ||
| 228 | + | ||
| 229 | + if File.writable?(tmp_path) | ||
| 230 | + puts "yes".green | ||
| 231 | + else | ||
| 232 | + puts "no".red | ||
| 233 | + try_fixing_it( | ||
| 234 | + "sudo chown -R gitlab #{tmp_path}", | ||
| 235 | + "sudo chmod -R rwX #{tmp_path}" | ||
| 236 | + ) | ||
| 237 | + for_more_information( | ||
| 238 | + see_installation_guide_section "GitLab" | ||
| 239 | + ) | ||
| 240 | + check_failed | ||
| 241 | + end | ||
| 27 | end | 242 | end |
| 28 | end | 243 | end |
| 29 | 244 | ||
| 245 | + | ||
| 246 | + | ||
| 30 | namespace :env do | 247 | namespace :env do |
| 31 | desc "GITLAB | Check the configuration of the environment" | 248 | desc "GITLAB | Check the configuration of the environment" |
| 32 | task check: :environment do | 249 | task check: :environment do |
| 250 | + warn_user_is_not_gitlab | ||
| 251 | + start_checking "Environment" | ||
| 252 | + | ||
| 253 | + check_gitlab_in_git_group | ||
| 254 | + check_issue_1056_shell_profile_error | ||
| 255 | + check_gitlab_git_config | ||
| 256 | + check_python2_exists | ||
| 257 | + check_python2_version | ||
| 258 | + | ||
| 259 | + finished_checking "Environment" | ||
| 260 | + end | ||
| 261 | + | ||
| 262 | + | ||
| 263 | + # Checks | ||
| 264 | + ######################## | ||
| 265 | + | ||
| 266 | + def check_gitlab_git_config | ||
| 267 | + print "Git configured for gitlab user? ... " | ||
| 268 | + | ||
| 269 | + options = { | ||
| 270 | + "user.name" => "GitLab", | ||
| 271 | + "user.email" => Gitlab.config.email_from | ||
| 272 | + } | ||
| 273 | + correct_options = options.map do |name, value| | ||
| 274 | + run("git config --global --get #{name}").try(:squish) == value | ||
| 275 | + end | ||
| 276 | + | ||
| 277 | + if correct_options.all? | ||
| 278 | + puts "yes".green | ||
| 279 | + else | ||
| 280 | + puts "no".red | ||
| 281 | + try_fixing_it( | ||
| 282 | + "sudo -u gitlab -H git config --global user.name \"#{options["user.name"]}\"", | ||
| 283 | + "sudo -u gitlab -H git config --global user.email \"#{options["user.email"]}\"" | ||
| 284 | + ) | ||
| 285 | + for_more_information( | ||
| 286 | + see_installation_guide_section "GitLab" | ||
| 287 | + ) | ||
| 288 | + check_failed | ||
| 289 | + end | ||
| 290 | + end | ||
| 291 | + | ||
| 292 | + def check_gitlab_in_git_group | ||
| 293 | + print "gitlab user is in git group? ... " | ||
| 294 | + | ||
| 295 | + if run_and_match("id -rnG", /\Wgit\W/) | ||
| 296 | + puts "yes".green | ||
| 297 | + else | ||
| 298 | + puts "no".red | ||
| 299 | + try_fixing_it( | ||
| 300 | + "sudo usermod -a -G git gitlab" | ||
| 301 | + ) | ||
| 302 | + for_more_information( | ||
| 303 | + see_installation_guide_section "System Users" | ||
| 304 | + ) | ||
| 305 | + check_failed | ||
| 306 | + end | ||
| 307 | + end | ||
| 308 | + | ||
| 309 | + # see https://github.com/gitlabhq/gitlabhq/issues/1059 | ||
| 310 | + def check_issue_1056_shell_profile_error | ||
| 311 | + print "Has no \"-e\" in ~git/.profile ... " | ||
| 312 | + | ||
| 313 | + profile_file = File.expand_path("~#{Gitlab.config.ssh_user}/.profile") | ||
| 314 | + | ||
| 315 | + unless File.read(profile_file) =~ /^-e PATH/ | ||
| 316 | + puts "yes".green | ||
| 317 | + else | ||
| 318 | + puts "no".red | ||
| 319 | + try_fixing_it( | ||
| 320 | + "Open #{profile_file}", | ||
| 321 | + "Find the line starting with \"-e PATH\"", | ||
| 322 | + "Remove \"-e \" so the line starts with PATH" | ||
| 323 | + ) | ||
| 324 | + for_more_information( | ||
| 325 | + see_installation_guide_section("Gitolite"), | ||
| 326 | + "https://github.com/gitlabhq/gitlabhq/issues/1059" | ||
| 327 | + ) | ||
| 328 | + check_failed | ||
| 329 | + end | ||
| 330 | + end | ||
| 331 | + | ||
| 332 | + def check_python2_exists | ||
| 333 | + print "Has python2? ... " | ||
| 334 | + | ||
| 335 | + # Python prints its version to STDERR | ||
| 336 | + # so we can't just use run("python2 --version") | ||
| 337 | + if run_and_match("which python2", /python2$/) | ||
| 338 | + puts "yes".green | ||
| 339 | + else | ||
| 340 | + puts "no".red | ||
| 341 | + try_fixing_it( | ||
| 342 | + "Make sure you have Python 2.5+ installed", | ||
| 343 | + "Link it to python2" | ||
| 344 | + ) | ||
| 345 | + for_more_information( | ||
| 346 | + see_installation_guide_section "Packages / Dependencies" | ||
| 347 | + ) | ||
| 348 | + check_failed | ||
| 349 | + end | ||
| 350 | + end | ||
| 351 | + | ||
| 352 | + def check_python2_version | ||
| 353 | + print "python2 is supported version? ... " | ||
| 354 | + | ||
| 355 | + # Python prints its version to STDERR | ||
| 356 | + # so we can't just use run("python2 --version") | ||
| 357 | + | ||
| 358 | + unless run_and_match("which python2", /python2$/) | ||
| 359 | + puts "can't check because of previous errors".magenta | ||
| 360 | + return | ||
| 361 | + end | ||
| 362 | + | ||
| 363 | + if `python2 --version 2>&1` =~ /2\.[567]\.\d/ | ||
| 364 | + puts "yes".green | ||
| 365 | + else | ||
| 366 | + puts "no".red | ||
| 367 | + try_fixing_it( | ||
| 368 | + "Make sure you have Python 2.5+ installed", | ||
| 369 | + "Link it to python2" | ||
| 370 | + ) | ||
| 371 | + for_more_information( | ||
| 372 | + see_installation_guide_section "Packages / Dependencies" | ||
| 373 | + ) | ||
| 374 | + check_failed | ||
| 375 | + end | ||
| 33 | end | 376 | end |
| 34 | end | 377 | end |
| 35 | 378 | ||
| 379 | + | ||
| 380 | + | ||
| 36 | namespace :gitolite do | 381 | namespace :gitolite do |
| 37 | desc "GITLAB | Check the configuration of Gitolite" | 382 | desc "GITLAB | Check the configuration of Gitolite" |
| 38 | task check: :environment do | 383 | task check: :environment do |
| 39 | - git_base_path = Gitlab.config.git_base_path | 384 | + warn_user_is_not_gitlab |
| 385 | + start_checking "Gitolite" | ||
| 386 | + | ||
| 387 | + check_gitolite_is_up_to_date | ||
| 388 | + check_gitoliterc_repo_umask | ||
| 389 | + check_gitoliterc_git_config_keys | ||
| 390 | + check_dot_gitolite_exists | ||
| 391 | + check_dot_gitolite_user_and_group | ||
| 392 | + check_dot_gitolite_permissions | ||
| 393 | + check_repo_base_exists | ||
| 394 | + check_repo_base_user_and_group | ||
| 395 | + check_repo_base_permissions | ||
| 396 | + check_can_clone_gitolite_admin | ||
| 397 | + check_can_commit_to_gitolite_admin | ||
| 398 | + check_post_receive_hook_exists | ||
| 399 | + check_post_receive_hook_is_up_to_date | ||
| 400 | + check_repos_post_receive_hooks_is_link | ||
| 401 | + check_repos_git_config | ||
| 402 | + | ||
| 403 | + finished_checking "Gitolite" | ||
| 404 | + end | ||
| 405 | + | ||
| 406 | + | ||
| 407 | + # Checks | ||
| 408 | + ######################## | ||
| 409 | + | ||
| 410 | + def check_can_clone_gitolite_admin | ||
| 411 | + print "Can clone gitolite-admin? ... " | ||
| 412 | + | ||
| 413 | + test_path = "/tmp/gitlab_gitolite_admin_test" | ||
| 414 | + FileUtils.rm_rf(test_path) | ||
| 415 | + `git clone -q #{Gitlab.config.gitolite_admin_uri} #{test_path}` | ||
| 416 | + raise unless $?.success? | ||
| 417 | + | ||
| 418 | + puts "yes".green | ||
| 419 | + rescue | ||
| 420 | + puts "no".red | ||
| 421 | + try_fixing_it( | ||
| 422 | + "Make sure the \"admin_uri\" is set correctly in config/gitlab.yml", | ||
| 423 | + "Try cloning it yourself with:", | ||
| 424 | + " git clone -q #{Gitlab.config.gitolite_admin_uri} /tmp/gitolite-admin", | ||
| 425 | + "Make sure Gitolite is installed correctly." | ||
| 426 | + ) | ||
| 427 | + for_more_information( | ||
| 428 | + see_installation_guide_section "Gitolite" | ||
| 429 | + ) | ||
| 430 | + check_failed | ||
| 431 | + end | ||
| 432 | + | ||
| 433 | + # assumes #check_can_clone_gitolite_admin has been run before | ||
| 434 | + def check_can_commit_to_gitolite_admin | ||
| 435 | + print "Can commit to gitolite-admin? ... " | ||
| 436 | + | ||
| 437 | + test_path = "/tmp/gitlab_gitolite_admin_test" | ||
| 438 | + unless File.exists?(test_path) | ||
| 439 | + puts "can't check because of previous errors".magenta | ||
| 440 | + return | ||
| 441 | + end | ||
| 442 | + | ||
| 443 | + Dir.chdir(test_path) do | ||
| 444 | + `touch foo && git add foo && git commit -qm foo` | ||
| 445 | + raise unless $?.success? | ||
| 446 | + end | ||
| 447 | + | ||
| 448 | + puts "yes".green | ||
| 449 | + rescue | ||
| 450 | + puts "no".red | ||
| 451 | + try_fixing_it( | ||
| 452 | + "Try committing to it yourself with:", | ||
| 453 | + " git clone -q #{Gitlab.config.gitolite_admin_uri} /tmp/gitolite-admin", | ||
| 454 | + " touch foo", | ||
| 455 | + " git add foo", | ||
| 456 | + " git commit -m \"foo\"", | ||
| 457 | + "Make sure Gitolite is installed correctly." | ||
| 458 | + ) | ||
| 459 | + for_more_information( | ||
| 460 | + see_installation_guide_section "Gitolite" | ||
| 461 | + ) | ||
| 462 | + check_failed | ||
| 463 | + ensure | ||
| 464 | + FileUtils.rm_rf("/tmp/gitolite_gitlab_test") | ||
| 465 | + end | ||
| 466 | + | ||
| 467 | + def check_dot_gitolite_exists | ||
| 468 | + print "Config directory exists? ... " | ||
| 469 | + | ||
| 470 | + gitolite_config_path = File.expand_path("~#{Gitlab.config.ssh_user}/.gitolite") | ||
| 471 | + | ||
| 472 | + if File.directory?(gitolite_config_path) | ||
| 473 | + puts "yes".green | ||
| 474 | + else | ||
| 475 | + puts "no".red | ||
| 476 | + puts "#{gitolite_config_path} is missing".red | ||
| 477 | + try_fixing_it( | ||
| 478 | + "This should have been created when setting up Gitolite.", | ||
| 479 | + "Make sure Gitolite is installed correctly." | ||
| 480 | + ) | ||
| 481 | + for_more_information( | ||
| 482 | + see_installation_guide_section "Gitolite" | ||
| 483 | + ) | ||
| 484 | + check_failed | ||
| 485 | + end | ||
| 486 | + end | ||
| 487 | + | ||
| 488 | + def check_dot_gitolite_permissions | ||
| 489 | + print "Config directory access is drwxr-x---? ... " | ||
| 40 | 490 | ||
| 41 | - print "#{git_base_path}............" | ||
| 42 | - if File.exists?(git_base_path) | ||
| 43 | - puts "exists".green | 491 | + gitolite_config_path = File.expand_path("~#{Gitlab.config.ssh_user}/.gitolite") |
| 492 | + unless File.exists?(gitolite_config_path) | ||
| 493 | + puts "can't check because of previous errors".magenta | ||
| 494 | + return | ||
| 495 | + end | ||
| 496 | + | ||
| 497 | + if `stat --printf %a #{gitolite_config_path}` == "750" | ||
| 498 | + puts "yes".green | ||
| 44 | else | 499 | else |
| 45 | - puts "missing".red | 500 | + puts "no".red |
| 501 | + puts "#{gitolite_config_path} is not writable".red | ||
| 502 | + try_fixing_it( | ||
| 503 | + "sudo chmod 750 #{gitolite_config_path}" | ||
| 504 | + ) | ||
| 505 | + for_more_information( | ||
| 506 | + see_installation_guide_section "Gitolite" | ||
| 507 | + ) | ||
| 508 | + check_failed | ||
| 509 | + end | ||
| 510 | + end | ||
| 511 | + | ||
| 512 | + def check_dot_gitolite_user_and_group | ||
| 513 | + print "Config directory owned by git:git? ... " | ||
| 514 | + | ||
| 515 | + gitolite_config_path = File.expand_path("~#{Gitlab.config.ssh_user}/.gitolite") | ||
| 516 | + unless File.exists?(gitolite_config_path) | ||
| 517 | + puts "can't check because of previous errors".magenta | ||
| 46 | return | 518 | return |
| 47 | end | 519 | end |
| 48 | 520 | ||
| 49 | - print "#{git_base_path} is writable?............" | ||
| 50 | - if File.stat(git_base_path).writable? | ||
| 51 | - puts "YES".green | 521 | + if `stat --printf %U #{gitolite_config_path}` == "git" && # user |
| 522 | + `stat --printf %G #{gitolite_config_path}` == "git" #group | ||
| 523 | + puts "yes".green | ||
| 524 | + else | ||
| 525 | + puts "no".red | ||
| 526 | + puts "#{gitolite_config_path} is not owned by git".red | ||
| 527 | + try_fixing_it( | ||
| 528 | + "sudo chown -R git:git #{gitolite_config_path}" | ||
| 529 | + ) | ||
| 530 | + for_more_information( | ||
| 531 | + see_installation_guide_section "Gitolite" | ||
| 532 | + ) | ||
| 533 | + check_failed | ||
| 534 | + end | ||
| 535 | + end | ||
| 536 | + | ||
| 537 | + def check_gitolite_is_up_to_date | ||
| 538 | + print "Using recommended version ... " | ||
| 539 | + if gitolite_version.try(:start_with?, "v3.04") | ||
| 540 | + puts "yes".green | ||
| 541 | + else | ||
| 542 | + puts "no".red | ||
| 543 | + try_fixing_it( | ||
| 544 | + "We strongly recommend using the version pointed out in the installation guide." | ||
| 545 | + ) | ||
| 546 | + for_more_information( | ||
| 547 | + see_installation_guide_section "Gitolite" | ||
| 548 | + ) | ||
| 549 | + # this is not a "hard" failure | ||
| 550 | + end | ||
| 551 | + end | ||
| 552 | + | ||
| 553 | + def check_gitoliterc_git_config_keys | ||
| 554 | + gitoliterc_path = File.join(gitolite_home, ".gitolite.rc") | ||
| 555 | + | ||
| 556 | + print "Allow all Git config keys in .gitolite.rc ... " | ||
| 557 | + option_name = if has_gitolite3? | ||
| 558 | + # see https://github.com/sitaramc/gitolite/blob/v3.04/src/lib/Gitolite/Rc.pm#L329 | ||
| 559 | + "GIT_CONFIG_KEYS" | ||
| 560 | + else | ||
| 561 | + # assume older version | ||
| 562 | + # see https://github.com/sitaramc/gitolite/blob/v2.3/conf/example.gitolite.rc#L49 | ||
| 563 | + "$GL_GITCONFIG_KEYS" | ||
| 564 | + end | ||
| 565 | + option_value = ".*" | ||
| 566 | + if open(gitoliterc_path).grep(/#{option_name}\s*=[>]?\s*["']#{option_value}["']/).any? | ||
| 567 | + puts "yes".green | ||
| 568 | + else | ||
| 569 | + puts "no".red | ||
| 570 | + try_fixing_it( | ||
| 571 | + "Open #{gitoliterc_path}", | ||
| 572 | + "Find the \"#{option_name}\" option", | ||
| 573 | + "Change its value to \".*\"" | ||
| 574 | + ) | ||
| 575 | + for_more_information( | ||
| 576 | + see_installation_guide_section "Gitolite" | ||
| 577 | + ) | ||
| 578 | + check_failed | ||
| 579 | + end | ||
| 580 | + end | ||
| 581 | + | ||
| 582 | + def check_gitoliterc_repo_umask | ||
| 583 | + gitoliterc_path = File.join(gitolite_home, ".gitolite.rc") | ||
| 584 | + | ||
| 585 | + print "Repo umask is 0007 in .gitolite.rc? ... " | ||
| 586 | + option_name = if has_gitolite3? | ||
| 587 | + # see https://github.com/sitaramc/gitolite/blob/v3.04/src/lib/Gitolite/Rc.pm#L328 | ||
| 588 | + "UMASK" | ||
| 589 | + else | ||
| 590 | + # assume older version | ||
| 591 | + # see https://github.com/sitaramc/gitolite/blob/v2.3/conf/example.gitolite.rc#L32 | ||
| 592 | + "$REPO_UMASK" | ||
| 593 | + end | ||
| 594 | + option_value = "0007" | ||
| 595 | + if open(gitoliterc_path).grep(/#{option_name}\s*=[>]?\s*#{option_value}/).any? | ||
| 596 | + puts "yes".green | ||
| 597 | + else | ||
| 598 | + puts "no".red | ||
| 599 | + try_fixing_it( | ||
| 600 | + "Open #{gitoliterc_path}", | ||
| 601 | + "Find the \"#{option_name}\" option", | ||
| 602 | + "Change its value to \"0007\"" | ||
| 603 | + ) | ||
| 604 | + for_more_information( | ||
| 605 | + see_installation_guide_section "Gitolite" | ||
| 606 | + ) | ||
| 607 | + check_failed | ||
| 608 | + end | ||
| 609 | + end | ||
| 610 | + | ||
| 611 | + def check_post_receive_hook_exists | ||
| 612 | + print "post-receive hook exists? ... " | ||
| 613 | + | ||
| 614 | + hook_file = "post-receive" | ||
| 615 | + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") | ||
| 616 | + gitolite_hook_file = File.join(gitolite_hooks_path, hook_file) | ||
| 617 | + | ||
| 618 | + gitlab_hook_file = Rails.root.join.join("lib", "hooks", hook_file) | ||
| 619 | + | ||
| 620 | + if File.exists?(gitolite_hook_file) | ||
| 621 | + puts "yes".green | ||
| 52 | else | 622 | else |
| 53 | - puts "NO".red | 623 | + puts "no".red |
| 624 | + try_fixing_it( | ||
| 625 | + "sudo -u git cp #{gitlab_hook_file} #{gitolite_hook_file}" | ||
| 626 | + ) | ||
| 627 | + for_more_information( | ||
| 628 | + see_installation_guide_section "Setup GitLab Hooks" | ||
| 629 | + ) | ||
| 630 | + check_failed | ||
| 631 | + end | ||
| 632 | + end | ||
| 633 | + | ||
| 634 | + def check_post_receive_hook_is_up_to_date | ||
| 635 | + print "post-receive hook up-to-date? ... " | ||
| 636 | + | ||
| 637 | + hook_file = "post-receive" | ||
| 638 | + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") | ||
| 639 | + gitolite_hook_file = File.join(gitolite_hooks_path, hook_file) | ||
| 640 | + gitolite_hook_content = File.read(gitolite_hook_file) | ||
| 641 | + | ||
| 642 | + unless File.exists?(gitolite_hook_file) | ||
| 643 | + puts "can't check because of previous errors".magenta | ||
| 54 | return | 644 | return |
| 55 | end | 645 | end |
| 56 | 646 | ||
| 57 | - FileUtils.rm_rf("/tmp/gitolite_gitlab_test") | ||
| 58 | - begin | ||
| 59 | - `git clone -q #{Gitlab.config.gitolite_admin_uri} /tmp/gitolite_gitlab_test` | ||
| 60 | - raise unless $?.success? | ||
| 61 | - print "Can clone gitolite-admin?............" | ||
| 62 | - puts "YES".green | ||
| 63 | - rescue | ||
| 64 | - print "Can clone gitolite-admin?............" | ||
| 65 | - puts "NO".red | 647 | + gitlab_hook_file = Rails.root.join.join("lib", "hooks", hook_file) |
| 648 | + gitlab_hook_content = File.read(gitlab_hook_file) | ||
| 649 | + | ||
| 650 | + if gitolite_hook_content == gitlab_hook_content | ||
| 651 | + puts "yes".green | ||
| 652 | + else | ||
| 653 | + puts "no".red | ||
| 654 | + try_fixing_it( | ||
| 655 | + "sudo -u git cp #{gitlab_hook_file} #{gitolite_hook_file}" | ||
| 656 | + ) | ||
| 657 | + for_more_information( | ||
| 658 | + see_installation_guide_section "Setup GitLab Hooks" | ||
| 659 | + ) | ||
| 660 | + check_failed | ||
| 661 | + end | ||
| 662 | + end | ||
| 663 | + | ||
| 664 | + def check_repo_base_exists | ||
| 665 | + print "Repo base directory exists? ... " | ||
| 666 | + | ||
| 667 | + repo_base_path = Gitlab.config.git_base_path | ||
| 668 | + | ||
| 669 | + if File.exists?(repo_base_path) | ||
| 670 | + puts "yes".green | ||
| 671 | + else | ||
| 672 | + puts "no".red | ||
| 673 | + puts "#{repo_base_path} is missing".red | ||
| 674 | + try_fixing_it( | ||
| 675 | + "This should have been created when setting up Gitolite.", | ||
| 676 | + "Make sure it's set correctly in config/gitlab.yml", | ||
| 677 | + "Make sure Gitolite is installed correctly." | ||
| 678 | + ) | ||
| 679 | + for_more_information( | ||
| 680 | + see_installation_guide_section "Gitolite" | ||
| 681 | + ) | ||
| 682 | + check_failed | ||
| 683 | + end | ||
| 684 | + end | ||
| 685 | + | ||
| 686 | + def check_repo_base_permissions | ||
| 687 | + print "Repo base access is drwsrws---? ... " | ||
| 688 | + | ||
| 689 | + repo_base_path = Gitlab.config.git_base_path | ||
| 690 | + unless File.exists?(repo_base_path) | ||
| 691 | + puts "can't check because of previous errors".magenta | ||
| 66 | return | 692 | return |
| 67 | end | 693 | end |
| 68 | 694 | ||
| 69 | - begin | ||
| 70 | - Dir.chdir("/tmp/gitolite_gitlab_test") do | ||
| 71 | - `touch blah && git add blah && git commit -qm blah -- blah` | ||
| 72 | - raise unless $?.success? | ||
| 73 | - end | ||
| 74 | - print "Can git commit?............" | ||
| 75 | - puts "YES".green | ||
| 76 | - rescue | ||
| 77 | - print "Can git commit?............" | ||
| 78 | - puts "NO".red | 695 | + if `stat --printf %a #{repo_base_path}` == "6770" |
| 696 | + puts "yes".green | ||
| 697 | + else | ||
| 698 | + puts "no".red | ||
| 699 | + puts "#{repo_base_path} is not writable".red | ||
| 700 | + try_fixing_it( | ||
| 701 | + "sudo chmod -R ug+rwXs,o-rwx #{repo_base_path}" | ||
| 702 | + ) | ||
| 703 | + for_more_information( | ||
| 704 | + see_installation_guide_section "Gitolite" | ||
| 705 | + ) | ||
| 706 | + check_failed | ||
| 707 | + end | ||
| 708 | + end | ||
| 709 | + | ||
| 710 | + def check_repo_base_user_and_group | ||
| 711 | + print "Repo base owned by git:git? ... " | ||
| 712 | + | ||
| 713 | + repo_base_path = Gitlab.config.git_base_path | ||
| 714 | + unless File.exists?(repo_base_path) | ||
| 715 | + puts "can't check because of previous errors".magenta | ||
| 79 | return | 716 | return |
| 80 | - ensure | ||
| 81 | - FileUtils.rm_rf("/tmp/gitolite_gitlab_test") | ||
| 82 | end | 717 | end |
| 83 | 718 | ||
| 84 | - print "UMASK for .gitolite.rc is 0007? ............" | ||
| 85 | - if open(File.absolute_path("#{git_base_path}/../.gitolite.rc")).grep(/UMASK([ \t]*)=([ \t>]*)0007/).any? | ||
| 86 | - puts "YES".green | 719 | + if `stat --printf %U #{repo_base_path}` == "git" && # user |
| 720 | + `stat --printf %G #{repo_base_path}` == "git" #group | ||
| 721 | + puts "yes".green | ||
| 87 | else | 722 | else |
| 88 | - puts "NO".red | 723 | + puts "no".red |
| 724 | + puts "#{repo_base_path} is not owned by git".red | ||
| 725 | + try_fixing_it( | ||
| 726 | + "sudo chown -R git:git #{repo_base_path}" | ||
| 727 | + ) | ||
| 728 | + for_more_information( | ||
| 729 | + see_installation_guide_section "Gitolite" | ||
| 730 | + ) | ||
| 731 | + check_failed | ||
| 732 | + end | ||
| 733 | + end | ||
| 734 | + | ||
| 735 | + def check_repos_git_config | ||
| 736 | + print "Git config in repos: ... " | ||
| 737 | + | ||
| 738 | + unless Project.count > 0 | ||
| 739 | + puts "can't check, you have no projects".magenta | ||
| 89 | return | 740 | return |
| 90 | end | 741 | end |
| 742 | + puts "" | ||
| 91 | 743 | ||
| 92 | - gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") | ||
| 93 | - gitlab_hook_files = ['post-receive'] | ||
| 94 | - gitlab_hook_files.each do |file_name| | ||
| 95 | - dest = File.join(gitolite_hooks_path, file_name) | ||
| 96 | - print "#{dest} exists? ............" | ||
| 97 | - if File.exists?(dest) | ||
| 98 | - puts "YES".green | 744 | + options = { |
| 745 | + "core.sharedRepository" => "0660", | ||
| 746 | + } | ||
| 747 | + | ||
| 748 | + Project.find_each(batch_size: 100) do |project| | ||
| 749 | + print "#{project.name.yellow} ... " | ||
| 750 | + | ||
| 751 | + correct_options = options.map do |name, value| | ||
| 752 | + run("git --git-dir=\"#{project.path_to_repo}\" config --get #{name}").try(:chomp) == value | ||
| 753 | + end | ||
| 754 | + | ||
| 755 | + if correct_options.all? | ||
| 756 | + puts "ok".green | ||
| 99 | else | 757 | else |
| 100 | - puts "NO".red | ||
| 101 | - return | 758 | + puts "wrong or missing".red |
| 759 | + try_fixing_it( | ||
| 760 | + "sudo -u gitlab -H bundle exec rake gitlab:gitolite:update_repos" | ||
| 761 | + ) | ||
| 762 | + for_more_information( | ||
| 763 | + "doc/raketasks/maintenance.md" | ||
| 764 | + ) | ||
| 765 | + check_failed | ||
| 102 | end | 766 | end |
| 103 | end | 767 | end |
| 768 | + end | ||
| 769 | + | ||
| 770 | + def check_repos_post_receive_hooks_is_link | ||
| 771 | + print "post-receive hooks in repos are links: ... " | ||
| 772 | + | ||
| 773 | + hook_file = "post-receive" | ||
| 774 | + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") | ||
| 775 | + gitolite_hook_file = File.join(gitolite_hooks_path, hook_file) | ||
| 776 | + | ||
| 777 | + unless File.exists?(gitolite_hook_file) | ||
| 778 | + puts "can't check because of previous errors".magenta | ||
| 779 | + return | ||
| 780 | + end | ||
| 104 | 781 | ||
| 105 | - if Project.count > 0 | ||
| 106 | - puts "\nValidating projects repositories:".yellow | ||
| 107 | - Project.find_each(:batch_size => 100) do |project| | ||
| 108 | - print "* #{project.name}....." | ||
| 109 | - hook_file = File.join(project.path_to_repo, 'hooks', 'post-receive') | 782 | + unless Project.count > 0 |
| 783 | + puts "can't check, you have no projects".magenta | ||
| 784 | + return | ||
| 785 | + end | ||
| 786 | + puts "" | ||
| 110 | 787 | ||
| 111 | - unless File.exists?(hook_file) | ||
| 112 | - puts "post-receive file missing".red | ||
| 113 | - next | ||
| 114 | - end | 788 | + Project.find_each(batch_size: 100) do |project| |
| 789 | + print "#{project.name.yellow} ... " | ||
| 790 | + project_hook_file = File.join(project.path_to_repo, "hooks", hook_file) | ||
| 115 | 791 | ||
| 116 | - original_content = File.read(Rails.root.join('lib', 'hooks', 'post-receive')) | ||
| 117 | - new_content = File.read(hook_file) | 792 | + unless File.exists?(project_hook_file) |
| 793 | + puts "missing".red | ||
| 794 | + try_fixing_it( | ||
| 795 | + "sudo -u git ln -sf #{gitolite_hook_file} #{project_hook_file}" | ||
| 796 | + ) | ||
| 797 | + for_more_information( | ||
| 798 | + "lib/support/rewrite-hooks.sh" | ||
| 799 | + ) | ||
| 800 | + check_failed | ||
| 801 | + next | ||
| 802 | + end | ||
| 118 | 803 | ||
| 119 | - if original_content == new_content | ||
| 120 | - puts "post-receive file ok".green | ||
| 121 | - else | ||
| 122 | - puts "post-receive file content does not match".red | ||
| 123 | - end | 804 | + if run_and_match("stat --format %N #{project_hook_file}", /#{hook_file}.+->.+#{gitolite_hook_file}/) |
| 805 | + puts "ok".green | ||
| 806 | + else | ||
| 807 | + puts "not a link to Gitolite's hook".red | ||
| 808 | + try_fixing_it( | ||
| 809 | + "sudo -u git ln -sf #{gitolite_hook_file} #{project_hook_file}" | ||
| 810 | + ) | ||
| 811 | + for_more_information( | ||
| 812 | + "lib/support/rewrite-hooks.sh" | ||
| 813 | + ) | ||
| 814 | + check_failed | ||
| 124 | end | 815 | end |
| 125 | end | 816 | end |
| 126 | end | 817 | end |
| 818 | + | ||
| 819 | + | ||
| 820 | + # Helper methods | ||
| 821 | + ######################## | ||
| 822 | + | ||
| 823 | + def gitolite_home | ||
| 824 | + File.expand_path("~#{Gitlab.config.ssh_user}") | ||
| 825 | + end | ||
| 826 | + | ||
| 827 | + def gitolite_version | ||
| 828 | + gitolite_version_file = "#{gitolite_home}/gitolite/src/VERSION" | ||
| 829 | + if File.readable?(gitolite_version_file) | ||
| 830 | + File.read(gitolite_version_file) | ||
| 831 | + end | ||
| 832 | + end | ||
| 833 | + | ||
| 834 | + def has_gitolite3? | ||
| 835 | + gitolite_version.try(:start_with?, "v3.") | ||
| 836 | + end | ||
| 127 | end | 837 | end |
| 128 | 838 | ||
| 839 | + | ||
| 840 | + | ||
| 129 | namespace :resque do | 841 | namespace :resque do |
| 130 | desc "GITLAB | Check the configuration of Resque" | 842 | desc "GITLAB | Check the configuration of Resque" |
| 131 | task check: :environment do | 843 | task check: :environment do |
| 844 | + warn_user_is_not_gitlab | ||
| 845 | + start_checking "Resque" | ||
| 846 | + | ||
| 847 | + check_resque_running | ||
| 848 | + | ||
| 849 | + finished_checking "Resque" | ||
| 850 | + end | ||
| 851 | + | ||
| 852 | + | ||
| 853 | + # Checks | ||
| 854 | + ######################## | ||
| 855 | + | ||
| 856 | + def check_resque_running | ||
| 857 | + print "Running? ... " | ||
| 858 | + | ||
| 859 | + if run_and_match("ps aux | grep -i resque", /resque-[\d\.]+:.+$/) | ||
| 860 | + puts "yes".green | ||
| 861 | + else | ||
| 862 | + puts "no".red | ||
| 863 | + puts "#{repo_base_path} is not owned by git".red | ||
| 864 | + try_fixing_it( | ||
| 865 | + "sudo service gitlab restart", | ||
| 866 | + "or", | ||
| 867 | + "sudo /etc/init.d/gitlab restart" | ||
| 868 | + ) | ||
| 869 | + for_more_information( | ||
| 870 | + see_installation_guide_section("Install Init Script"), | ||
| 871 | + "see log/resque.log for possible errors" | ||
| 872 | + ) | ||
| 873 | + check_failed | ||
| 874 | + end | ||
| 875 | + end | ||
| 876 | + end | ||
| 877 | + | ||
| 878 | + | ||
| 879 | + # Helper methods | ||
| 880 | + ########################## | ||
| 881 | + | ||
| 882 | + def check_failed | ||
| 883 | + puts " Please #{"fix the error above"} and rerun the checks.".red | ||
| 884 | + end | ||
| 885 | + | ||
| 886 | + def for_more_information(*sources) | ||
| 887 | + sources = sources.shift if sources.first.is_a?(Array) | ||
| 888 | + | ||
| 889 | + puts " For more information see:".blue | ||
| 890 | + sources.each do |source| | ||
| 891 | + puts " #{source}" | ||
| 892 | + end | ||
| 893 | + end | ||
| 894 | + | ||
| 895 | + def finished_checking(component) | ||
| 896 | + puts "" | ||
| 897 | + puts "Checking #{component.yellow} ... #{"Finished".green}" | ||
| 898 | + puts "" | ||
| 899 | + end | ||
| 900 | + | ||
| 901 | + # Runs the given command | ||
| 902 | + # | ||
| 903 | + # Returns nil if the command was not found | ||
| 904 | + # Returns the output of the command otherwise | ||
| 905 | + # | ||
| 906 | + # see also #run_and_match | ||
| 907 | + def run(command) | ||
| 908 | + unless `#{command} 2>/dev/null`.blank? | ||
| 909 | + `#{command}` | ||
| 910 | + end | ||
| 911 | + end | ||
| 912 | + | ||
| 913 | + # Runs the given command and matches the output agains the given pattern | ||
| 914 | + # | ||
| 915 | + # Returns nil if nothing matched | ||
| 916 | + # Retunrs the MatchData if the pattern matched | ||
| 917 | + # | ||
| 918 | + # see also #run | ||
| 919 | + # see also String#match | ||
| 920 | + def run_and_match(command, pattern) | ||
| 921 | + run(command).try(:match, pattern) | ||
| 922 | + end | ||
| 923 | + | ||
| 924 | + def see_database_guide | ||
| 925 | + "doc/install/databases.md" | ||
| 926 | + end | ||
| 927 | + | ||
| 928 | + def see_installation_guide_section(section) | ||
| 929 | + "doc/install/installation.md in section \"#{section}\"" | ||
| 930 | + end | ||
| 931 | + | ||
| 932 | + def start_checking(component) | ||
| 933 | + puts "Checking #{component.yellow} ..." | ||
| 934 | + puts "" | ||
| 935 | + end | ||
| 936 | + | ||
| 937 | + def try_fixing_it(*steps) | ||
| 938 | + steps = steps.shift if steps.first.is_a?(Array) | ||
| 939 | + | ||
| 940 | + puts " Try fixing it:".blue | ||
| 941 | + steps.each do |step| | ||
| 942 | + puts " #{step}" | ||
| 943 | + end | ||
| 944 | + end | ||
| 945 | + | ||
| 946 | + def warn_user_is_not_gitlab | ||
| 947 | + unless @warned_user_not_gitlab | ||
| 948 | + current_user = run("whoami").chomp | ||
| 949 | + unless current_user == "gitlab" | ||
| 950 | + puts "#{Colored.color(:black)+Colored.color(:on_yellow)} Warning #{Colored.extra(:clear)}" | ||
| 951 | + puts " You are running as user #{current_user.magenta}, we hope you know what you are doing." | ||
| 952 | + puts " Some tests may pass\/fail for the wrong reason." | ||
| 953 | + puts " For meaningful results you should run this as user #{"gitlab".magenta}." | ||
| 954 | + puts "" | ||
| 955 | + end | ||
| 956 | + @warned_user_not_gitlab = true | ||
| 132 | end | 957 | end |
| 133 | end | 958 | end |
| 134 | end | 959 | end |
lib/tasks/gitlab/info.rake
| @@ -84,7 +84,13 @@ namespace :gitlab do | @@ -84,7 +84,13 @@ namespace :gitlab do | ||
| 84 | 84 | ||
| 85 | # Helper methods | 85 | # Helper methods |
| 86 | 86 | ||
| 87 | - # Runs the given command and matches the output agains the given RegExp | 87 | + # Runs the given command and matches the output agains the given pattern |
| 88 | + # | ||
| 89 | + # Returns nil if nothing matched | ||
| 90 | + # Retunrs the MatchData if the pattern matched | ||
| 91 | + # | ||
| 92 | + # see also #run | ||
| 93 | + # see also String#match | ||
| 88 | def run_and_match(command, regexp) | 94 | def run_and_match(command, regexp) |
| 89 | run(command).try(:match, regexp) | 95 | run(command).try(:match, regexp) |
| 90 | end | 96 | end |
| @@ -93,6 +99,8 @@ namespace :gitlab do | @@ -93,6 +99,8 @@ namespace :gitlab do | ||
| 93 | # | 99 | # |
| 94 | # Returns nil if the command was not found | 100 | # Returns nil if the command was not found |
| 95 | # Returns the output of the command otherwise | 101 | # Returns the output of the command otherwise |
| 102 | + # | ||
| 103 | + # see also #run_and_match | ||
| 96 | def run(command) | 104 | def run(command) |
| 97 | unless `#{command} 2>/dev/null`.blank? | 105 | unless `#{command} 2>/dev/null`.blank? |
| 98 | `#{command}` | 106 | `#{command}` |