lib/kitsune/kit/commands/bootstrap.rb



require "thor"
require "stringio"
require_relative "../defaults"
require_relative "../options_builder"
require_relative "../provisioner"

module Kitsune
  module Kit
    module Commands
      class Bootstrap < Thor
        namespace "bootstrap"

        class_option :rollback, type: :boolean, default: false, desc: "Rollback the setup"
        class_option :keep_server, type: :boolean, default: false, desc: "Keep server after rollback"
        class_option :ssh_port, type: :string, desc: "SSH port for server"
        class_option :ssh_key_path, type: :string, desc: "SSH private key path"

        desc "execute", "Run the full bootstrap process or rollback"
        def execute
          filled_options = Kitsune::Kit::OptionsBuilder.build(
            options,
            defaults: Kitsune::Kit::Defaults.ssh
          )

          if filled_options[:rollback]
            say "🔄 Rolling back server configurations...", :yellow
            rollback_sequence(filled_options)
          else
            say "🏗️ Setting up server from scratch...", :green
            setup_sequence(filled_options)
          end

          say "🎉 Done!", :green
        end

        no_commands do
          def setup_sequence(filled_options)
            droplet_ip = fetch_droplet_ip

            say "→ Droplet IP: #{droplet_ip}", :cyan

            run_cli("setup_user create", droplet_ip, filled_options)
            run_cli("setup_firewall create", droplet_ip, filled_options)
            run_cli("setup_unattended create", droplet_ip, filled_options)
            run_cli("setup_swap create", droplet_ip, filled_options)
            run_cli("setup_do_metrics create", droplet_ip, filled_options)
          end

          def rollback_sequence(filled_options)
            provision_options = Kitsune::Kit::OptionsBuilder.build(
              {},
              defaults: Kitsune::Kit::Defaults::infra
            )

            provisioner = Kitsune::Kit::Provisioner.new(provision_options)

            if (droplet = provisioner.find_droplet).nil?
              say "💡 Nothing to rollback.", :green
              return
            end

            droplet_ip = provisioner.send(:public_ip, droplet)
            say "→ Using Droplet IP: #{droplet_ip}", :cyan

            run_cli("setup_do_metrics rollback", droplet_ip, filled_options)

            if ssh_accessible?(droplet_ip, filled_options)
              run_cli("setup_unattended rollback", droplet_ip, filled_options)
              run_cli("setup_firewall rollback", droplet_ip, filled_options)
            else
              say "⏭️  Skipping unattended-upgrades and firewall rollback (no deploy user)", :yellow
            end

            run_cli("setup_swap rollback", droplet_ip, filled_options)
            run_cli("setup_user rollback", droplet_ip, filled_options)

            unless filled_options[:keep_server]
              say "▶️ Running: kitsune kit provision rollback", :blue
              Kitsune::Kit::CLI.start(%w[provision rollback])
            else
              say "⏭️  Skipping droplet deletion (--keep-server enabled)", :yellow
            end
          end

          def fetch_droplet_ip
            out = StringIO.new
            $stdout = out
            begin
              Kitsune::Kit::CLI.start(["provision", "create"])
            ensure
              $stdout = STDOUT
            end
          
            ip = out.string[/(\d{1,3}\.){3}\d{1,3}/]
            abort "❌ Could not detect droplet IP!" if ip.nil? || ip.empty?
            ip
          end

          def run_cli(command, droplet_ip, filled_options)
            say "\n▶️ Running: kitsune kit #{command} --server-ip #{droplet_ip}", :blue
            subcommand, action = command.split(" ", 2)
            Kitsune::Kit::CLI.start([
              subcommand, action,
              "--server-ip", droplet_ip,
              "--ssh-port", filled_options[:ssh_port],
              "--ssh-key-path", filled_options[:ssh_key_path]
            ])
          rescue SystemExit => e
            abort "❌ Command failed: #{command} (exit #{e.status})"
          end

          def ssh_accessible?(droplet_ip, filled_options)
            system(
              "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new " \
              "-p #{filled_options[:ssh_port]} " \
              "-i #{File.expand_path(filled_options[:ssh_key_path])} " \
              "deploy@#{droplet_ip} true",
              out: File::NULL,
              err: File::NULL
            )
          end
        end
      end
    end
  end
end