kitsune-kit 0.1.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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +6 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +192 -0
- data/Rakefile +8 -0
- data/bin/kit +8 -0
- data/kitsune-kit-logo.jpg +0 -0
- data/lib/kitsune/blueprints/.env.template +17 -0
- data/lib/kitsune/blueprints/docker/postgres.yml +20 -0
- data/lib/kitsune/blueprints/kit.env.template +1 -0
- data/lib/kitsune/kit/cli.rb +64 -0
- data/lib/kitsune/kit/commands/bootstrap.rb +118 -0
- data/lib/kitsune/kit/commands/bootstrap_docker.rb +66 -0
- data/lib/kitsune/kit/commands/init.rb +148 -0
- data/lib/kitsune/kit/commands/install_docker_engine.rb +146 -0
- data/lib/kitsune/kit/commands/postinstall_docker.rb +142 -0
- data/lib/kitsune/kit/commands/provision.rb +43 -0
- data/lib/kitsune/kit/commands/setup_docker_prereqs.rb +150 -0
- data/lib/kitsune/kit/commands/setup_firewall.rb +132 -0
- data/lib/kitsune/kit/commands/setup_postgres_docker.rb +246 -0
- data/lib/kitsune/kit/commands/setup_unattended.rb +132 -0
- data/lib/kitsune/kit/commands/setup_user.rb +189 -0
- data/lib/kitsune/kit/commands/switch_env.rb +42 -0
- data/lib/kitsune/kit/defaults.rb +56 -0
- data/lib/kitsune/kit/env_loader.rb +41 -0
- data/lib/kitsune/kit/options_builder.rb +26 -0
- data/lib/kitsune/kit/provisioner.rb +109 -0
- data/lib/kitsune/kit/version.rb +7 -0
- data/lib/kitsune/kit.rb +10 -0
- data/sig/kitsune/kit.rbs +6 -0
- metadata +180 -0
@@ -0,0 +1,148 @@
|
|
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
|
+
dest_path = ".kitsune/docker/postgres.yml"
|
128
|
+
copy_with_prompt(blueprint_path("docker/postgres.yml"), dest_path)
|
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
|
@@ -0,0 +1,146 @@
|
|
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
|
@@ -0,0 +1,142 @@
|
|
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
|
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "net/ssh"
|
3
|
+
require_relative "../options_builder"
|
4
|
+
require_relative "../defaults"
|
5
|
+
|
6
|
+
module Kitsune
|
7
|
+
module Kit
|
8
|
+
module Commands
|
9
|
+
class SetupDockerPrereqs < Thor
|
10
|
+
namespace "setup_docker_prereqs"
|
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 prerequisites 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", "Remove installed Docker prerequisites 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="setup_docker_prereqs"
|
63
|
+
BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
64
|
+
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
65
|
+
|
66
|
+
TARGET_PKGS=(
|
67
|
+
apt-transport-https
|
68
|
+
ca-certificates
|
69
|
+
curl
|
70
|
+
gnupg
|
71
|
+
lsb-release
|
72
|
+
software-properties-common
|
73
|
+
)
|
74
|
+
|
75
|
+
echo "✍🏻 TARGET_PKGS=(\${TARGET_PKGS[*]})"
|
76
|
+
|
77
|
+
if [ ! -f "$AFTER_FILE" ]; then
|
78
|
+
for pkg in "\${TARGET_PKGS[@]}"; do
|
79
|
+
if dpkg -l "\$pkg" &>/dev/null; then
|
80
|
+
echo "\$pkg" >> "$BEFORE_FILE"
|
81
|
+
fi
|
82
|
+
done
|
83
|
+
|
84
|
+
echo "✍🏻 Installing prerequisites..."
|
85
|
+
sudo apt-get update -y
|
86
|
+
sudo apt-get install -y "\${TARGET_PKGS[@]}"
|
87
|
+
sudo touch "$AFTER_FILE" && echo " - marker created at $AFTER_FILE"
|
88
|
+
else
|
89
|
+
sudo apt-get update -y
|
90
|
+
sudo apt-get install -y "\${TARGET_PKGS[@]}"
|
91
|
+
echo "✅ Prerequisites are current"
|
92
|
+
fi
|
93
|
+
EOH
|
94
|
+
say output
|
95
|
+
say "✅ Docker prerequisites setup completed", :green
|
96
|
+
end
|
97
|
+
|
98
|
+
def perform_rollback(ssh)
|
99
|
+
output = ssh.exec! <<~EOH
|
100
|
+
set -e
|
101
|
+
|
102
|
+
sudo mkdir -p /usr/local/backups
|
103
|
+
sudo chown deploy:deploy /usr/local/backups
|
104
|
+
|
105
|
+
BACKUP_DIR="/usr/local/backups"
|
106
|
+
SCRIPT_ID="setup_docker_prereqs"
|
107
|
+
BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
108
|
+
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
109
|
+
|
110
|
+
TARGET_PKGS=(
|
111
|
+
apt-transport-https
|
112
|
+
ca-certificates
|
113
|
+
curl
|
114
|
+
gnupg
|
115
|
+
lsb-release
|
116
|
+
software-properties-common
|
117
|
+
)
|
118
|
+
|
119
|
+
echo "✍🏻 TARGET_PKGS=(\${TARGET_PKGS[*]})"
|
120
|
+
|
121
|
+
if [ -f "$AFTER_FILE" ]; then
|
122
|
+
to_remove=()
|
123
|
+
for pkg in "\${TARGET_PKGS[@]}"; do
|
124
|
+
if dpkg -l "\$pkg" &>/dev/null; then
|
125
|
+
if ! grep -Fxq "\$pkg" "$BEFORE_FILE"; then
|
126
|
+
to_remove+=("\$pkg")
|
127
|
+
fi
|
128
|
+
fi
|
129
|
+
done
|
130
|
+
|
131
|
+
if [ \${#to_remove[@]} -gt 0 ]; then
|
132
|
+
echo "🔁 Removing packages..."
|
133
|
+
sudo apt-get remove -y "\${to_remove[@]}" && echo " - removed: \${to_remove[*]}"
|
134
|
+
else
|
135
|
+
echo " - no new packages to remove"
|
136
|
+
fi
|
137
|
+
|
138
|
+
sudo rm -f "$BEFORE_FILE" "$AFTER_FILE" && echo " - backups removed"
|
139
|
+
else
|
140
|
+
echo " - no marker for $SCRIPT_ID, skipping removal"
|
141
|
+
fi
|
142
|
+
EOH
|
143
|
+
say output
|
144
|
+
say "✅ Docker prerequisites rollback completed", :green
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|