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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +117 -2
- data/CONTRIBUTING.md +83 -0
- data/README.md +108 -136
- 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/provider-adapters.md +49 -0
- data/docs/architecture.md +89 -0
- data/docs/commands.md +210 -0
- data/docs/configuration.md +210 -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/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 +802 -64
- data/lib/kitsune/kit/clock.rb +11 -0
- data/lib/kitsune/kit/configuration.rb +493 -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 +285 -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 +127 -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 +137 -0
- data/lib/kitsune/kit/scripts/firewall.sh +205 -0
- data/lib/kitsune/kit/scripts/metrics.sh +54 -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 +81 -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 +72 -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/environment_selection.rb +70 -0
- data/lib/kitsune/kit/workflows/import_server.rb +100 -0
- data/lib/kitsune/kit/workflows/initialize_project.rb +121 -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 +41 -2
- metadata +122 -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,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
|