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,241 +0,0 @@
|
|
|
1
|
-
require "thor"
|
|
2
|
-
require "net/ssh"
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "shellwords"
|
|
5
|
-
require_relative "../defaults"
|
|
6
|
-
require_relative "../options_builder"
|
|
7
|
-
require "pry"
|
|
8
|
-
module Kitsune
|
|
9
|
-
module Kit
|
|
10
|
-
module Commands
|
|
11
|
-
class SetupRedisDocker < Thor
|
|
12
|
-
namespace "setup_redis_docker"
|
|
13
|
-
|
|
14
|
-
class_option :server_ip, aliases: "-s", required: true, desc: "Server IP address or hostname"
|
|
15
|
-
class_option :ssh_port, aliases: "-p", desc: "SSH port"
|
|
16
|
-
class_option :ssh_key_path, aliases: "-k", desc: "Path to SSH private key"
|
|
17
|
-
|
|
18
|
-
desc "create", "Setup Redis using Docker Compose on remote server"
|
|
19
|
-
def create
|
|
20
|
-
redis_defaults = Kitsune::Kit::Defaults.redis
|
|
21
|
-
|
|
22
|
-
if redis_defaults[:redis_password] == "secret"
|
|
23
|
-
say "⚠️ Warning: You are using the default Redis password ('secret').", :yellow
|
|
24
|
-
if ENV.fetch("KIT_ENV", "development") == "production"
|
|
25
|
-
abort "❌ Production environment requires a secure Redis password!"
|
|
26
|
-
else
|
|
27
|
-
say "🔒 Please change REDIS_PASSWORD in your .env if needed.", :yellow
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
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_setup(ssh, redis_defaults, filled_options)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
desc "rollback", "Remove Redis Docker setup from remote server"
|
|
43
|
-
def rollback
|
|
44
|
-
redis_defaults = Kitsune::Kit::Defaults.redis
|
|
45
|
-
|
|
46
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
47
|
-
options,
|
|
48
|
-
required: [:server_ip],
|
|
49
|
-
defaults: Kitsune::Kit::Defaults.ssh
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
53
|
-
perform_rollback(ssh, redis_defaults)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
no_commands do
|
|
58
|
-
def with_ssh_connection(filled_options)
|
|
59
|
-
server = filled_options[:server_ip]
|
|
60
|
-
port = filled_options[:ssh_port]
|
|
61
|
-
key = File.expand_path(filled_options[:ssh_key_path])
|
|
62
|
-
|
|
63
|
-
say "🔑 Connecting as deploy@#{server}:#{port}", :green
|
|
64
|
-
Net::SSH.start(server, "deploy", port: port, keys: [key], non_interactive: true, timeout: 5) do |ssh|
|
|
65
|
-
yield ssh
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def perform_setup(ssh, redis_defaults, filled_options)
|
|
70
|
-
local_compose = ".kitsune/docker/redis.yml"
|
|
71
|
-
remote_dir = "$HOME/docker/redis"
|
|
72
|
-
compose_remote = "#{remote_dir}/docker-compose.yml"
|
|
73
|
-
env_remote = "#{remote_dir}/.env"
|
|
74
|
-
marker = "/usr/local/backups/setup_redis_docker.after"
|
|
75
|
-
|
|
76
|
-
abort "❌ Missing #{local_compose}" unless File.exist?(local_compose)
|
|
77
|
-
|
|
78
|
-
# 1. Create base directory securely
|
|
79
|
-
ssh.exec!("mkdir -p #{remote_dir} && chmod 700 #{remote_dir}")
|
|
80
|
-
|
|
81
|
-
# 2. Upload docker-compose.yml
|
|
82
|
-
say "📦 Uploading docker-compose.yml to #{remote_dir}", :cyan
|
|
83
|
-
upload_file(ssh, File.read(local_compose), compose_remote)
|
|
84
|
-
|
|
85
|
-
# 3. Create .env file for docker-compose based on redis_defaults
|
|
86
|
-
env_content = <<~ENVFILE
|
|
87
|
-
REDIS_PORT=#{redis_defaults[:redis_port]}
|
|
88
|
-
REDIS_PASSWORD=#{redis_defaults[:redis_password]}
|
|
89
|
-
ENVFILE
|
|
90
|
-
upload_file(ssh, env_content, env_remote)
|
|
91
|
-
|
|
92
|
-
# 4. Secure file permissions
|
|
93
|
-
ssh.exec!("chmod 600 #{compose_remote} #{env_remote}")
|
|
94
|
-
|
|
95
|
-
# 5. Create a backup marker
|
|
96
|
-
ssh.exec!("sudo mkdir -p /usr/local/backups && sudo touch #{marker}")
|
|
97
|
-
|
|
98
|
-
# 6. Validate docker-compose.yml
|
|
99
|
-
say "🔍 Validating docker-compose.yml...", :cyan
|
|
100
|
-
validation_output = ssh.exec!("cd #{remote_dir} && docker compose config")
|
|
101
|
-
say validation_output, :cyan
|
|
102
|
-
|
|
103
|
-
# 7. Check if container is running
|
|
104
|
-
container_status = ssh.exec!("docker ps --filter 'name=redis' --format '{{.Status}}'").strip
|
|
105
|
-
|
|
106
|
-
if container_status.empty?
|
|
107
|
-
say "▶️ No running container. Running docker compose up...", :cyan
|
|
108
|
-
ssh.exec!("cd #{remote_dir} && docker compose up -d")
|
|
109
|
-
else
|
|
110
|
-
say "⚠️ Redis container is already running.", :yellow
|
|
111
|
-
if yes?("🔁 Recreate the container with updated configuration? [y/N]", :yellow)
|
|
112
|
-
say "🔄 Recreating container...", :cyan
|
|
113
|
-
ssh.exec!("cd #{remote_dir} && docker compose down -v && docker compose up -d")
|
|
114
|
-
else
|
|
115
|
-
say "⏩ Keeping existing container.", :cyan
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# 8. Check container status
|
|
120
|
-
say "📋 Final container status (docker compose ps):", :cyan
|
|
121
|
-
docker_ps_output = ssh.exec!("cd #{remote_dir} && docker compose ps --format json")
|
|
122
|
-
|
|
123
|
-
if docker_ps_output.nil? || docker_ps_output.strip.empty? || docker_ps_output.include?("no configuration file")
|
|
124
|
-
say "⚠️ docker compose ps returned no valid output.", :yellow
|
|
125
|
-
else
|
|
126
|
-
begin
|
|
127
|
-
services = JSON.parse(docker_ps_output)
|
|
128
|
-
services = [services] if services.is_a?(Hash)
|
|
129
|
-
|
|
130
|
-
redis = services.find { |svc| svc["Service"] == "redis" }
|
|
131
|
-
status = redis && redis["State"]
|
|
132
|
-
health = redis && redis["Health"]
|
|
133
|
-
|
|
134
|
-
if (status == "running" && health == "healthy") || (health == "healthy")
|
|
135
|
-
say "✅ Redis container is running and healthy.", :green
|
|
136
|
-
else
|
|
137
|
-
say "⚠️ Redis container is not healthy yet.", :yellow
|
|
138
|
-
end
|
|
139
|
-
rescue JSON::ParserError => e
|
|
140
|
-
say "🚨 Failed to parse docker compose ps output as JSON: #{e.message}", :red
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# 9. Check Redis readiness with retries
|
|
145
|
-
say "🔍 Checking Redis health with retries...", :cyan
|
|
146
|
-
|
|
147
|
-
max_attempts = 10
|
|
148
|
-
attempt = 0
|
|
149
|
-
success = false
|
|
150
|
-
|
|
151
|
-
while attempt < max_attempts
|
|
152
|
-
attempt += 1
|
|
153
|
-
healthcheck = ssh.exec!("docker exec $(docker ps -qf name=redis) redis-cli --no-auth-warning -a \"#{redis_defaults[:redis_password]}\" PING")
|
|
154
|
-
|
|
155
|
-
if healthcheck.strip == "PONG"
|
|
156
|
-
say "✅ Redis is up and responding to PING! (attempt #{attempt})", :green
|
|
157
|
-
success = true
|
|
158
|
-
|
|
159
|
-
redis_url = build_redis_url(filled_options, redis_defaults)
|
|
160
|
-
say "🔗 Your REDIS_URL is:\t", :cyan
|
|
161
|
-
say redis_url, :green
|
|
162
|
-
break
|
|
163
|
-
else
|
|
164
|
-
say "⏳ Redis not ready yet, retrying in 5 seconds... (#{attempt}/#{max_attempts})", :yellow
|
|
165
|
-
sleep 5
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
unless success
|
|
170
|
-
say "❌ Redis did not become ready after #{max_attempts} attempts.", :red
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# 10. Allow Redis port through firewall (ufw)
|
|
174
|
-
say "🛡️ Configuring firewall to allow Redis (port #{redis_defaults[:redis_port]})...", :cyan
|
|
175
|
-
output = ssh.exec! <<~EOH
|
|
176
|
-
if command -v ufw >/dev/null; then
|
|
177
|
-
if ! sudo ufw status | grep -q "#{redis_defaults[:redis_port]}"; then
|
|
178
|
-
sudo ufw allow #{redis_defaults[:redis_port]}
|
|
179
|
-
else
|
|
180
|
-
echo "💡 Port #{redis_defaults[:redis_port]} is already allowed in ufw."
|
|
181
|
-
fi
|
|
182
|
-
else
|
|
183
|
-
echo "⚠️ ufw not found. Skipping firewall configuration."
|
|
184
|
-
fi
|
|
185
|
-
EOH
|
|
186
|
-
say output
|
|
187
|
-
|
|
188
|
-
say "✅ Redis setup completed successfully!", :green
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def perform_rollback(ssh, defaults)
|
|
192
|
-
output = ssh.exec! <<~EOH
|
|
193
|
-
set -e
|
|
194
|
-
|
|
195
|
-
BASE_DIR="$HOME/docker/redis"
|
|
196
|
-
BACKUP_DIR="/usr/local/backups"
|
|
197
|
-
SCRIPT_ID="setup_redis_docker"
|
|
198
|
-
AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
199
|
-
|
|
200
|
-
if [ -f "$AFTER_FILE" ]; then
|
|
201
|
-
echo "🔁 Stopping and removing docker containers..."
|
|
202
|
-
cd "$BASE_DIR"
|
|
203
|
-
docker compose down -v || true
|
|
204
|
-
|
|
205
|
-
echo "🧹 Cleaning up files..."
|
|
206
|
-
rm -rf "$BASE_DIR"
|
|
207
|
-
sudo rm -f "$AFTER_FILE"
|
|
208
|
-
|
|
209
|
-
if command -v ufw >/dev/null; then
|
|
210
|
-
echo "🛡️ Removing Redis port from firewall..."
|
|
211
|
-
sudo ufw delete allow #{defaults[:redis_port]} || true
|
|
212
|
-
fi
|
|
213
|
-
else
|
|
214
|
-
echo "💡 Nothing to rollback"
|
|
215
|
-
fi
|
|
216
|
-
|
|
217
|
-
echo "✅ Rollback completed"
|
|
218
|
-
EOH
|
|
219
|
-
|
|
220
|
-
say output
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def upload_file(ssh, content, remote_path)
|
|
225
|
-
escaped = Shellwords.escape(content)
|
|
226
|
-
ssh.exec!("mkdir -p #{File.dirname(remote_path)}")
|
|
227
|
-
ssh.exec!("echo #{escaped} > #{remote_path}")
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def build_redis_url(filled_options, redis_defaults)
|
|
231
|
-
password = redis_defaults[:redis_password]
|
|
232
|
-
host = filled_options[:server_ip]
|
|
233
|
-
port = redis_defaults[:redis_port]
|
|
234
|
-
|
|
235
|
-
"redis://:#{password}@#{host}:#{port}/0"
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
end
|
|
@@ -1,151 +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 SetupSwap < Thor
|
|
10
|
-
namespace "setup_swap"
|
|
11
|
-
|
|
12
|
-
class_option :server_ip, aliases: "-s", required: true, desc: "Server IP address"
|
|
13
|
-
class_option :ssh_port, aliases: "-p", desc: "SSH port"
|
|
14
|
-
class_option :ssh_key_path, aliases: "-k", desc: "SSH private key path"
|
|
15
|
-
class_option :size_gb, type: :numeric, desc: "Swap size in GB"
|
|
16
|
-
class_option :swappiness, type: :numeric, desc: "vm.swappiness value (0-100)"
|
|
17
|
-
|
|
18
|
-
desc "create", "Create and activate a swap file on the remote server"
|
|
19
|
-
def create
|
|
20
|
-
if Kitsune::Kit::Defaults.system[:disable_swap]
|
|
21
|
-
say "⚠️ Swap setup is disabled via DISABLE_SWAP=true", :yellow
|
|
22
|
-
return
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
26
|
-
options,
|
|
27
|
-
required: [:server_ip],
|
|
28
|
-
defaults: Kitsune::Kit::Defaults.system.merge(Kitsune::Kit::Defaults.ssh)
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
32
|
-
perform_setup(ssh, filled_options)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
desc "rollback", "Disable and remove swap file from remote server"
|
|
37
|
-
def rollback
|
|
38
|
-
filled_options = Kitsune::Kit::OptionsBuilder.build(
|
|
39
|
-
options,
|
|
40
|
-
required: [:server_ip],
|
|
41
|
-
defaults: Kitsune::Kit::Defaults.system.merge(Kitsune::Kit::Defaults.ssh)
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
with_ssh_connection(filled_options) do |ssh|
|
|
45
|
-
perform_rollback(ssh)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
no_commands do
|
|
50
|
-
def with_ssh_connection(filled)
|
|
51
|
-
server = filled[:server_ip]
|
|
52
|
-
port = filled[:ssh_port]
|
|
53
|
-
key = File.expand_path(filled[:ssh_key_path])
|
|
54
|
-
|
|
55
|
-
say "🔑 Connecting as deploy@#{server}:#{port}", :green
|
|
56
|
-
Net::SSH.start(server, "deploy", port: port, keys: [key], non_interactive: true, timeout: 5) do |ssh|
|
|
57
|
-
yield ssh
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def perform_setup(ssh, filled_options)
|
|
62
|
-
size_gb = filled_options[:swap_size_gb].to_i
|
|
63
|
-
swappiness = filled_options[:swap_swappiness].to_i
|
|
64
|
-
|
|
65
|
-
abort "❌ Invalid swap size" if size_gb <= 0
|
|
66
|
-
|
|
67
|
-
script = <<~EOH
|
|
68
|
-
set -e
|
|
69
|
-
|
|
70
|
-
BACKUP_DIR="/usr/local/backups"
|
|
71
|
-
MARKER_FILE="${BACKUP_DIR}/setup_swap.after"
|
|
72
|
-
SWAPPINESS_BEFORE_FILE="${BACKUP_DIR}/setup_swap.swappiness.before"
|
|
73
|
-
|
|
74
|
-
if [ -f "$MARKER_FILE" ]; then
|
|
75
|
-
echo "🔁 Swap already set up, skipping."
|
|
76
|
-
exit 0
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
SWAPFILE="/swapfile"
|
|
80
|
-
SIZE_GB=#{size_gb}
|
|
81
|
-
SIZE_BYTES=$((SIZE_GB * 1024 * 1024 * 1024))
|
|
82
|
-
|
|
83
|
-
echo "📝 Creating ${SIZE_GB}GB swap file..."
|
|
84
|
-
sudo fallocate -l ${SIZE_BYTES} ${SWAPFILE}
|
|
85
|
-
sudo chmod 600 ${SWAPFILE}
|
|
86
|
-
sudo mkswap ${SWAPFILE}
|
|
87
|
-
sudo swapon ${SWAPFILE}
|
|
88
|
-
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
|
|
89
|
-
|
|
90
|
-
# Backup swappiness if not already backed up
|
|
91
|
-
if [ ! -f "$SWAPPINESS_BEFORE_FILE" ]; then
|
|
92
|
-
CURRENT=$(cat /proc/sys/vm/swappiness)
|
|
93
|
-
echo "$CURRENT" | sudo tee "$SWAPPINESS_BEFORE_FILE"
|
|
94
|
-
echo "✍🏻 Backed up vm.swappiness: $CURRENT"
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
echo "🛠️ Setting vm.swappiness=#{swappiness}"
|
|
98
|
-
sudo sysctl vm.swappiness=#{swappiness}
|
|
99
|
-
echo "vm.swappiness=#{swappiness}" | sudo tee -a /etc/sysctl.conf
|
|
100
|
-
|
|
101
|
-
sudo mkdir -p "$BACKUP_DIR"
|
|
102
|
-
sudo touch "$MARKER_FILE"
|
|
103
|
-
|
|
104
|
-
echo "✅ Swap file created and swappiness set"
|
|
105
|
-
free -h
|
|
106
|
-
EOH
|
|
107
|
-
|
|
108
|
-
say ssh.exec!(script)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def perform_rollback(ssh)
|
|
112
|
-
script = <<~EOH
|
|
113
|
-
set -e
|
|
114
|
-
|
|
115
|
-
BACKUP_DIR="/usr/local/backups"
|
|
116
|
-
MARKER_FILE="${BACKUP_DIR}/setup_swap.after"
|
|
117
|
-
SWAPPINESS_BEFORE_FILE="${BACKUP_DIR}/setup_swap.swappiness.before"
|
|
118
|
-
|
|
119
|
-
if [ ! -f "$MARKER_FILE" ]; then
|
|
120
|
-
echo "💡 No swap marker found, skipping rollback."
|
|
121
|
-
exit 0
|
|
122
|
-
fi
|
|
123
|
-
|
|
124
|
-
echo "🧹 Removing swap..."
|
|
125
|
-
sudo swapoff /swapfile || true
|
|
126
|
-
sudo rm -f /swapfile
|
|
127
|
-
sudo sed -i '/\\/swapfile none swap sw 0 0/d' /etc/fstab
|
|
128
|
-
sudo rm -f "$MARKER_FILE"
|
|
129
|
-
|
|
130
|
-
if [ -f "$SWAPPINESS_BEFORE_FILE" ]; then
|
|
131
|
-
ORIGINAL=$(cat "$SWAPPINESS_BEFORE_FILE")
|
|
132
|
-
echo "🔁 Restoring vm.swappiness: $ORIGINAL"
|
|
133
|
-
sudo sysctl vm.swappiness=$ORIGINAL
|
|
134
|
-
sudo sed -i '/vm.swappiness=/d' /etc/sysctl.conf
|
|
135
|
-
echo "vm.swappiness=$ORIGINAL" | sudo tee -a /etc/sysctl.conf
|
|
136
|
-
sudo rm -f "$SWAPPINESS_BEFORE_FILE"
|
|
137
|
-
else
|
|
138
|
-
echo "💡 No swappiness backup found, skipping restore."
|
|
139
|
-
fi
|
|
140
|
-
|
|
141
|
-
echo "✅ Swap rollback completed"
|
|
142
|
-
free -h
|
|
143
|
-
EOH
|
|
144
|
-
|
|
145
|
-
say ssh.exec!(script)
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
@@ -1,132 +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 SetupUnattended < Thor
|
|
10
|
-
namespace "setup_unattended"
|
|
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", "Configure unattended-upgrades 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", "Revert unattended-upgrades configuration on 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
|
-
RESOURCE="/etc/apt/apt.conf.d/20auto-upgrades"
|
|
62
|
-
BACKUP_DIR="/usr/local/backups"
|
|
63
|
-
SCRIPT_ID="setup_unattended"
|
|
64
|
-
BACKUP_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
|
65
|
-
MARKER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
66
|
-
|
|
67
|
-
echo "✍🏻 Installing required packages…"
|
|
68
|
-
if ! dpkg -l | grep -q "^ii\\s*unattended-upgrades"; then
|
|
69
|
-
sudo apt-get update -y
|
|
70
|
-
sudo apt-get install -y unattended-upgrades apt-listchanges && echo " - packages installed"
|
|
71
|
-
else
|
|
72
|
-
echo " - unattended-upgrades already installed"
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
if [ ! -f "$MARKER_FILE" ]; then
|
|
76
|
-
echo "✍🏻 Backing up existing config…"
|
|
77
|
-
sudo cp "$RESOURCE" "$BACKUP_FILE" && echo " - backup saved to $BACKUP_FILE"
|
|
78
|
-
sudo touch "$MARKER_FILE" && echo " - marker created at $MARKER_FILE"
|
|
79
|
-
else
|
|
80
|
-
echo " - backup & marker already exist"
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
echo "✍🏻 Applying new auto-upgrades config…"
|
|
84
|
-
sudo tee "$RESOURCE" > /dev/null <<UPGR
|
|
85
|
-
APT::Periodic::Update-Package-Lists "1";
|
|
86
|
-
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
87
|
-
APT::Periodic::AutocleanInterval "7";
|
|
88
|
-
APT::Periodic::Unattended-Upgrade "1";
|
|
89
|
-
UPGR
|
|
90
|
-
echo " - config applied"
|
|
91
|
-
|
|
92
|
-
echo "✍🏻 Enabling & restarting unattended-upgrades…"
|
|
93
|
-
sudo systemctl --quiet enable unattended-upgrades.service >/dev/null 2>&1 && echo " - service enabled"
|
|
94
|
-
sudo systemctl --quiet restart unattended-upgrades.service && echo " - service restarted"
|
|
95
|
-
EOH
|
|
96
|
-
say output
|
|
97
|
-
say "✅ Unattended-upgrades setup completed", :green
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def perform_rollback(ssh)
|
|
101
|
-
output = ssh.exec! <<~EOH
|
|
102
|
-
set -e
|
|
103
|
-
|
|
104
|
-
sudo mkdir -p /usr/local/backups
|
|
105
|
-
sudo chown deploy:deploy /usr/local/backups
|
|
106
|
-
|
|
107
|
-
RESOURCE="/etc/apt/apt.conf.d/20auto-upgrades"
|
|
108
|
-
BACKUP_DIR="/usr/local/backups"
|
|
109
|
-
SCRIPT_ID="setup_unattended"
|
|
110
|
-
BACKUP_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
|
|
111
|
-
MARKER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
|
|
112
|
-
|
|
113
|
-
if [ -f "$MARKER_FILE" ]; then
|
|
114
|
-
echo "🔁 Restoring original auto-upgrades config…"
|
|
115
|
-
sudo mv "$BACKUP_FILE" "$RESOURCE" && echo " - config restored from $BACKUP_FILE"
|
|
116
|
-
sudo rm -f "$MARKER_FILE" && echo " - marker $MARKER_FILE removed"
|
|
117
|
-
else
|
|
118
|
-
echo " - no marker for $SCRIPT_ID, skipping restore"
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
echo "✍🏻 Stopping & disabling unattended-upgrades…"
|
|
122
|
-
sudo systemctl --quiet stop unattended-upgrades.service apt-daily.timer apt-daily-upgrade.timer && echo " - services stopped"
|
|
123
|
-
sudo systemctl --quiet disable unattended-upgrades.service && echo " - service disabled"
|
|
124
|
-
EOH
|
|
125
|
-
say output
|
|
126
|
-
say "✅ Unattended-upgrades rollback completed", :green
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|