#!/usr/bin/env ruby require_relative "../lib/lbhrr" class LbhrrCLI < Thor no_commands do def manifest? File.join(Dir.pwd, "config","manifest.yml") end def package # Load configuration config = load_configuration host = config["host"] user = config["user"] port = config["port"] version = config["version"].to_i raise "Configuration error: Host, User, or Version missing" unless host && port && user && version > 0 # Check for a git repository unless `git rev-parse --is-inside-work-tree > /dev/null 2>&1` raise "No Git repository found in the current directory" end # Delete vendor directory if it exists vendor_path = File.join(Dir.pwd, "vendor") FileUtils.rm_rf(vendor_path) if Dir.exist?(vendor_path) `bundle config set --local path 'vendor/bundle'` system "bundle install" or raise "Bundle install failed" # Check if the repository is clean puts "Git repository is dirty, committing changes..." system("git add .") or raise "Failed to add changes to Git" system("git commit -m 'packaged #{version}'") or raise "Failed to commit changes to Git" puts "Packaging completed successfully." rescue => e puts "Packaging error: #{e.message}" end def amend_last_commit(new_message) # Ensure we are in a git repository system("git rev-parse --git-dir > /dev/null 2>&1") or raise "Not a git repository" # Amend the last commit with the new message system("git commit --amend -m \"#{new_message}\"") or raise "Failed to amend the last commit" puts "Successfully amended the last commit." rescue => e puts "Error during commit amend: #{e.message}" end def create_gitignore gitignore_path = File.join(Dir.pwd, ".gitignore") return if File.exist?(gitignore_path) gitignore_content = ' # Ignore bundler config and installed gems. /.bundle # Ignore all logfiles and tempfiles. /log/* /tmp/* !/log/.keep !/tmp/.keep # Ignore other unneeded files. *.pid *.swap *.gem *.rbc .DS_Store .idea .byebug_history ' File.write(gitignore_path, gitignore_content.strip) puts ".gitignore created for Rack project." rescue => e puts "Error creating .gitignore: #{e.message}" end def deployed(local_manifest_path, version) # Git add all changes increment_version(local_manifest_path, version) system("git add .") or raise "Failed to add changes to Git" # Commit changes with a message commit_message = "Deployed version #{version}" amend_last_commit commit_message # Create a Git tag for the version tag_message = "Deployment for version #{version}" system("git tag -a 'v#{version}' -m '#{tag_message}'") or raise "Failed to create Git tag" puts "Deployment changes committed and tagged as v#{version}." rescue => e puts "Error during post-deployment process: #{e.message}" end def create_example_global_config example_global_config = { "user" => "root", "host" => "harbr.zero2one.ee" } YAML.dump(example_global_config) end def create_run_file run_file_path = File.join(Dir.pwd, "exe", "run") run_file_content = "#!/bin/sh\n HARBR_ENV=$2 bundle exec puma -p $1" exe_directory = File.join(Dir.pwd, "exe") Dir.mkdir(exe_directory) unless Dir.exist?(exe_directory) File.write(run_file_path, run_file_content) File.chmod(0o755, run_file_path) # Set executable permission puts "Created ./exe/run file." rescue => e puts "Error creating ./exe/run file: #{e.message}" end def create_example_local_config example_local_config = { "name" => File.basename(Dir.pwd), "version" => "0", "port" => "#{File.basename(Dir.pwd)}.app", "host" => "#{File.basename(Dir.pwd)}.harbr.zero2one.ee" } YAML.dump(example_local_config) end def load_configuration global_config_dir = File.expand_path("~/.config/harbr") global_config_path = File.join(global_config_dir, "harbr.manifest.yml") local_config_path = File.join(Dir.pwd, "config", "manifest.yml") # Ensure global configuration exists unless File.exist?(global_config_path) FileUtils.mkdir_p(global_config_dir) unless Dir.exist?(global_config_dir) File.write(global_config_path, create_example_global_config) end # Ensure local configuration exists unless File.exist?(local_config_path) end # Load and merge configurations global_config = YAML.load_file(global_config_path) || {} if File.exist? local_config_path local_config = YAML.load_file(local_config_path) || {} global_config.merge(local_config) end global_config end def increment_version(manifest_path, current_version) new_version = current_version + 1 manifest = YAML.load_file(manifest_path) manifest["version"] = new_version File.write(manifest_path, manifest.to_yaml) puts "Version incremented to #{new_version}" end end desc "init", "Initialize project with .gitignore" def init local_config_path = File.join(Dir.pwd, "config", "manifest.yml") unless File.exist?(local_config_path) FileUtils.mkdir_p(File.dirname(local_config_path)) unless Dir.exist?(File.dirname(local_config_path)) File.write(local_config_path, create_example_local_config) end # Load and merge configurations local_config = YAML.load_file(local_config_path) || {} create_gitignore create_run_file # Include other initialization tasks if necessary end desc "containers", "list all containers" def containers config = load_configuration host = config["host"] user = config["user"] puts `ssh #{user}@#{host} 'harbr containers'` end desc "drop", "Destroy an app and remove all traces" def drop container_name = File.basename(Dir.pwd) config = load_configuration host = config["host"] user = config["user"] puts "Destroying app: #{container_name}" `ssh #{user}@#{host} 'harbr destroy #{container_name}'` unless manifest? puts "no manifest found!" return end current_dir = Dir.pwd parent_dir = File.dirname(current_dir) # Requesting confirmation if yes? "Are you sure you want to delete the directory: #{current_dir}? [y/N]" # Proceeding with deletion puts "Changing directory to the parent directory..." Dir.chdir(parent_dir) puts "Deleting the directory: #{current_dir}" FileUtils.rm_rf(current_dir) Dir.chdir(parent_dir) puts "#{current_dir} has been deleted." end puts "App #{container_name} has been successfully destroyed." end desc "raise", "Deploy an application using the configuration from config/manifest.yml to staging" def raise package config = load_configuration host = config["host"] user = config["user"] raise "Host configuration missing" unless host local_manifest_path = File.join(Dir.pwd, "config", "manifest.yml") raise "Local manifest file not found at #{local_manifest_path}" unless File.exist?(local_manifest_path) local_config = YAML.load_file(local_manifest_path) || {} version = local_config["version"].to_i raise "Version not specified in manifest.yml" unless version basename = File.basename(Dir.pwd) base_directory = "/var/harbr/containers/#{basename}" versions_directory = "#{base_directory}/versions" data_directory = "/var/dddr/#{basename}/" destination_path = "#{versions_directory}/#{version}" # Prepare the rsync exclude option using .gitignore gitignore_path = File.join(Dir.pwd, ".gitignore") exclude_option = File.exist?(gitignore_path) ? "--exclude='.git' --exclude-from='#{gitignore_path}'" : "" # Check and create the versions directory on the server `ssh #{user}@#{host} 'mkdir -p #{versions_directory}'` `ssh #{user}@#{host} 'mkdir -p #{data_directory}'` # Rsync files to the new version directory, excluding files as per .gitignore rsync_command = "rsync -avz #{exclude_option} ./ #{user}@#{host}:#{destination_path}" if `#{rsync_command}` puts "Successfully deployed application version #{version} to #{host}" deployed(local_manifest_path, version) else puts "Failed to deploy application version #{version} to #{host}" end rescue => e puts "Deployment error: #{e.message}" end desc "logs", "Show logs for a container" method_option :live, type: :boolean, aliases: "-l", desc: "Process in live mode" method_option :next, type: :boolean, default: true, aliases: "-n", desc: "Process in next mode" def logs(name=nil) config = load_configuration host = config["host"] user = config["user"] if name.nil? unless manifest? puts "no manifest found!" return end container_name = File.basename(Dir.pwd) else container_name = name end if options[:live] exec "ssh #{user}@#{host} 'harbr peek #{container_name} --live'" else exec "ssh #{user}@#{host} 'harbr peek #{container_name} --next'" end end desc "activity", "harbr logs " def activity container_name = File.basename(Dir.pwd) config = load_configuration host = config["host"] user = config["user"] exec "ssh #{user}@#{host} 'harbr logs'" end desc "hoist", "Deploy an application using the configuration from config/manifest.yml to production" def hoist(name=nil) if name.nil? unless manifest? puts "no manifest found!" return end config = load_configuration host = config["host"] user = config["user"] name = config["name"] end puts `ssh #{user}@#{host} 'harbr deploy #{name}'` end desc "rollback", "rollback an application using the configuration from config/manifest.yml" def rollback(name=nil) if name.nil? unless manifest? puts "no manifest found!" return end config = load_configuration host = config["host"] user = config["user"] name = config["name"] end puts `ssh #{user}@#{host} 'harbr rollback #{name}'` end def self.exit_on_failure? true end end LbhrrCLI.start(ARGV)