kitsune-kit 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +143 -2
- data/CONTRIBUTING.md +83 -0
- data/README.md +112 -135
- data/SECURITY.md +25 -0
- data/bin/kit +4 -1
- data/docs/architecture/decisions/0001-product-boundary.md +23 -0
- data/docs/architecture/decisions/0002-core-and-interfaces.md +23 -0
- data/docs/architecture/decisions/0003-configuration-state-and-secrets.md +23 -0
- data/docs/architecture/decisions/0004-supported-platforms.md +19 -0
- data/docs/architecture/decisions/0005-operation-semantics.md +19 -0
- data/docs/architecture/decisions/0006-tui-backend.md +21 -0
- data/docs/architecture/decisions/0007-compose-customization.md +30 -0
- data/docs/architecture/provider-adapters.md +49 -0
- data/docs/architecture.md +93 -0
- data/docs/commands.md +224 -0
- data/docs/configuration.md +226 -0
- data/docs/getting-started.md +133 -0
- data/docs/providers/digitalocean.md +52 -0
- data/docs/releasing.md +43 -0
- data/docs/roadmap.md +46 -0
- data/docs/security-audit.md +130 -0
- data/docs/security.md +116 -0
- data/docs/services/compose.md +164 -0
- data/docs/services/postgres.md +78 -0
- data/docs/services/redis.md +53 -0
- data/docs/testing.md +200 -0
- data/docs/troubleshooting.md +123 -0
- data/docs/tui.md +57 -0
- data/lib/kitsune/kit/adapters/confirming_host_key_verifier.rb +42 -0
- data/lib/kitsune/kit/adapters/digitalocean_provider.rb +217 -0
- data/lib/kitsune/kit/adapters/fake_clock.rb +34 -0
- data/lib/kitsune/kit/adapters/fake_provider.rb +107 -0
- data/lib/kitsune/kit/adapters/fake_reporter.rb +34 -0
- data/lib/kitsune/kit/adapters/fake_secret_store.rb +35 -0
- data/lib/kitsune/kit/adapters/fake_state_store.rb +91 -0
- data/lib/kitsune/kit/adapters/fake_transport.rb +56 -0
- data/lib/kitsune/kit/adapters/net_ssh_transport.rb +155 -0
- data/lib/kitsune/kit/adapters/provider.rb +34 -0
- data/lib/kitsune/kit/adapters/transport.rb +22 -0
- data/lib/kitsune/kit/adapters/transport_factory.rb +116 -0
- data/lib/kitsune/kit/application.rb +170 -0
- data/lib/kitsune/kit/cancellation.rb +21 -0
- data/lib/kitsune/kit/cli.rb +879 -64
- data/lib/kitsune/kit/clock.rb +11 -0
- data/lib/kitsune/kit/configuration.rb +577 -0
- data/lib/kitsune/kit/errors.rb +98 -0
- data/lib/kitsune/kit/events.rb +55 -0
- data/lib/kitsune/kit/operations/ensure_dns_records.rb +165 -0
- data/lib/kitsune/kit/operations/ensure_server.rb +135 -0
- data/lib/kitsune/kit/operations/ensure_service.rb +302 -0
- data/lib/kitsune/kit/operations/remote_script.rb +214 -0
- data/lib/kitsune/kit/operations/service_backup.rb +71 -0
- data/lib/kitsune/kit/operations/service_files.rb +131 -0
- data/lib/kitsune/kit/operations/service_firewall.rb +190 -0
- data/lib/kitsune/kit/operations/service_state.rb +59 -0
- data/lib/kitsune/kit/plan.rb +72 -0
- data/lib/kitsune/kit/reporters/human.rb +89 -0
- data/lib/kitsune/kit/reporters/json.rb +65 -0
- data/lib/kitsune/kit/reporters/reporter.rb +11 -0
- data/lib/kitsune/kit/result.rb +28 -0
- data/lib/kitsune/kit/run_journal.rb +102 -0
- data/lib/kitsune/kit/run_logger.rb +37 -0
- data/lib/kitsune/kit/scripts/docker.sh +142 -0
- data/lib/kitsune/kit/scripts/firewall.sh +210 -0
- data/lib/kitsune/kit/scripts/metrics.sh +59 -0
- data/lib/kitsune/kit/scripts/ssh.sh +95 -0
- data/lib/kitsune/kit/scripts/swap.sh +86 -0
- data/lib/kitsune/kit/scripts/unattended.sh +86 -0
- data/lib/kitsune/kit/scripts/user.sh +114 -0
- data/lib/kitsune/kit/secret_filter.rb +54 -0
- data/lib/kitsune/kit/secret_store.rb +32 -0
- data/lib/kitsune/kit/secret_stores/store.rb +12 -0
- data/lib/kitsune/kit/service_compose.rb +272 -0
- data/lib/kitsune/kit/state_store.rb +158 -0
- data/lib/kitsune/kit/state_stores/store.rb +15 -0
- data/lib/kitsune/kit/tui/actions.rb +72 -0
- data/lib/kitsune/kit/tui/application.rb +35 -0
- data/lib/kitsune/kit/tui/controller.rb +162 -0
- data/lib/kitsune/kit/tui/renderer.rb +134 -0
- data/lib/kitsune/kit/tui/state.rb +18 -0
- data/lib/kitsune/kit/tui/store.rb +88 -0
- data/lib/kitsune/kit/tui/terminal.rb +95 -0
- data/lib/kitsune/kit/version.rb +1 -1
- data/lib/kitsune/kit/workflows/apply_plan.rb +145 -0
- data/lib/kitsune/kit/workflows/base.rb +31 -0
- data/lib/kitsune/kit/workflows/build_plan.rb +33 -0
- data/lib/kitsune/kit/workflows/destroy_server.rb +84 -0
- data/lib/kitsune/kit/workflows/doctor.rb +225 -0
- data/lib/kitsune/kit/workflows/eject_compose.rb +82 -0
- data/lib/kitsune/kit/workflows/environment_selection.rb +70 -0
- data/lib/kitsune/kit/workflows/import_server.rb +100 -0
- data/lib/kitsune/kit/workflows/initialize_project.rb +129 -0
- data/lib/kitsune/kit/workflows/inspect_environment.rb +44 -0
- data/lib/kitsune/kit/workflows/rollback.rb +54 -0
- data/lib/kitsune/kit/workflows/support_bundle.rb +82 -0
- data/lib/kitsune/kit.rb +42 -2
- metadata +125 -79
- data/.rspec +0 -3
- data/Rakefile +0 -8
- data/kitsune-kit-logo.jpg +0 -0
- data/lib/kitsune/blueprints/.env.template +0 -31
- data/lib/kitsune/blueprints/docker/postgres.yml +0 -27
- data/lib/kitsune/blueprints/docker/redis.yml +0 -23
- data/lib/kitsune/blueprints/kit.env.template +0 -1
- data/lib/kitsune/kit/ansi_color.rb +0 -78
- data/lib/kitsune/kit/commands/bootstrap.rb +0 -134
- data/lib/kitsune/kit/commands/bootstrap_docker.rb +0 -66
- data/lib/kitsune/kit/commands/dns.rb +0 -112
- data/lib/kitsune/kit/commands/init.rb +0 -148
- data/lib/kitsune/kit/commands/install_docker_engine.rb +0 -146
- data/lib/kitsune/kit/commands/postinstall_docker.rb +0 -142
- data/lib/kitsune/kit/commands/provision.rb +0 -43
- data/lib/kitsune/kit/commands/setup_do_metrics.rb +0 -123
- data/lib/kitsune/kit/commands/setup_docker_prereqs.rb +0 -151
- data/lib/kitsune/kit/commands/setup_firewall.rb +0 -132
- data/lib/kitsune/kit/commands/setup_postgres_docker.rb +0 -246
- data/lib/kitsune/kit/commands/setup_redis_docker.rb +0 -241
- data/lib/kitsune/kit/commands/setup_swap.rb +0 -151
- data/lib/kitsune/kit/commands/setup_unattended.rb +0 -132
- data/lib/kitsune/kit/commands/setup_user.rb +0 -189
- data/lib/kitsune/kit/commands/ssh.rb +0 -46
- data/lib/kitsune/kit/commands/switch_env.rb +0 -42
- data/lib/kitsune/kit/defaults.rb +0 -91
- data/lib/kitsune/kit/env_loader.rb +0 -41
- data/lib/kitsune/kit/options_builder.rb +0 -26
- data/lib/kitsune/kit/provisioner.rb +0 -107
- data/sig/kitsune/kit.rbs +0 -6
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
require "thor"
|
|
2
|
-
require "open3"
|
|
3
|
-
require_relative "../defaults"
|
|
4
|
-
require_relative "../options_builder"
|
|
5
|
-
|
|
6
|
-
module Kitsune
|
|
7
|
-
module Kit
|
|
8
|
-
module Commands
|
|
9
|
-
class BootstrapDocker < Thor
|
|
10
|
-
namespace "bootstrap_docker"
|
|
11
|
-
|
|
12
|
-
class_option :server_ip, aliases: "-s", required: true, desc: "Server IP address or hostname"
|
|
13
|
-
class_option :ssh_port, aliases: "-p", desc: "SSH port for server"
|
|
14
|
-
class_option :ssh_key_path, aliases: "-k", desc: "SSH private key path"
|
|
15
|
-
class_option :rollback, type: :boolean, default: false, desc: "Rollback Docker setup steps"
|
|
16
|
-
|
|
17
|
-
desc "execute", "Run full Docker setup or rollback sequence"
|
|
18
|
-
def execute
|
|
19
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
20
|
-
options,
|
|
21
|
-
required: [:server_ip],
|
|
22
|
-
defaults: Kitsune::Kit::Defaults.ssh
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
if filled_options[:rollback]
|
|
26
|
-
say "🔄 Rolling back full Docker setup...", :yellow
|
|
27
|
-
rollback_sequence(filled_options)
|
|
28
|
-
else
|
|
29
|
-
say "🐳 Running full Docker setup...", :green
|
|
30
|
-
setup_sequence(filled_options)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
say "🎉 Done!", :green
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
no_commands do
|
|
37
|
-
def setup_sequence(filled_options)
|
|
38
|
-
run_cli("setup_docker_prereqs create", filled_options)
|
|
39
|
-
run_cli("install_docker_engine create", filled_options)
|
|
40
|
-
run_cli("postinstall_docker create", filled_options)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def rollback_sequence(filled_options)
|
|
44
|
-
run_cli("postinstall_docker rollback", filled_options)
|
|
45
|
-
run_cli("install_docker_engine rollback", filled_options)
|
|
46
|
-
run_cli("setup_docker_prereqs rollback", filled_options)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def run_cli(command, filled_options)
|
|
50
|
-
say "\n▶️ Running: kitsune kit #{command} --server-ip #{filled_options[:server_ip]}", :blue
|
|
51
|
-
|
|
52
|
-
subcommand, action = command.split(" ", 2)
|
|
53
|
-
Kitsune::Kit::CLI.start([
|
|
54
|
-
subcommand, action,
|
|
55
|
-
"--server-ip", filled_options[:server_ip],
|
|
56
|
-
"--ssh-port", filled_options[:ssh_port],
|
|
57
|
-
"--ssh-key-path", filled_options[:ssh_key_path]
|
|
58
|
-
])
|
|
59
|
-
rescue SystemExit => e
|
|
60
|
-
abort "❌ Command failed: #{command} (exit #{e.status})"
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
require "droplet_kit"
|
|
2
|
-
require "thor"
|
|
3
|
-
|
|
4
|
-
module Kitsune
|
|
5
|
-
module Kit
|
|
6
|
-
module Commands
|
|
7
|
-
class Dns < Thor
|
|
8
|
-
namespace "dns"
|
|
9
|
-
|
|
10
|
-
class_option :domains, type: :string, desc: "Comma-separated domain list (or from ENV['DOMAIN_NAMES'])"
|
|
11
|
-
class_option :server_ip, type: :string, required: true, desc: "IPv4 to assign to domain(s)"
|
|
12
|
-
class_option :ttl, type: :numeric, default: 3600, desc: "TTL in seconds for DNS records"
|
|
13
|
-
|
|
14
|
-
desc "link", "Link domains to a given IP using A records"
|
|
15
|
-
def link
|
|
16
|
-
validate_ip!
|
|
17
|
-
|
|
18
|
-
domains = resolve_domains
|
|
19
|
-
return if domains.empty?
|
|
20
|
-
|
|
21
|
-
ip = options[:server_ip]
|
|
22
|
-
ttl = ENV.fetch("DNS_TTL", options[:ttl]).to_i
|
|
23
|
-
|
|
24
|
-
client = DropletKit::Client.new(access_token: ENV.fetch("DO_API_TOKEN"))
|
|
25
|
-
|
|
26
|
-
domains.each do |fqdn|
|
|
27
|
-
parts = fqdn.split('.')
|
|
28
|
-
next if parts.size < 2
|
|
29
|
-
|
|
30
|
-
root_domain = parts[-2..].join('.')
|
|
31
|
-
subdomain = parts[0..-3].join('.')
|
|
32
|
-
name_for_a = subdomain.empty? ? "@" : subdomain
|
|
33
|
-
|
|
34
|
-
puts "\n🌐 Linking '#{fqdn}' to IP #{ip} (domain: #{root_domain}, record: #{name_for_a})"
|
|
35
|
-
|
|
36
|
-
records = client.domain_records.all(for_domain: root_domain)
|
|
37
|
-
existing = records.find { |r| r.type == "A" && r.name == name_for_a }
|
|
38
|
-
|
|
39
|
-
domain_record = DropletKit::DomainRecord.new(
|
|
40
|
-
type: "A",
|
|
41
|
-
name: name_for_a,
|
|
42
|
-
data: ip,
|
|
43
|
-
ttl: ttl
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
msg = "'#{AnsiColor.colorize(name_for_a, color: :green)}.#{AnsiColor.colorize(root_domain, color: :green)}' → #{AnsiColor.colorize(ip, color: :light_cyan)}"
|
|
47
|
-
if existing
|
|
48
|
-
client.domain_records.update(
|
|
49
|
-
domain_record,
|
|
50
|
-
for_domain: root_domain,
|
|
51
|
-
id: existing.id
|
|
52
|
-
)
|
|
53
|
-
puts "✅ Updated A record #{msg}"
|
|
54
|
-
else
|
|
55
|
-
client.domain_records.create(
|
|
56
|
-
domain_record,
|
|
57
|
-
for_domain: root_domain
|
|
58
|
-
)
|
|
59
|
-
puts "✅ Created A record #{msg}"
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
desc "rollback", "Remove A records for the specified domains"
|
|
65
|
-
def rollback
|
|
66
|
-
domains = resolve_domains
|
|
67
|
-
return if domains.empty?
|
|
68
|
-
|
|
69
|
-
client = DropletKit::Client.new(access_token: ENV.fetch("DO_API_TOKEN"))
|
|
70
|
-
|
|
71
|
-
domains.each do |fqdn|
|
|
72
|
-
parts = fqdn.split('.')
|
|
73
|
-
next if parts.size < 2
|
|
74
|
-
|
|
75
|
-
root_domain = parts[-2..].join('.')
|
|
76
|
-
subdomain = parts[0..-3].join('.')
|
|
77
|
-
name_for_a = subdomain.empty? ? "@" : subdomain
|
|
78
|
-
|
|
79
|
-
puts "\n🗑️ Attempting to delete A record for '#{fqdn}' (domain: #{root_domain}, record: #{name_for_a})"
|
|
80
|
-
|
|
81
|
-
records = client.domain_records.all(for_domain: root_domain)
|
|
82
|
-
existing = records.find { |r| r.type == "A" && r.name == name_for_a }
|
|
83
|
-
|
|
84
|
-
if existing
|
|
85
|
-
client.domain_records.delete(for_domain: root_domain, id: existing.id)
|
|
86
|
-
puts "✅ Deleted A record '#{name_for_a}.#{root_domain}'"
|
|
87
|
-
else
|
|
88
|
-
puts "💡 No A record found for '#{name_for_a}.#{root_domain}', nothing to delete"
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
no_commands do
|
|
94
|
-
def validate_ip!
|
|
95
|
-
if options[:server_ip].nil? || options[:server_ip].empty?
|
|
96
|
-
abort "❌ Missing required option: --server-ip"
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def resolve_domains
|
|
101
|
-
raw = options[:domains] || ENV["DOMAIN_NAMES"]
|
|
102
|
-
if raw.nil? || raw.strip.empty?
|
|
103
|
-
puts "⏭️ No domains provided. Skipping DNS operation."
|
|
104
|
-
return []
|
|
105
|
-
end
|
|
106
|
-
raw.split(',').map(&:strip).reject(&:empty?)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
require "thor"
|
|
2
|
-
require "fileutils"
|
|
3
|
-
|
|
4
|
-
module Kitsune
|
|
5
|
-
module Kit
|
|
6
|
-
module Commands
|
|
7
|
-
class Init < Thor
|
|
8
|
-
namespace "init"
|
|
9
|
-
|
|
10
|
-
default_task :init
|
|
11
|
-
|
|
12
|
-
desc "init", "Initialize Kitsune Kit project structure"
|
|
13
|
-
def init
|
|
14
|
-
say "✨ Initializing Kitsune project...", :green
|
|
15
|
-
create_base_structure
|
|
16
|
-
selected_envs = select_environments
|
|
17
|
-
copy_env_templates(selected_envs)
|
|
18
|
-
selected_default_env = select_default_environment(selected_envs)
|
|
19
|
-
create_kit_env(selected_default_env)
|
|
20
|
-
copy_docker_templates
|
|
21
|
-
say "🎉 Done! '.kitsune/' structure is ready.", :green
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
no_commands do
|
|
25
|
-
def blueprint_path(relative_path)
|
|
26
|
-
File.expand_path("../../blueprints/#{relative_path}", __dir__)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def create_base_structure
|
|
30
|
-
dirs = [
|
|
31
|
-
".kitsune",
|
|
32
|
-
".kitsune/docker"
|
|
33
|
-
]
|
|
34
|
-
dirs.each do |dir|
|
|
35
|
-
unless Dir.exist?(dir)
|
|
36
|
-
FileUtils.mkdir_p(dir)
|
|
37
|
-
say "📂 Created directory: #{dir}", :cyan
|
|
38
|
-
else
|
|
39
|
-
say "📂 Directory already exists: #{dir}", :yellow
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def environments_options
|
|
45
|
-
{
|
|
46
|
-
"1" => "development",
|
|
47
|
-
"2" => "production",
|
|
48
|
-
"3" => "staging",
|
|
49
|
-
"4" => "test"
|
|
50
|
-
}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def select_environments
|
|
54
|
-
say "🌎 Which environments do you want to create?", :cyan
|
|
55
|
-
environments_options.each do |number, env|
|
|
56
|
-
say " #{number}) #{env}"
|
|
57
|
-
end
|
|
58
|
-
input = ask("➡️ Enter numbers separated by commas (or type 'all') [default: all]:", :yellow)
|
|
59
|
-
input = input.strip.downcase
|
|
60
|
-
|
|
61
|
-
if input.empty? || input == "all"
|
|
62
|
-
environments_options.values
|
|
63
|
-
else
|
|
64
|
-
selected = input.split(",").map(&:strip)
|
|
65
|
-
environments = selected.map { |num| environments_options[num] }.compact
|
|
66
|
-
if environments.empty?
|
|
67
|
-
say "⚠️ Invalid selection. Creating all environments.", :yellow
|
|
68
|
-
environments_options.values
|
|
69
|
-
else
|
|
70
|
-
environments
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def select_default_environment(selected_envs)
|
|
76
|
-
say "🎯 Which environment should be set as default in '.kitsune/kit.env'?", :cyan
|
|
77
|
-
selected_envs.each_with_index do |env, index|
|
|
78
|
-
say " #{index + 1}) #{env}"
|
|
79
|
-
end
|
|
80
|
-
input = ask("➡️ Enter number [default: 1]:", :yellow)
|
|
81
|
-
input = input.strip
|
|
82
|
-
|
|
83
|
-
if input.empty?
|
|
84
|
-
selected_envs[0] # default to first selected
|
|
85
|
-
else
|
|
86
|
-
index = input.to_i - 1
|
|
87
|
-
if index >= 0 && index < selected_envs.size
|
|
88
|
-
selected_envs[index]
|
|
89
|
-
else
|
|
90
|
-
say "⚠️ Invalid selection. Defaulting to '#{selected_envs[0]}'", :yellow
|
|
91
|
-
selected_envs[0]
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def create_kit_env(default_env)
|
|
97
|
-
path = ".kitsune/kit.env"
|
|
98
|
-
template = File.read(blueprint_path("kit.env.template"))
|
|
99
|
-
content = template.gsub("KIT_ENV=development", "KIT_ENV=#{default_env}")
|
|
100
|
-
|
|
101
|
-
if File.exist?(path)
|
|
102
|
-
if yes?("⚠️ File #{path} already exists. Overwrite? [y/N]", :yellow)
|
|
103
|
-
File.write(path, content)
|
|
104
|
-
say "✅ Overwritten: #{path}", :cyan
|
|
105
|
-
else
|
|
106
|
-
say "⏩ Skipped: #{path}", :yellow
|
|
107
|
-
end
|
|
108
|
-
else
|
|
109
|
-
File.write(path, content)
|
|
110
|
-
say "📝 Created: #{path}", :cyan
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
say ""
|
|
114
|
-
say "🎯 Kitsune Kit environment set to: #{default_env}", :green
|
|
115
|
-
say "📄 Environment file used: .kitsune/infra.#{default_env}.env", :green
|
|
116
|
-
say ""
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def copy_env_templates(selected_envs)
|
|
120
|
-
selected_envs.each do |env|
|
|
121
|
-
dest_path = ".kitsune/infra.#{env}.env"
|
|
122
|
-
copy_with_prompt(blueprint_path(".env.template"), dest_path)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def copy_docker_templates
|
|
127
|
-
copy_with_prompt(blueprint_path("docker/postgres.yml"), ".kitsune/docker/postgres.yml")
|
|
128
|
-
copy_with_prompt(blueprint_path("docker/redis.yml"), ".kitsune/docker/redis.yml")
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def copy_with_prompt(source, destination)
|
|
132
|
-
if File.exist?(destination)
|
|
133
|
-
if yes?("⚠️ File #{destination} already exists. Overwrite? [y/N]", :yellow)
|
|
134
|
-
FileUtils.cp(source, destination)
|
|
135
|
-
say "✅ Overwritten: #{destination}", :cyan
|
|
136
|
-
else
|
|
137
|
-
say "⏩ Skipped: #{destination}", :yellow
|
|
138
|
-
end
|
|
139
|
-
else
|
|
140
|
-
FileUtils.cp(source, destination)
|
|
141
|
-
say "📝 Created: #{destination}", :cyan
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
require "thor"
|
|
2
|
-
require "net/ssh"
|
|
3
|
-
require_relative "../defaults"
|
|
4
|
-
require_relative "../options_builder"
|
|
5
|
-
|
|
6
|
-
module Kitsune
|
|
7
|
-
module Kit
|
|
8
|
-
module Commands
|
|
9
|
-
class InstallDockerEngine < Thor
|
|
10
|
-
namespace "install_docker_engine"
|
|
11
|
-
|
|
12
|
-
class_option :server_ip, aliases: "-s", required: true, desc: "Server IP address or hostname"
|
|
13
|
-
class_option :ssh_port, aliases: "-p", desc: "SSH port"
|
|
14
|
-
class_option :ssh_key_path, aliases: "-k", desc: "Path to your private SSH key"
|
|
15
|
-
|
|
16
|
-
desc "create", "Install Docker Engine on the remote server"
|
|
17
|
-
def create
|
|
18
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
19
|
-
options,
|
|
20
|
-
required: [:server_ip],
|
|
21
|
-
defaults: Kitsune::Kit::Defaults.ssh
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
25
|
-
perform_setup(ssh)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
desc "rollback", "Uninstall Docker Engine from the remote server"
|
|
30
|
-
def rollback
|
|
31
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
32
|
-
options,
|
|
33
|
-
required: [:server_ip],
|
|
34
|
-
defaults: Kitsune::Kit::Defaults.ssh
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
38
|
-
perform_rollback(ssh)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
no_commands do
|
|
43
|
-
def with_ssh_connection(filled_options)
|
|
44
|
-
server = filled_options[:server_ip]
|
|
45
|
-
port = filled_options[:ssh_port]
|
|
46
|
-
key = File.expand_path(filled_options[:ssh_key_path])
|
|
47
|
-
|
|
48
|
-
say "🔑 Connecting as deploy@#{server}:#{port}", :green
|
|
49
|
-
Net::SSH.start(server, "deploy", port: port, keys: [key], non_interactive: true, timeout: 5) do |ssh|
|
|
50
|
-
yield ssh
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def perform_setup(ssh)
|
|
55
|
-
output = ssh.exec! <<~EOH
|
|
56
|
-
set -e
|
|
57
|
-
|
|
58
|
-
sudo mkdir -p /usr/local/backups
|
|
59
|
-
sudo chown deploy:deploy /usr/local/backups
|
|
60
|
-
|
|
61
|
-
BACKUP_DIR="/usr/local/backups"
|
|
62
|
-
SCRIPT_ID="install_docker_engine"
|
|
63
|
-
BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
|
64
|
-
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
65
|
-
|
|
66
|
-
TARGET_PKGS=(docker-ce docker-ce-cli containerd.io)
|
|
67
|
-
|
|
68
|
-
echo "✍🏻 TARGET_PKGS=(\${TARGET_PKGS[*]})"
|
|
69
|
-
|
|
70
|
-
if [ ! -f "$AFTER_FILE" ]; then
|
|
71
|
-
for pkg in "\${TARGET_PKGS[@]}"; do
|
|
72
|
-
if dpkg -l "\$pkg" &>/dev/null; then
|
|
73
|
-
echo "\$pkg" >> "$BEFORE_FILE"
|
|
74
|
-
fi
|
|
75
|
-
done
|
|
76
|
-
|
|
77
|
-
if [ ! -f /usr/share/keyrings/docker-archive-keyring.gpg ]; then
|
|
78
|
-
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
if [ ! -f /etc/apt/sources.list.d/docker.list ]; then
|
|
82
|
-
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
echo "✍🏻 Installing Docker Engine..."
|
|
86
|
-
sudo apt-get update -y
|
|
87
|
-
sudo apt-get install -y "\${TARGET_PKGS[@]}"
|
|
88
|
-
sudo touch "$AFTER_FILE" && echo " - marker at $AFTER_FILE"
|
|
89
|
-
echo "✅ Docker installed"
|
|
90
|
-
else
|
|
91
|
-
echo "🔄 Docker already set up, ensuring latest..."
|
|
92
|
-
sudo apt-get update -y
|
|
93
|
-
sudo apt-get install -y "\${TARGET_PKGS[@]}"
|
|
94
|
-
echo "✅ Docker packages are current"
|
|
95
|
-
fi
|
|
96
|
-
EOH
|
|
97
|
-
say output
|
|
98
|
-
say "✅ Docker Engine setup completed", :green
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def perform_rollback(ssh)
|
|
102
|
-
output = ssh.exec! <<~EOH
|
|
103
|
-
set -e
|
|
104
|
-
|
|
105
|
-
sudo mkdir -p /usr/local/backups
|
|
106
|
-
sudo chown deploy:deploy /usr/local/backups
|
|
107
|
-
|
|
108
|
-
BACKUP_DIR="/usr/local/backups"
|
|
109
|
-
SCRIPT_ID="install_docker_engine"
|
|
110
|
-
BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
|
111
|
-
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
112
|
-
|
|
113
|
-
TARGET_PKGS=(docker-ce docker-ce-cli containerd.io)
|
|
114
|
-
|
|
115
|
-
echo "✍🏻 TARGET_PKGS=(\${TARGET_PKGS[*]})"
|
|
116
|
-
|
|
117
|
-
if [ -f "$AFTER_FILE" ]; then
|
|
118
|
-
to_remove=()
|
|
119
|
-
for pkg in "\${TARGET_PKGS[@]}"; do
|
|
120
|
-
if dpkg -l "\$pkg" &>/dev/null && ! grep -Fxq "\$pkg" "$BEFORE_FILE"; then
|
|
121
|
-
to_remove+=("\$pkg")
|
|
122
|
-
fi
|
|
123
|
-
done
|
|
124
|
-
|
|
125
|
-
if [ \${#to_remove[@]} -gt 0 ]; then
|
|
126
|
-
echo "🔁 Removing Docker packages..."
|
|
127
|
-
sudo apt-get remove -y "\${to_remove[@]}" && echo " - removed: \${to_remove[*]}"
|
|
128
|
-
else
|
|
129
|
-
echo " - no Docker packages to remove"
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
sudo rm -f "$BEFORE_FILE" "$AFTER_FILE" && echo " - cleanup markers"
|
|
133
|
-
else
|
|
134
|
-
echo " - no marker for $SCRIPT_ID, skipping"
|
|
135
|
-
fi
|
|
136
|
-
|
|
137
|
-
echo "✅ Rollback done"
|
|
138
|
-
EOH
|
|
139
|
-
say output
|
|
140
|
-
say "✅ Docker Engine rollback completed", :green
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
require "thor"
|
|
2
|
-
require "net/ssh"
|
|
3
|
-
require_relative "../defaults"
|
|
4
|
-
require_relative "../options_builder"
|
|
5
|
-
|
|
6
|
-
module Kitsune
|
|
7
|
-
module Kit
|
|
8
|
-
module Commands
|
|
9
|
-
class PostinstallDocker < Thor
|
|
10
|
-
namespace "postinstall_docker"
|
|
11
|
-
|
|
12
|
-
class_option :server_ip, aliases: "-s", required: true, desc: "Server IP address or hostname"
|
|
13
|
-
class_option :ssh_port, aliases: "-p", desc: "SSH port"
|
|
14
|
-
class_option :ssh_key_path, aliases: "-k", desc: "Path to your private SSH key"
|
|
15
|
-
|
|
16
|
-
desc "create", "Apply Docker post-install configuration (start service, add groups, create network)"
|
|
17
|
-
def create
|
|
18
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
19
|
-
options,
|
|
20
|
-
required: [:server_ip],
|
|
21
|
-
defaults: Kitsune::Kit::Defaults.ssh
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
25
|
-
perform_setup(ssh)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
desc "rollback", "Undo Docker post-install configuration"
|
|
30
|
-
def rollback
|
|
31
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
32
|
-
options,
|
|
33
|
-
required: [:server_ip],
|
|
34
|
-
defaults: Kitsune::Kit::Defaults.ssh
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
38
|
-
perform_rollback(ssh)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
no_commands do
|
|
43
|
-
def with_ssh_connection(filled_options)
|
|
44
|
-
server = filled_options[:server_ip]
|
|
45
|
-
port = filled_options[:ssh_port]
|
|
46
|
-
key = File.expand_path(filled_options[:ssh_key_path])
|
|
47
|
-
|
|
48
|
-
say "🔑 Connecting as deploy@#{server}:#{port}", :green
|
|
49
|
-
Net::SSH.start(server, "deploy", port: port, keys: [key], non_interactive: true, timeout: 5) do |ssh|
|
|
50
|
-
yield ssh
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def perform_setup(ssh)
|
|
55
|
-
output = ssh.exec! <<~EOH
|
|
56
|
-
set -e
|
|
57
|
-
|
|
58
|
-
sudo mkdir -p /usr/local/backups
|
|
59
|
-
sudo chown deploy:deploy /usr/local/backups
|
|
60
|
-
|
|
61
|
-
BACKUP_DIR="/usr/local/backups"
|
|
62
|
-
SCRIPT_ID="postinstall_docker"
|
|
63
|
-
BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
|
64
|
-
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
65
|
-
|
|
66
|
-
echo "✍🏻 Performing post-install Docker tasks"
|
|
67
|
-
|
|
68
|
-
if [ ! -f "$AFTER_FILE" ]; then
|
|
69
|
-
# Record state
|
|
70
|
-
systemctl is-enabled docker &>/dev/null && echo "docker.service enabled" >> "$BEFORE_FILE" || echo "docker.service disabled" >> "$BEFORE_FILE"
|
|
71
|
-
groups deploy | grep -q docker && echo "deploy in docker group" >> "$BEFORE_FILE" || echo "deploy not in docker group" >> "$BEFORE_FILE"
|
|
72
|
-
sudo docker network inspect private &>/dev/null && echo "network private exists" >> "$BEFORE_FILE" || echo "network private absent" >> "$BEFORE_FILE"
|
|
73
|
-
|
|
74
|
-
# Start and enable Docker
|
|
75
|
-
sudo systemctl start docker
|
|
76
|
-
sudo systemctl enable docker
|
|
77
|
-
echo "🚀 Docker service started and enabled"
|
|
78
|
-
echo "docker.service enabled" >> "$AFTER_FILE"
|
|
79
|
-
|
|
80
|
-
# Add deploy to docker group
|
|
81
|
-
sudo usermod -aG docker deploy
|
|
82
|
-
echo "👥 Added 'deploy' to docker group"
|
|
83
|
-
echo "added docker group" >> "$AFTER_FILE"
|
|
84
|
-
|
|
85
|
-
# Create private network if missing
|
|
86
|
-
if ! sudo docker network inspect private &>/dev/null; then
|
|
87
|
-
sudo docker network create -d bridge private
|
|
88
|
-
echo "🌐 Created Docker network 'private'"
|
|
89
|
-
echo "created network private" >> "$AFTER_FILE"
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
echo "✅ Post-install Docker tasks complete"
|
|
93
|
-
else
|
|
94
|
-
echo "🔄 Post-install tasks already applied, skipping setup"
|
|
95
|
-
fi
|
|
96
|
-
EOH
|
|
97
|
-
say output
|
|
98
|
-
say "✅ Post-install Docker setup completed", :green
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def perform_rollback(ssh)
|
|
102
|
-
output = ssh.exec! <<~EOH
|
|
103
|
-
set -e
|
|
104
|
-
|
|
105
|
-
sudo mkdir -p /usr/local/backups
|
|
106
|
-
sudo chown deploy:deploy /usr/local/backups
|
|
107
|
-
|
|
108
|
-
BACKUP_DIR="/usr/local/backups"
|
|
109
|
-
SCRIPT_ID="postinstall_docker"
|
|
110
|
-
BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
|
111
|
-
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
112
|
-
|
|
113
|
-
echo "🔄 Rolling back post-install Docker tasks..."
|
|
114
|
-
|
|
115
|
-
if [ -f "$AFTER_FILE" ]; then
|
|
116
|
-
if grep -Fxq "docker.service enabled" "$AFTER_FILE"; then
|
|
117
|
-
sudo systemctl disable docker
|
|
118
|
-
echo " - Docker service disabled"
|
|
119
|
-
fi
|
|
120
|
-
if grep -Fxq "added docker group" "$AFTER_FILE"; then
|
|
121
|
-
sudo gpasswd -d deploy docker || true
|
|
122
|
-
echo " - Removed 'deploy' from docker group"
|
|
123
|
-
fi
|
|
124
|
-
if grep -Fxq "created network private" "$AFTER_FILE"; then
|
|
125
|
-
sudo docker network rm private || true
|
|
126
|
-
echo " - Removed Docker network 'private'"
|
|
127
|
-
fi
|
|
128
|
-
sudo rm -f "$BEFORE_FILE" "$AFTER_FILE"
|
|
129
|
-
else
|
|
130
|
-
echo " - no marker for $SCRIPT_ID, skipping rollback"
|
|
131
|
-
fi
|
|
132
|
-
|
|
133
|
-
echo "✅ Rollback complete"
|
|
134
|
-
EOH
|
|
135
|
-
say output
|
|
136
|
-
say "✅ Post-install Docker rollback completed", :green
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
require "thor"
|
|
2
|
-
require_relative "../defaults"
|
|
3
|
-
require_relative "../provisioner"
|
|
4
|
-
require_relative "../options_builder"
|
|
5
|
-
|
|
6
|
-
module Kitsune
|
|
7
|
-
module Kit
|
|
8
|
-
module Commands
|
|
9
|
-
class Provision < Thor
|
|
10
|
-
namespace "provision"
|
|
11
|
-
|
|
12
|
-
class_option :droplet_name, type: :string, aliases: "-n", desc: "Droplet name"
|
|
13
|
-
class_option :region, type: :string, aliases: "-r", desc: "Region"
|
|
14
|
-
class_option :size, type: :string, aliases: "-s", desc: "Size"
|
|
15
|
-
class_option :image, type: :string, aliases: "-i", desc: "Image"
|
|
16
|
-
class_option :tag, type: :string, aliases: "-t", desc: "Tag to filter/create"
|
|
17
|
-
class_option :ssh_key_id, type: :string, aliases: "-k", desc: "SSH key ID"
|
|
18
|
-
|
|
19
|
-
desc "create", "Create the Droplet if it doesn't exist"
|
|
20
|
-
def create
|
|
21
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
22
|
-
options,
|
|
23
|
-
required: [:ssh_key_id],
|
|
24
|
-
defaults: Kitsune::Kit::Defaults.infra
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
Provisioner.new(filled_options).create_or_show
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
desc "rollback", "Remove the Droplet if it exists"
|
|
31
|
-
def rollback
|
|
32
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
33
|
-
options,
|
|
34
|
-
required: [:ssh_key_id],
|
|
35
|
-
defaults: Kitsune::Kit::Defaults.infra
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
Provisioner.new(filled_options).rollback
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|