#!/usr/bin/env ruby require 'thor' require 'yaml' require 'tempfile' require 'fileutils' class LbhrrCLI < Thor no_commands do def amend_last_commit(new_message) begin # 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 end def create_gitignore gitignore_path = File.join(Dir.pwd, '.gitignore') return if File.exist?(gitignore_path) gitignore_content = %q{ # 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(version) # Git add all changes 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_example_local_config example_local_config = { 'name' => File.basename(Dir.pwd), 'version' => '0', '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) 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 global_config = YAML.load_file(global_config_path) || {} local_config = YAML.load_file(local_config_path) || {} global_config.merge(local_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.open(manifest_path, 'w') { |file| file.write(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 # Include other initialization tasks if necessary end desc "package", "Prepare the application for deployment" def package begin # Load configuration config = load_configuration host = config['host'] user = config['user'] version = config['version'].to_i raise "Configuration error: Host, User, or Version missing" unless host && user && version > 0 # Check for a git repository unless system("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) system("bundle install --path vendor/bundle") 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 end desc "deploy", "Deploy an application using the configuration from config/manifest.yml" def deploy begin 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/#{basename}" versions_directory = "#{base_directory}/versions" destination_path = "#{versions_directory}/#{version}" current_path = "#{base_directory}/current" # Check and create the versions directory on the server system("ssh #{user}@#{host} 'mkdir -p #{versions_directory}'") # 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}'" : "" # Rsync files to the new version directory, excluding files as per .gitignore rsync_command = "rsync -avz #{exclude_option} ./ #{user}@#{host}:#{destination_path}" if system(rsync_command) && system("ssh #{user}@#{host} 'ln -sfn #{destination_path} #{current_path}'") puts "Successfully deployed application version #{version} to #{host}" increment_version(local_manifest_path, version) deployed(version) else puts "Failed to deploy application version #{version} to #{host}" end rescue => e puts "Deployment error: #{e.message}" end end end LbhrrCLI.start(ARGV)