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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -2
  3. data/CONTRIBUTING.md +83 -0
  4. data/README.md +112 -135
  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/decisions/0007-compose-customization.md +30 -0
  14. data/docs/architecture/provider-adapters.md +49 -0
  15. data/docs/architecture.md +93 -0
  16. data/docs/commands.md +224 -0
  17. data/docs/configuration.md +226 -0
  18. data/docs/getting-started.md +133 -0
  19. data/docs/providers/digitalocean.md +52 -0
  20. data/docs/releasing.md +43 -0
  21. data/docs/roadmap.md +46 -0
  22. data/docs/security-audit.md +130 -0
  23. data/docs/security.md +116 -0
  24. data/docs/services/compose.md +164 -0
  25. data/docs/services/postgres.md +78 -0
  26. data/docs/services/redis.md +53 -0
  27. data/docs/testing.md +200 -0
  28. data/docs/troubleshooting.md +123 -0
  29. data/docs/tui.md +57 -0
  30. data/lib/kitsune/kit/adapters/confirming_host_key_verifier.rb +42 -0
  31. data/lib/kitsune/kit/adapters/digitalocean_provider.rb +217 -0
  32. data/lib/kitsune/kit/adapters/fake_clock.rb +34 -0
  33. data/lib/kitsune/kit/adapters/fake_provider.rb +107 -0
  34. data/lib/kitsune/kit/adapters/fake_reporter.rb +34 -0
  35. data/lib/kitsune/kit/adapters/fake_secret_store.rb +35 -0
  36. data/lib/kitsune/kit/adapters/fake_state_store.rb +91 -0
  37. data/lib/kitsune/kit/adapters/fake_transport.rb +56 -0
  38. data/lib/kitsune/kit/adapters/net_ssh_transport.rb +155 -0
  39. data/lib/kitsune/kit/adapters/provider.rb +34 -0
  40. data/lib/kitsune/kit/adapters/transport.rb +22 -0
  41. data/lib/kitsune/kit/adapters/transport_factory.rb +116 -0
  42. data/lib/kitsune/kit/application.rb +170 -0
  43. data/lib/kitsune/kit/cancellation.rb +21 -0
  44. data/lib/kitsune/kit/cli.rb +879 -64
  45. data/lib/kitsune/kit/clock.rb +11 -0
  46. data/lib/kitsune/kit/configuration.rb +577 -0
  47. data/lib/kitsune/kit/errors.rb +98 -0
  48. data/lib/kitsune/kit/events.rb +55 -0
  49. data/lib/kitsune/kit/operations/ensure_dns_records.rb +165 -0
  50. data/lib/kitsune/kit/operations/ensure_server.rb +135 -0
  51. data/lib/kitsune/kit/operations/ensure_service.rb +302 -0
  52. data/lib/kitsune/kit/operations/remote_script.rb +214 -0
  53. data/lib/kitsune/kit/operations/service_backup.rb +71 -0
  54. data/lib/kitsune/kit/operations/service_files.rb +131 -0
  55. data/lib/kitsune/kit/operations/service_firewall.rb +190 -0
  56. data/lib/kitsune/kit/operations/service_state.rb +59 -0
  57. data/lib/kitsune/kit/plan.rb +72 -0
  58. data/lib/kitsune/kit/reporters/human.rb +89 -0
  59. data/lib/kitsune/kit/reporters/json.rb +65 -0
  60. data/lib/kitsune/kit/reporters/reporter.rb +11 -0
  61. data/lib/kitsune/kit/result.rb +28 -0
  62. data/lib/kitsune/kit/run_journal.rb +102 -0
  63. data/lib/kitsune/kit/run_logger.rb +37 -0
  64. data/lib/kitsune/kit/scripts/docker.sh +142 -0
  65. data/lib/kitsune/kit/scripts/firewall.sh +210 -0
  66. data/lib/kitsune/kit/scripts/metrics.sh +59 -0
  67. data/lib/kitsune/kit/scripts/ssh.sh +95 -0
  68. data/lib/kitsune/kit/scripts/swap.sh +86 -0
  69. data/lib/kitsune/kit/scripts/unattended.sh +86 -0
  70. data/lib/kitsune/kit/scripts/user.sh +114 -0
  71. data/lib/kitsune/kit/secret_filter.rb +54 -0
  72. data/lib/kitsune/kit/secret_store.rb +32 -0
  73. data/lib/kitsune/kit/secret_stores/store.rb +12 -0
  74. data/lib/kitsune/kit/service_compose.rb +272 -0
  75. data/lib/kitsune/kit/state_store.rb +158 -0
  76. data/lib/kitsune/kit/state_stores/store.rb +15 -0
  77. data/lib/kitsune/kit/tui/actions.rb +72 -0
  78. data/lib/kitsune/kit/tui/application.rb +35 -0
  79. data/lib/kitsune/kit/tui/controller.rb +162 -0
  80. data/lib/kitsune/kit/tui/renderer.rb +134 -0
  81. data/lib/kitsune/kit/tui/state.rb +18 -0
  82. data/lib/kitsune/kit/tui/store.rb +88 -0
  83. data/lib/kitsune/kit/tui/terminal.rb +95 -0
  84. data/lib/kitsune/kit/version.rb +1 -1
  85. data/lib/kitsune/kit/workflows/apply_plan.rb +145 -0
  86. data/lib/kitsune/kit/workflows/base.rb +31 -0
  87. data/lib/kitsune/kit/workflows/build_plan.rb +33 -0
  88. data/lib/kitsune/kit/workflows/destroy_server.rb +84 -0
  89. data/lib/kitsune/kit/workflows/doctor.rb +225 -0
  90. data/lib/kitsune/kit/workflows/eject_compose.rb +82 -0
  91. data/lib/kitsune/kit/workflows/environment_selection.rb +70 -0
  92. data/lib/kitsune/kit/workflows/import_server.rb +100 -0
  93. data/lib/kitsune/kit/workflows/initialize_project.rb +129 -0
  94. data/lib/kitsune/kit/workflows/inspect_environment.rb +44 -0
  95. data/lib/kitsune/kit/workflows/rollback.rb +54 -0
  96. data/lib/kitsune/kit/workflows/support_bundle.rb +82 -0
  97. data/lib/kitsune/kit.rb +42 -2
  98. metadata +125 -79
  99. data/.rspec +0 -3
  100. data/Rakefile +0 -8
  101. data/kitsune-kit-logo.jpg +0 -0
  102. data/lib/kitsune/blueprints/.env.template +0 -31
  103. data/lib/kitsune/blueprints/docker/postgres.yml +0 -27
  104. data/lib/kitsune/blueprints/docker/redis.yml +0 -23
  105. data/lib/kitsune/blueprints/kit.env.template +0 -1
  106. data/lib/kitsune/kit/ansi_color.rb +0 -78
  107. data/lib/kitsune/kit/commands/bootstrap.rb +0 -134
  108. data/lib/kitsune/kit/commands/bootstrap_docker.rb +0 -66
  109. data/lib/kitsune/kit/commands/dns.rb +0 -112
  110. data/lib/kitsune/kit/commands/init.rb +0 -148
  111. data/lib/kitsune/kit/commands/install_docker_engine.rb +0 -146
  112. data/lib/kitsune/kit/commands/postinstall_docker.rb +0 -142
  113. data/lib/kitsune/kit/commands/provision.rb +0 -43
  114. data/lib/kitsune/kit/commands/setup_do_metrics.rb +0 -123
  115. data/lib/kitsune/kit/commands/setup_docker_prereqs.rb +0 -151
  116. data/lib/kitsune/kit/commands/setup_firewall.rb +0 -132
  117. data/lib/kitsune/kit/commands/setup_postgres_docker.rb +0 -246
  118. data/lib/kitsune/kit/commands/setup_redis_docker.rb +0 -241
  119. data/lib/kitsune/kit/commands/setup_swap.rb +0 -151
  120. data/lib/kitsune/kit/commands/setup_unattended.rb +0 -132
  121. data/lib/kitsune/kit/commands/setup_user.rb +0 -189
  122. data/lib/kitsune/kit/commands/ssh.rb +0 -46
  123. data/lib/kitsune/kit/commands/switch_env.rb +0 -42
  124. data/lib/kitsune/kit/defaults.rb +0 -91
  125. data/lib/kitsune/kit/env_loader.rb +0 -41
  126. data/lib/kitsune/kit/options_builder.rb +0 -26
  127. data/lib/kitsune/kit/provisioner.rb +0 -107
  128. data/sig/kitsune/kit.rbs +0 -6
@@ -1,123 +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 SetupDoMetrics < Thor
10
- namespace "setup_do_metrics"
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
-
16
- desc "create", "Install and enable the DigitalOcean Metrics Agent"
17
- def create
18
- unless Kitsune::Kit::Defaults.metrics[:enable_do_metrics]
19
- say "โš ๏ธ DigitalOcean Metrics Agent setup is disabled via ENABLE_DO_METRICS=false", :yellow
20
- return
21
- end
22
-
23
- filled_options = Kitsune::Kit::OptionsBuilder.build(
24
- options,
25
- required: [:server_ip],
26
- defaults: Kitsune::Kit::Defaults.ssh
27
- )
28
-
29
- with_ssh_connection(filled_options) do |ssh|
30
- install_agent(ssh)
31
- end
32
- end
33
-
34
- desc "rollback", "Uninstall the DigitalOcean Metrics Agent"
35
- def rollback
36
- filled_options = Kitsune::Kit::OptionsBuilder.build(
37
- options,
38
- required: [:server_ip],
39
- defaults: Kitsune::Kit::Defaults.ssh
40
- )
41
-
42
- with_ssh_connection(filled_options) do |ssh|
43
- uninstall_agent(ssh)
44
- end
45
- end
46
-
47
- no_commands do
48
- def with_ssh_connection(filled_options)
49
- Net::SSH.start(
50
- filled_options[:server_ip],
51
- "deploy",
52
- port: filled_options[:ssh_port],
53
- keys: [File.expand_path(filled_options[:ssh_key_path])],
54
- non_interactive: true,
55
- timeout: 5
56
- ) do |ssh|
57
- yield ssh
58
- end
59
- end
60
-
61
- def install_agent(ssh)
62
- marker = "/usr/local/backups/setup_do_metrics.after"
63
-
64
- script = <<~BASH
65
- set -e
66
- sudo mkdir -p /usr/local/backups
67
-
68
- if [ -f "#{marker}" ]; then
69
- echo "๐Ÿ” Metrics Agent already installed, skipping."
70
- exit 0
71
- fi
72
-
73
- echo "โœ๐Ÿป Installing DigitalOcean Metrics Agent..."
74
- curl -sSL https://repos.insights.digitalocean.com/install.sh | sudo bash
75
-
76
- sudo touch "#{marker}"
77
- echo "โœ… Metrics Agent installed"
78
-
79
- ps aux | grep do-agent | grep -v grep || echo "โš ๏ธ do-agent process not found"
80
- BASH
81
-
82
- say ssh.exec!(script)
83
- end
84
-
85
- def uninstall_agent(ssh)
86
- marker = "/usr/local/backups/setup_do_metrics.after"
87
-
88
- script = <<~BASH
89
- set -e
90
-
91
- if [ ! -f "#{marker}" ]; then
92
- echo "๐Ÿ’ก No marker for metrics agent found. Skipping rollback."
93
- exit 0
94
- fi
95
-
96
- echo "๐Ÿงน Uninstalling DigitalOcean Metrics Agent..."
97
-
98
- if dpkg -l | grep -q do-agent; then
99
- echo "๐Ÿ“ฆ Removing do-agent package..."
100
- sudo apt-get remove --purge -y do-agent
101
- else
102
- echo "๐Ÿ’ก do-agent is not installed, skipping package removal."
103
- fi
104
-
105
- if systemctl list-unit-files | grep -q do-agent.service; then
106
- echo "โœ๐Ÿป Stopping do-agent service..."
107
- sudo systemctl stop do-agent || true
108
- sudo systemctl disable do-agent || true
109
- else
110
- echo "๐Ÿ’ก do-agent.service not found in systemd, skipping stop/disable"
111
- fi
112
-
113
- sudo rm -f "#{marker}"
114
- echo "โœ… Metrics Agent removed"
115
- BASH
116
-
117
- say ssh.exec!(script)
118
- end
119
- end
120
- end
121
- end
122
- end
123
- end
@@ -1,151 +0,0 @@
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
- echo "๐Ÿ” Already set up, ensuring latest"
90
- sudo apt-get update -y
91
- sudo apt-get install -y "\${TARGET_PKGS[@]}"
92
- echo "โœ… Prerequisites are current"
93
- fi
94
- EOH
95
- say output
96
- say "โœ… Docker prerequisites setup completed", :green
97
- end
98
-
99
- def perform_rollback(ssh)
100
- output = ssh.exec! <<~EOH
101
- set -e
102
-
103
- sudo mkdir -p /usr/local/backups
104
- sudo chown deploy:deploy /usr/local/backups
105
-
106
- BACKUP_DIR="/usr/local/backups"
107
- SCRIPT_ID="setup_docker_prereqs"
108
- BEFORE_FILE="${BACKUP_DIR}/${SCRIPT_ID}.before"
109
- AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
110
-
111
- TARGET_PKGS=(
112
- apt-transport-https
113
- ca-certificates
114
- curl
115
- gnupg
116
- lsb-release
117
- software-properties-common
118
- )
119
-
120
- echo "โœ๐Ÿป TARGET_PKGS=(\${TARGET_PKGS[*]})"
121
-
122
- if [ -f "$AFTER_FILE" ]; then
123
- to_remove=()
124
- for pkg in "\${TARGET_PKGS[@]}"; do
125
- if dpkg -l "\$pkg" &>/dev/null; then
126
- if ! grep -Fxq "\$pkg" "$BEFORE_FILE"; then
127
- to_remove+=("\$pkg")
128
- fi
129
- fi
130
- done
131
-
132
- if [ \${#to_remove[@]} -gt 0 ]; then
133
- echo "๐Ÿ” Removing packages..."
134
- sudo apt-get remove -y "\${to_remove[@]}" && echo " - removed: \${to_remove[*]}"
135
- else
136
- echo " - no new packages to remove"
137
- fi
138
-
139
- sudo rm -f "$BEFORE_FILE" "$AFTER_FILE" && echo " - backups removed"
140
- else
141
- echo " - no marker for $SCRIPT_ID, skipping removal"
142
- fi
143
- EOH
144
- say output
145
- say "โœ… Docker prerequisites rollback completed", :green
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 SetupFirewall < Thor
10
- namespace "setup_firewall"
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", "Setup UFW firewall rules 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, filled_options)
26
- end
27
- end
28
-
29
- desc "rollback", "Remove UFW firewall rules and disable UFW 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, filled_options)
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, filled_options)
55
- ssh_port = filled_options[:ssh_port]
56
-
57
- output = ssh.exec! <<~EOH
58
- set -e
59
-
60
- echo "โœ๐Ÿป Updating repositories and ensuring UFW is installedโ€ฆ"
61
- if ! dpkg -l | grep -q ufw; then
62
- sudo apt-get update -y
63
- sudo apt-get install -y ufw && echo " - ufw installed"
64
- else
65
- echo " - ufw is already installed"
66
- fi
67
-
68
- echo "โœ๐Ÿป Configuring UFW rulesโ€ฆ"
69
- add_rule() {
70
- local rule="$1"
71
- if ! sudo ufw status | grep -q "$rule"; then
72
- sudo ufw allow "$rule" >/dev/null 2>&1 && echo " - rule '$rule' added"
73
- else
74
- echo " - rule '$rule' already exists"
75
- fi
76
- }
77
- add_rule "#{ssh_port}/tcp"
78
- add_rule "80/tcp"
79
- add_rule "443/tcp"
80
-
81
- echo "โœ๐Ÿป Enabling UFW loggingโ€ฆ"
82
- if ! sudo ufw status verbose | grep -q "Logging: on"; then
83
- sudo ufw logging on >/dev/null 2>&1 && echo " - logging enabled"
84
- else
85
- echo " - logging was already enabled"
86
- fi
87
-
88
- echo "โœ๐Ÿป Enabling UFWโ€ฆ"
89
- if sudo ufw status | grep -q "Status: inactive"; then
90
- sudo ufw --force enable >/dev/null 2>&1 && echo " - UFW enabled"
91
- else
92
- echo " - UFW is already enabled"
93
- fi
94
- EOH
95
- say output
96
- say "โœ… Firewall setup completed", :green
97
- end
98
-
99
- def perform_rollback(ssh, filled_options)
100
- ssh_port = filled_options[:ssh_port]
101
-
102
- output = ssh.exec! <<~EOH
103
- set -e
104
-
105
- echo "๐Ÿ” Removing UFW rulesโ€ฆ"
106
- delete_rule() {
107
- local rule="$1"
108
- if sudo ufw status | grep -q "$rule"; then
109
- sudo ufw delete allow "$rule" >/dev/null 2>&1 && echo " - rule '$rule' removed"
110
- else
111
- echo " - rule '$rule' does not exist"
112
- fi
113
- }
114
- delete_rule "#{ssh_port}/tcp"
115
- delete_rule "80/tcp"
116
- delete_rule "443/tcp"
117
-
118
- echo "โœ๐Ÿป Disabling UFW if activeโ€ฆ"
119
- if sudo ufw status | grep -q "Status: inactive"; then
120
- echo " - UFW is already inactive"
121
- else
122
- sudo ufw --force disable >/dev/null 2>&1 && echo " - UFW disabled"
123
- fi
124
- EOH
125
- say output
126
- say "โœ… Firewall rollback completed", :green
127
- end
128
- end
129
- end
130
- end
131
- end
132
- end
@@ -1,246 +0,0 @@
1
- require "thor"
2
- require "net/ssh"
3
- require "tempfile"
4
- require "fileutils"
5
- require "shellwords"
6
- require_relative "../defaults"
7
- require_relative "../options_builder"
8
-
9
- module Kitsune
10
- module Kit
11
- module Commands
12
- class SetupPostgresDocker < Thor
13
- namespace "setup_postgres_docker"
14
-
15
- class_option :server_ip, aliases: "-s", required: true, desc: "Server IP address or hostname"
16
- class_option :ssh_port, aliases: "-p", desc: "SSH port"
17
- class_option :ssh_key_path, aliases: "-k", desc: "Path to SSH private key"
18
-
19
- desc "create", "Setup PostgreSQL using Docker Compose on remote server"
20
- def create
21
- postgres_defaults = Kitsune::Kit::Defaults.postgres
22
-
23
- if postgres_defaults[:postgres_password] == "secret"
24
- say "โš ๏ธ Warning: You are using the default PostgreSQL password ('secret').", :yellow
25
- if ENV.fetch("KIT_ENV", "development") == "production"
26
- abort "โŒ Production environment requires a secure PostgreSQL password!"
27
- else
28
- say "๐Ÿ”’ Please change POSTGRES_PASSWORD in your .env if needed.", :yellow
29
- end
30
- end
31
-
32
- filled_options = Kitsune::Kit::OptionsBuilder.build(
33
- options,
34
- required: [:server_ip],
35
- defaults: Kitsune::Kit::Defaults.ssh
36
- )
37
-
38
- with_ssh_connection(filled_options) do |ssh|
39
- perform_setup(ssh, postgres_defaults, filled_options)
40
- end
41
- end
42
-
43
- desc "rollback", "Remove PostgreSQL Docker setup from remote server"
44
- def rollback
45
- postgres_defaults = Kitsune::Kit::Defaults.postgres
46
-
47
- filled_options = Kitsune::Kit::OptionsBuilder.build(
48
- options,
49
- required: [:server_ip],
50
- defaults: Kitsune::Kit::Defaults.ssh
51
- )
52
-
53
- with_ssh_connection(filled_options) do |ssh|
54
- perform_rollback(ssh, postgres_defaults)
55
- end
56
- end
57
-
58
- no_commands do
59
- def with_ssh_connection(filled_options)
60
- server = filled_options[:server_ip]
61
- port = filled_options[:ssh_port]
62
- key = File.expand_path(filled_options[:ssh_key_path])
63
-
64
- say "๐Ÿ”‘ Connecting as deploy@#{server}:#{port}", :green
65
- Net::SSH.start(server, "deploy", port: port, keys: [key], non_interactive: true, timeout: 5) do |ssh|
66
- yield ssh
67
- end
68
- end
69
-
70
- def perform_setup(ssh, postgres_defaults, filled_options)
71
- local_compose = ".kitsune/docker/postgres.yml"
72
- remote_dir = "$HOME/docker/postgres"
73
- compose_remote = "#{remote_dir}/docker-compose.yml"
74
- env_remote = "#{remote_dir}/.env"
75
- marker = "/usr/local/backups/setup_postgres_docker.after"
76
-
77
- abort "โŒ Missing #{local_compose}" unless File.exist?(local_compose)
78
-
79
- # 1. Create base directory securely
80
- ssh.exec!("mkdir -p #{remote_dir} && chmod 700 #{remote_dir}")
81
-
82
- # 2. Upload docker-compose.yml
83
- say "๐Ÿ“ฆ Uploading docker-compose.yml to #{remote_dir}", :cyan
84
- upload_file(ssh, File.read(local_compose), compose_remote)
85
-
86
- # 3. Create .env file for docker-compose based on postgres_defaults
87
- say "๐Ÿ“ฆ Creating .env file for Docker Compose...", :cyan
88
- env_content = <<~ENVFILE
89
- POSTGRES_DB=#{postgres_defaults[:postgres_db]}
90
- POSTGRES_USER=#{postgres_defaults[:postgres_user]}
91
- POSTGRES_PASSWORD=#{postgres_defaults[:postgres_password]}
92
- POSTGRES_PORT=#{postgres_defaults[:postgres_port]}
93
- POSTGRES_IMAGE=#{postgres_defaults[:postgres_image]}
94
- ENVFILE
95
- upload_file(ssh, env_content, env_remote)
96
-
97
- # 4. Secure file permissions
98
- ssh.exec!("chmod 600 #{compose_remote} #{env_remote}")
99
-
100
- # 5. Create backup marker
101
- ssh.exec!("sudo mkdir -p /usr/local/backups && sudo touch #{marker}")
102
-
103
- # 6. Validate docker-compose.yml
104
- say "๐Ÿ” Validating docker-compose.yml...", :cyan
105
- validation_output = ssh.exec!("cd #{remote_dir} && docker compose config")
106
- say validation_output, :cyan
107
-
108
- # 7. Check if container is running
109
- container_status = ssh.exec!("docker ps --filter 'name=postgres' --format '{{.Status}}'").strip
110
-
111
- if container_status.empty?
112
- say "โ–ถ๏ธ No running container. Running docker compose up...", :cyan
113
- ssh.exec!("cd #{remote_dir} && docker compose up -d")
114
- else
115
- say "โš ๏ธ PostgreSQL container is already running.", :yellow
116
- if yes?("๐Ÿ” Recreate the container with updated configuration? [y/N]", :yellow)
117
- say "๐Ÿ”„ Recreating container...", :cyan
118
- ssh.exec!("cd #{remote_dir} && docker compose down -v && docker compose up -d")
119
- else
120
- say "โฉ Keeping existing container.", :cyan
121
- end
122
- end
123
-
124
- # 8. Check container status
125
- say "๐Ÿ“‹ Final container status (docker compose ps):", :cyan
126
- docker_ps_output = ssh.exec!("cd #{remote_dir} && docker compose ps --format json")
127
-
128
- if docker_ps_output.nil? || docker_ps_output.strip.empty? || docker_ps_output.include?("no configuration file")
129
- say "โš ๏ธ docker compose ps returned no valid output.", :yellow
130
- else
131
- begin
132
- services = JSON.parse(docker_ps_output)
133
- services = [services] if services.is_a?(Hash)
134
-
135
- postgres = services.find { |svc| svc["Service"] == "postgres" }
136
- status = postgres && postgres["State"]
137
- health = postgres && postgres["Health"]
138
-
139
- if (status == "running" && health == "healthy") || (health == "healthy")
140
- say "โœ… PostgreSQL container is running and healthy.", :green
141
- else
142
- say "โš ๏ธ PostgreSQL container is not healthy yet.", :yellow
143
- end
144
- rescue JSON::ParserError => e
145
- say "๐Ÿšจ Failed to parse docker compose ps output as JSON: #{e.message}", :red
146
- end
147
- end
148
-
149
- # 9. Check PostgreSQL readiness with retries
150
- say "๐Ÿ” Checking PostgreSQL health with retries...", :cyan
151
-
152
- max_attempts = 10
153
- attempt = 0
154
- success = false
155
-
156
- while attempt < max_attempts
157
- attempt += 1
158
- healthcheck = ssh.exec!("docker exec $(docker ps -qf name=postgres) pg_isready -U #{postgres_defaults[:postgres_user]} -d #{postgres_defaults[:postgres_db]} -h localhost")
159
-
160
- if healthcheck.include?("accepting connections")
161
- say "โœ… PostgreSQL is up and accepting connections! (attempt #{attempt})", :green
162
- success = true
163
-
164
- database_url = build_database_url(filled_options, postgres_defaults)
165
- say "๐Ÿ”— Your DATABASE_URL is:\t", :cyan
166
- say database_url, :green
167
- break
168
- else
169
- say "โณ PostgreSQL not ready yet, retrying in 5 seconds... (#{attempt}/#{max_attempts})", :yellow
170
- sleep 5
171
- end
172
- end
173
-
174
- unless success
175
- say "โŒ PostgreSQL did not become ready after #{max_attempts} attempts.", :red
176
- end
177
-
178
- # 10. Allow PostgreSQL port through firewall (ufw)
179
- say "๐Ÿ›ก๏ธ Configuring firewall to allow PostgreSQL (port #{postgres_defaults[:postgres_port]})...", :cyan
180
- output = ssh.exec! <<~EOH
181
- if command -v ufw >/dev/null; then
182
- if ! sudo ufw status | grep -q "#{postgres_defaults[:postgres_port]}"; then
183
- sudo ufw allow #{postgres_defaults[:postgres_port]}
184
- else
185
- echo "๐Ÿ’ก Port #{postgres_defaults[:postgres_port]} is already allowed in ufw."
186
- fi
187
- else
188
- echo "โš ๏ธ ufw not found. Skipping firewall configuration."
189
- fi
190
- EOH
191
- say output
192
-
193
- say "โœ… PostgreSQL setup completed successfully!", :green
194
- end
195
-
196
- def perform_rollback(ssh, postgres_defaults)
197
- output = ssh.exec! <<~EOH
198
- set -e
199
-
200
- BASE_DIR="$HOME/docker/postgres"
201
- BACKUP_DIR="/usr/local/backups"
202
- SCRIPT_ID="setup_postgres_docker"
203
- AFTER_FILE="${BACKUP_DIR}/${SCRIPT_ID}.after"
204
-
205
- if [ -f "$AFTER_FILE" ]; then
206
- echo "๐Ÿ” Stopping and removing docker containers..."
207
- cd "$BASE_DIR"
208
- docker compose down -v || true
209
-
210
- echo "๐Ÿงน Cleaning up files..."
211
- rm -rf "$BASE_DIR"
212
- sudo rm -f "$AFTER_FILE"
213
-
214
- if command -v ufw >/dev/null; then
215
- echo "๐Ÿ›ก๏ธ Removing PostgreSQL port from firewall..."
216
- sudo ufw delete allow #{postgres_defaults[:postgres_port]} || true
217
- fi
218
- else
219
- echo "๐Ÿ’ก Nothing to rollback"
220
- fi
221
-
222
- echo "โœ… Rollback completed"
223
- EOH
224
- say output
225
- end
226
-
227
- def upload_file(ssh, content, remote_path)
228
- escaped = Shellwords.escape(content)
229
- ssh.exec!("mkdir -p #{File.dirname(remote_path)}")
230
- ssh.exec!("echo #{escaped} > #{remote_path}")
231
- end
232
-
233
- def build_database_url(filled_options, postgres_defaults)
234
- user = postgres_defaults[:postgres_user]
235
- password = postgres_defaults[:postgres_password]
236
- host = filled_options[:server_ip]
237
- port = postgres_defaults[:postgres_port]
238
- db = postgres_defaults[:postgres_db]
239
-
240
- "postgres://#{user}:#{password}@#{host}:#{port}/#{db}"
241
- end
242
- end
243
- end
244
- end
245
- end
246
- end