kitsune-kit 0.4.0 → 0.5.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.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -2
  3. data/CONTRIBUTING.md +83 -0
  4. data/README.md +108 -136
  5. data/SECURITY.md +25 -0
  6. data/bin/kit +4 -1
  7. data/docs/architecture/decisions/0001-product-boundary.md +23 -0
  8. data/docs/architecture/decisions/0002-core-and-interfaces.md +23 -0
  9. data/docs/architecture/decisions/0003-configuration-state-and-secrets.md +23 -0
  10. data/docs/architecture/decisions/0004-supported-platforms.md +19 -0
  11. data/docs/architecture/decisions/0005-operation-semantics.md +19 -0
  12. data/docs/architecture/decisions/0006-tui-backend.md +21 -0
  13. data/docs/architecture/provider-adapters.md +49 -0
  14. data/docs/architecture.md +89 -0
  15. data/docs/commands.md +210 -0
  16. data/docs/configuration.md +210 -0
  17. data/docs/getting-started.md +133 -0
  18. data/docs/providers/digitalocean.md +52 -0
  19. data/docs/releasing.md +43 -0
  20. data/docs/roadmap.md +46 -0
  21. data/docs/security-audit.md +130 -0
  22. data/docs/security.md +116 -0
  23. data/docs/services/postgres.md +78 -0
  24. data/docs/services/redis.md +53 -0
  25. data/docs/testing.md +200 -0
  26. data/docs/troubleshooting.md +123 -0
  27. data/docs/tui.md +57 -0
  28. data/lib/kitsune/kit/adapters/confirming_host_key_verifier.rb +42 -0
  29. data/lib/kitsune/kit/adapters/digitalocean_provider.rb +217 -0
  30. data/lib/kitsune/kit/adapters/fake_clock.rb +34 -0
  31. data/lib/kitsune/kit/adapters/fake_provider.rb +107 -0
  32. data/lib/kitsune/kit/adapters/fake_reporter.rb +34 -0
  33. data/lib/kitsune/kit/adapters/fake_secret_store.rb +35 -0
  34. data/lib/kitsune/kit/adapters/fake_state_store.rb +91 -0
  35. data/lib/kitsune/kit/adapters/fake_transport.rb +56 -0
  36. data/lib/kitsune/kit/adapters/net_ssh_transport.rb +155 -0
  37. data/lib/kitsune/kit/adapters/provider.rb +34 -0
  38. data/lib/kitsune/kit/adapters/transport.rb +22 -0
  39. data/lib/kitsune/kit/adapters/transport_factory.rb +116 -0
  40. data/lib/kitsune/kit/application.rb +170 -0
  41. data/lib/kitsune/kit/cancellation.rb +21 -0
  42. data/lib/kitsune/kit/cli.rb +802 -64
  43. data/lib/kitsune/kit/clock.rb +11 -0
  44. data/lib/kitsune/kit/configuration.rb +493 -0
  45. data/lib/kitsune/kit/errors.rb +98 -0
  46. data/lib/kitsune/kit/events.rb +55 -0
  47. data/lib/kitsune/kit/operations/ensure_dns_records.rb +165 -0
  48. data/lib/kitsune/kit/operations/ensure_server.rb +135 -0
  49. data/lib/kitsune/kit/operations/ensure_service.rb +285 -0
  50. data/lib/kitsune/kit/operations/remote_script.rb +214 -0
  51. data/lib/kitsune/kit/operations/service_backup.rb +71 -0
  52. data/lib/kitsune/kit/operations/service_files.rb +127 -0
  53. data/lib/kitsune/kit/operations/service_firewall.rb +190 -0
  54. data/lib/kitsune/kit/operations/service_state.rb +59 -0
  55. data/lib/kitsune/kit/plan.rb +72 -0
  56. data/lib/kitsune/kit/reporters/human.rb +89 -0
  57. data/lib/kitsune/kit/reporters/json.rb +65 -0
  58. data/lib/kitsune/kit/reporters/reporter.rb +11 -0
  59. data/lib/kitsune/kit/result.rb +28 -0
  60. data/lib/kitsune/kit/run_journal.rb +102 -0
  61. data/lib/kitsune/kit/run_logger.rb +37 -0
  62. data/lib/kitsune/kit/scripts/docker.sh +137 -0
  63. data/lib/kitsune/kit/scripts/firewall.sh +205 -0
  64. data/lib/kitsune/kit/scripts/metrics.sh +54 -0
  65. data/lib/kitsune/kit/scripts/ssh.sh +95 -0
  66. data/lib/kitsune/kit/scripts/swap.sh +86 -0
  67. data/lib/kitsune/kit/scripts/unattended.sh +81 -0
  68. data/lib/kitsune/kit/scripts/user.sh +114 -0
  69. data/lib/kitsune/kit/secret_filter.rb +54 -0
  70. data/lib/kitsune/kit/secret_store.rb +32 -0
  71. data/lib/kitsune/kit/secret_stores/store.rb +12 -0
  72. data/lib/kitsune/kit/service_compose.rb +72 -0
  73. data/lib/kitsune/kit/state_store.rb +158 -0
  74. data/lib/kitsune/kit/state_stores/store.rb +15 -0
  75. data/lib/kitsune/kit/tui/actions.rb +72 -0
  76. data/lib/kitsune/kit/tui/application.rb +35 -0
  77. data/lib/kitsune/kit/tui/controller.rb +162 -0
  78. data/lib/kitsune/kit/tui/renderer.rb +134 -0
  79. data/lib/kitsune/kit/tui/state.rb +18 -0
  80. data/lib/kitsune/kit/tui/store.rb +88 -0
  81. data/lib/kitsune/kit/tui/terminal.rb +95 -0
  82. data/lib/kitsune/kit/version.rb +1 -1
  83. data/lib/kitsune/kit/workflows/apply_plan.rb +145 -0
  84. data/lib/kitsune/kit/workflows/base.rb +31 -0
  85. data/lib/kitsune/kit/workflows/build_plan.rb +33 -0
  86. data/lib/kitsune/kit/workflows/destroy_server.rb +84 -0
  87. data/lib/kitsune/kit/workflows/doctor.rb +225 -0
  88. data/lib/kitsune/kit/workflows/environment_selection.rb +70 -0
  89. data/lib/kitsune/kit/workflows/import_server.rb +100 -0
  90. data/lib/kitsune/kit/workflows/initialize_project.rb +121 -0
  91. data/lib/kitsune/kit/workflows/inspect_environment.rb +44 -0
  92. data/lib/kitsune/kit/workflows/rollback.rb +54 -0
  93. data/lib/kitsune/kit/workflows/support_bundle.rb +82 -0
  94. data/lib/kitsune/kit.rb +41 -2
  95. metadata +122 -79
  96. data/.rspec +0 -3
  97. data/Rakefile +0 -8
  98. data/kitsune-kit-logo.jpg +0 -0
  99. data/lib/kitsune/blueprints/.env.template +0 -31
  100. data/lib/kitsune/blueprints/docker/postgres.yml +0 -27
  101. data/lib/kitsune/blueprints/docker/redis.yml +0 -23
  102. data/lib/kitsune/blueprints/kit.env.template +0 -1
  103. data/lib/kitsune/kit/ansi_color.rb +0 -78
  104. data/lib/kitsune/kit/commands/bootstrap.rb +0 -134
  105. data/lib/kitsune/kit/commands/bootstrap_docker.rb +0 -66
  106. data/lib/kitsune/kit/commands/dns.rb +0 -112
  107. data/lib/kitsune/kit/commands/init.rb +0 -148
  108. data/lib/kitsune/kit/commands/install_docker_engine.rb +0 -146
  109. data/lib/kitsune/kit/commands/postinstall_docker.rb +0 -142
  110. data/lib/kitsune/kit/commands/provision.rb +0 -43
  111. data/lib/kitsune/kit/commands/setup_do_metrics.rb +0 -123
  112. data/lib/kitsune/kit/commands/setup_docker_prereqs.rb +0 -151
  113. data/lib/kitsune/kit/commands/setup_firewall.rb +0 -132
  114. data/lib/kitsune/kit/commands/setup_postgres_docker.rb +0 -246
  115. data/lib/kitsune/kit/commands/setup_redis_docker.rb +0 -241
  116. data/lib/kitsune/kit/commands/setup_swap.rb +0 -151
  117. data/lib/kitsune/kit/commands/setup_unattended.rb +0 -132
  118. data/lib/kitsune/kit/commands/setup_user.rb +0 -189
  119. data/lib/kitsune/kit/commands/ssh.rb +0 -46
  120. data/lib/kitsune/kit/commands/switch_env.rb +0 -42
  121. data/lib/kitsune/kit/defaults.rb +0 -91
  122. data/lib/kitsune/kit/env_loader.rb +0 -41
  123. data/lib/kitsune/kit/options_builder.rb +0 -26
  124. data/lib/kitsune/kit/provisioner.rb +0 -107
  125. data/sig/kitsune/kit.rbs +0 -6
@@ -1,134 +0,0 @@
1
- require "thor"
2
- require "stringio"
3
- require_relative "../defaults"
4
- require_relative "../options_builder"
5
- require_relative "../provisioner"
6
-
7
- module Kitsune
8
- module Kit
9
- module Commands
10
- class Bootstrap < Thor
11
- namespace "bootstrap"
12
-
13
- class_option :rollback, type: :boolean, default: false, desc: "Rollback the setup"
14
- class_option :keep_server, type: :boolean, default: false, desc: "Keep server after rollback"
15
- class_option :ssh_port, type: :string, desc: "SSH port for server"
16
- class_option :ssh_key_path, type: :string, desc: "SSH private key path"
17
-
18
- desc "execute", "Run the full bootstrap process or rollback"
19
- def execute
20
- filled_options = Kitsune::Kit::OptionsBuilder.build(
21
- options,
22
- defaults: Kitsune::Kit::Defaults.ssh
23
- )
24
-
25
- if filled_options[:rollback]
26
- say "🔄 Rolling back server configurations...", :yellow
27
- rollback_sequence(filled_options)
28
- else
29
- say "🏗️ Setting up server from scratch...", :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
- droplet_ip = fetch_droplet_ip
39
-
40
- say "→ Droplet IP: #{droplet_ip}", :cyan
41
-
42
- run_cli("setup_user create", droplet_ip, filled_options)
43
- run_cli("setup_firewall create", droplet_ip, filled_options)
44
- run_cli("setup_unattended create", droplet_ip, filled_options)
45
- run_cli("setup_swap create", droplet_ip, filled_options)
46
- run_cli("setup_do_metrics create", droplet_ip, filled_options)
47
- run_cli("dns link", droplet_ip, filled_options.merge(domains: ENV["DOMAIN_NAMES"]))
48
- end
49
-
50
- def rollback_sequence(filled_options)
51
- provision_options = Kitsune::Kit::OptionsBuilder.build(
52
- {},
53
- defaults: Kitsune::Kit::Defaults::infra
54
- )
55
-
56
- provisioner = Kitsune::Kit::Provisioner.new(provision_options)
57
-
58
- if (droplet = provisioner.find_droplet).nil?
59
- say "💡 Nothing to rollback.", :green
60
- return
61
- end
62
-
63
- droplet_ip = provisioner.send(:public_ip, droplet)
64
- say "→ Using Droplet IP: #{droplet_ip}", :cyan
65
-
66
- run_cli("setup_do_metrics rollback", droplet_ip, filled_options)
67
-
68
- if ssh_accessible?(droplet_ip, filled_options)
69
- run_cli("setup_unattended rollback", droplet_ip, filled_options)
70
- run_cli("setup_firewall rollback", droplet_ip, filled_options)
71
- else
72
- say "⏭️ Skipping unattended-upgrades and firewall rollback (no deploy user)", :yellow
73
- end
74
-
75
- run_cli("setup_swap rollback", droplet_ip, filled_options)
76
- run_cli("setup_user rollback", droplet_ip, filled_options)
77
-
78
- unless filled_options[:keep_server]
79
- run_cli("dns rollback", droplet_ip, filled_options.merge(domains: ENV["DOMAIN_NAMES"]))
80
- say "▶️ Running: kitsune kit provision rollback", :blue
81
- Kitsune::Kit::CLI.start(%w[provision rollback])
82
- else
83
- say "⏭️ Skipping droplet deletion (--keep-server enabled), DNS rollback won't be executed", :yellow
84
- end
85
- end
86
-
87
- def fetch_droplet_ip
88
- out = StringIO.new
89
- $stdout = out
90
- begin
91
- Kitsune::Kit::CLI.start(["provision", "create"])
92
- ensure
93
- $stdout = STDOUT
94
- end
95
-
96
- ip = out.string[/(\d{1,3}\.){3}\d{1,3}/]
97
- abort "❌ Could not detect droplet IP!" if ip.nil? || ip.empty?
98
- ip
99
- end
100
-
101
- def run_cli(command, droplet_ip, filled_options)
102
- say "\n▶️ Running: kitsune kit #{command} --server-ip #{droplet_ip}", :blue
103
- subcommand, action = command.split(" ", 2)
104
-
105
- args = [subcommand, action, "--server-ip", droplet_ip]
106
-
107
- if subcommand != "dns"
108
- args += ["--ssh-port", filled_options[:ssh_port], "--ssh-key-path", filled_options[:ssh_key_path]]
109
- end
110
-
111
- if subcommand == "dns" && ENV["DOMAIN_NAMES"]
112
- args += ["--domains", ENV["DOMAIN_NAMES"]]
113
- end
114
-
115
- Kitsune::Kit::CLI.start(args)
116
- rescue SystemExit => e
117
- abort "❌ Command failed: #{command} (exit #{e.status})"
118
- end
119
-
120
- def ssh_accessible?(droplet_ip, filled_options)
121
- system(
122
- "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new " \
123
- "-p #{filled_options[:ssh_port]} " \
124
- "-i #{File.expand_path(filled_options[:ssh_key_path])} " \
125
- "deploy@#{droplet_ip} true",
126
- out: File::NULL,
127
- err: File::NULL
128
- )
129
- end
130
- end
131
- end
132
- end
133
- end
134
- end
@@ -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