kitsune-kit 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -2
  3. data/CONTRIBUTING.md +83 -0
  4. data/README.md +108 -136
  5. data/SECURITY.md +25 -0
  6. data/bin/kit +4 -1
  7. data/docs/architecture/decisions/0001-product-boundary.md +23 -0
  8. data/docs/architecture/decisions/0002-core-and-interfaces.md +23 -0
  9. data/docs/architecture/decisions/0003-configuration-state-and-secrets.md +23 -0
  10. data/docs/architecture/decisions/0004-supported-platforms.md +19 -0
  11. data/docs/architecture/decisions/0005-operation-semantics.md +19 -0
  12. data/docs/architecture/decisions/0006-tui-backend.md +21 -0
  13. data/docs/architecture/provider-adapters.md +49 -0
  14. data/docs/architecture.md +89 -0
  15. data/docs/commands.md +210 -0
  16. data/docs/configuration.md +210 -0
  17. data/docs/getting-started.md +133 -0
  18. data/docs/providers/digitalocean.md +52 -0
  19. data/docs/releasing.md +43 -0
  20. data/docs/roadmap.md +46 -0
  21. data/docs/security-audit.md +130 -0
  22. data/docs/security.md +116 -0
  23. data/docs/services/postgres.md +78 -0
  24. data/docs/services/redis.md +53 -0
  25. data/docs/testing.md +200 -0
  26. data/docs/troubleshooting.md +123 -0
  27. data/docs/tui.md +57 -0
  28. data/lib/kitsune/kit/adapters/confirming_host_key_verifier.rb +42 -0
  29. data/lib/kitsune/kit/adapters/digitalocean_provider.rb +217 -0
  30. data/lib/kitsune/kit/adapters/fake_clock.rb +34 -0
  31. data/lib/kitsune/kit/adapters/fake_provider.rb +107 -0
  32. data/lib/kitsune/kit/adapters/fake_reporter.rb +34 -0
  33. data/lib/kitsune/kit/adapters/fake_secret_store.rb +35 -0
  34. data/lib/kitsune/kit/adapters/fake_state_store.rb +91 -0
  35. data/lib/kitsune/kit/adapters/fake_transport.rb +56 -0
  36. data/lib/kitsune/kit/adapters/net_ssh_transport.rb +155 -0
  37. data/lib/kitsune/kit/adapters/provider.rb +34 -0
  38. data/lib/kitsune/kit/adapters/transport.rb +22 -0
  39. data/lib/kitsune/kit/adapters/transport_factory.rb +116 -0
  40. data/lib/kitsune/kit/application.rb +170 -0
  41. data/lib/kitsune/kit/cancellation.rb +21 -0
  42. data/lib/kitsune/kit/cli.rb +802 -64
  43. data/lib/kitsune/kit/clock.rb +11 -0
  44. data/lib/kitsune/kit/configuration.rb +493 -0
  45. data/lib/kitsune/kit/errors.rb +98 -0
  46. data/lib/kitsune/kit/events.rb +55 -0
  47. data/lib/kitsune/kit/operations/ensure_dns_records.rb +165 -0
  48. data/lib/kitsune/kit/operations/ensure_server.rb +135 -0
  49. data/lib/kitsune/kit/operations/ensure_service.rb +285 -0
  50. data/lib/kitsune/kit/operations/remote_script.rb +214 -0
  51. data/lib/kitsune/kit/operations/service_backup.rb +71 -0
  52. data/lib/kitsune/kit/operations/service_files.rb +127 -0
  53. data/lib/kitsune/kit/operations/service_firewall.rb +190 -0
  54. data/lib/kitsune/kit/operations/service_state.rb +59 -0
  55. data/lib/kitsune/kit/plan.rb +72 -0
  56. data/lib/kitsune/kit/reporters/human.rb +89 -0
  57. data/lib/kitsune/kit/reporters/json.rb +65 -0
  58. data/lib/kitsune/kit/reporters/reporter.rb +11 -0
  59. data/lib/kitsune/kit/result.rb +28 -0
  60. data/lib/kitsune/kit/run_journal.rb +102 -0
  61. data/lib/kitsune/kit/run_logger.rb +37 -0
  62. data/lib/kitsune/kit/scripts/docker.sh +137 -0
  63. data/lib/kitsune/kit/scripts/firewall.sh +205 -0
  64. data/lib/kitsune/kit/scripts/metrics.sh +54 -0
  65. data/lib/kitsune/kit/scripts/ssh.sh +95 -0
  66. data/lib/kitsune/kit/scripts/swap.sh +86 -0
  67. data/lib/kitsune/kit/scripts/unattended.sh +81 -0
  68. data/lib/kitsune/kit/scripts/user.sh +114 -0
  69. data/lib/kitsune/kit/secret_filter.rb +54 -0
  70. data/lib/kitsune/kit/secret_store.rb +32 -0
  71. data/lib/kitsune/kit/secret_stores/store.rb +12 -0
  72. data/lib/kitsune/kit/service_compose.rb +72 -0
  73. data/lib/kitsune/kit/state_store.rb +158 -0
  74. data/lib/kitsune/kit/state_stores/store.rb +15 -0
  75. data/lib/kitsune/kit/tui/actions.rb +72 -0
  76. data/lib/kitsune/kit/tui/application.rb +35 -0
  77. data/lib/kitsune/kit/tui/controller.rb +162 -0
  78. data/lib/kitsune/kit/tui/renderer.rb +134 -0
  79. data/lib/kitsune/kit/tui/state.rb +18 -0
  80. data/lib/kitsune/kit/tui/store.rb +88 -0
  81. data/lib/kitsune/kit/tui/terminal.rb +95 -0
  82. data/lib/kitsune/kit/version.rb +1 -1
  83. data/lib/kitsune/kit/workflows/apply_plan.rb +145 -0
  84. data/lib/kitsune/kit/workflows/base.rb +31 -0
  85. data/lib/kitsune/kit/workflows/build_plan.rb +33 -0
  86. data/lib/kitsune/kit/workflows/destroy_server.rb +84 -0
  87. data/lib/kitsune/kit/workflows/doctor.rb +225 -0
  88. data/lib/kitsune/kit/workflows/environment_selection.rb +70 -0
  89. data/lib/kitsune/kit/workflows/import_server.rb +100 -0
  90. data/lib/kitsune/kit/workflows/initialize_project.rb +121 -0
  91. data/lib/kitsune/kit/workflows/inspect_environment.rb +44 -0
  92. data/lib/kitsune/kit/workflows/rollback.rb +54 -0
  93. data/lib/kitsune/kit/workflows/support_bundle.rb +82 -0
  94. data/lib/kitsune/kit.rb +41 -2
  95. metadata +122 -79
  96. data/.rspec +0 -3
  97. data/Rakefile +0 -8
  98. data/kitsune-kit-logo.jpg +0 -0
  99. data/lib/kitsune/blueprints/.env.template +0 -31
  100. data/lib/kitsune/blueprints/docker/postgres.yml +0 -27
  101. data/lib/kitsune/blueprints/docker/redis.yml +0 -23
  102. data/lib/kitsune/blueprints/kit.env.template +0 -1
  103. data/lib/kitsune/kit/ansi_color.rb +0 -78
  104. data/lib/kitsune/kit/commands/bootstrap.rb +0 -134
  105. data/lib/kitsune/kit/commands/bootstrap_docker.rb +0 -66
  106. data/lib/kitsune/kit/commands/dns.rb +0 -112
  107. data/lib/kitsune/kit/commands/init.rb +0 -148
  108. data/lib/kitsune/kit/commands/install_docker_engine.rb +0 -146
  109. data/lib/kitsune/kit/commands/postinstall_docker.rb +0 -142
  110. data/lib/kitsune/kit/commands/provision.rb +0 -43
  111. data/lib/kitsune/kit/commands/setup_do_metrics.rb +0 -123
  112. data/lib/kitsune/kit/commands/setup_docker_prereqs.rb +0 -151
  113. data/lib/kitsune/kit/commands/setup_firewall.rb +0 -132
  114. data/lib/kitsune/kit/commands/setup_postgres_docker.rb +0 -246
  115. data/lib/kitsune/kit/commands/setup_redis_docker.rb +0 -241
  116. data/lib/kitsune/kit/commands/setup_swap.rb +0 -151
  117. data/lib/kitsune/kit/commands/setup_unattended.rb +0 -132
  118. data/lib/kitsune/kit/commands/setup_user.rb +0 -189
  119. data/lib/kitsune/kit/commands/ssh.rb +0 -46
  120. data/lib/kitsune/kit/commands/switch_env.rb +0 -42
  121. data/lib/kitsune/kit/defaults.rb +0 -91
  122. data/lib/kitsune/kit/env_loader.rb +0 -41
  123. data/lib/kitsune/kit/options_builder.rb +0 -26
  124. data/lib/kitsune/kit/provisioner.rb +0 -107
  125. data/sig/kitsune/kit.rbs +0 -6
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "adapters/digitalocean_provider"
4
+ require_relative "adapters/transport_factory"
5
+ require_relative "configuration"
6
+ require_relative "events"
7
+ require_relative "operations/ensure_server"
8
+ require_relative "operations/ensure_dns_records"
9
+ require_relative "operations/ensure_service"
10
+ require_relative "state_store"
11
+
12
+ module Kitsune
13
+ module Kit
14
+ class Application
15
+ attr_reader :config, :event_bus, :provider, :state_store, :transport_factory, :secret_store
16
+
17
+ def self.build(root:, environment:, config_path: nil, overrides: {}, env: ENV, event_bus: Events::Bus.new,
18
+ host_key_confirmation: nil, maximum_timeout: nil)
19
+ config = Configuration::Loader.new(root: root, env: env, config_path: config_path)
20
+ .load(environment: environment, overrides: overrides)
21
+ token = env.fetch(config.provider.token_env, "")
22
+ provider = Adapters::DigitalOceanProvider.new(token: token, maximum_timeout: maximum_timeout)
23
+ state_store = StateStore.new(root: root)
24
+ transport_factory = Adapters::TransportFactory.new(
25
+ config: config,
26
+ provider: provider,
27
+ root: root,
28
+ host_key_confirmation: host_key_confirmation,
29
+ maximum_timeout: maximum_timeout
30
+ )
31
+ new(
32
+ config: config,
33
+ provider: provider,
34
+ state_store: state_store,
35
+ transport_factory: transport_factory,
36
+ secret_store: SecretStores::Environment.new(env: env),
37
+ event_bus: event_bus,
38
+ maximum_timeout: maximum_timeout
39
+ )
40
+ end
41
+
42
+ def initialize(config:, provider:, state_store:, transport_factory: nil, event_bus: Events::Bus.new,
43
+ secret_store: SecretStores::Environment.new, maximum_timeout: nil)
44
+ @config = config
45
+ @provider = provider
46
+ @state_store = state_store
47
+ @transport_factory = transport_factory
48
+ @secret_store = secret_store
49
+ @event_bus = event_bus
50
+ @maximum_timeout = maximum_timeout
51
+ end
52
+
53
+ def operations
54
+ values = [Operations::EnsureServer.new(
55
+ config: config, provider: provider, state_store: state_store, maximum_timeout: @maximum_timeout
56
+ )]
57
+ values.concat(remote_operations) if @transport_factory
58
+ values.concat(service_operations) if @transport_factory
59
+ if config.dns.domains.any?
60
+ values << Operations::EnsureDnsRecords.new(config: config, provider: provider, state_store: state_store)
61
+ end
62
+ values
63
+ end
64
+
65
+ def service_operation(type)
66
+ Operations::EnsureService.new(
67
+ config: config,
68
+ type: type,
69
+ transport_factory: @transport_factory,
70
+ state_store: state_store,
71
+ secret_store: secret_store
72
+ )
73
+ end
74
+
75
+ private
76
+
77
+ def remote_operations
78
+ values = [user_operation, ssh_operation, firewall_operation]
79
+ if config.system.unattended_upgrades
80
+ values << remote(resource: "unattended_upgrades",
81
+ script: script("unattended.sh"))
82
+ end
83
+ values << swap_operation
84
+ values << docker_operation
85
+ if config.system.metrics
86
+ values << remote(resource: "metrics", script: script("metrics.sh"),
87
+ arguments: [config.system.metrics_installer_sha256])
88
+ end
89
+ values
90
+ end
91
+
92
+ def user_operation
93
+ remote(
94
+ resource: "user.#{config.ssh.user}",
95
+ script: script("user.sh"),
96
+ arguments: [config.ssh.user],
97
+ role: :wait_for_bootstrap,
98
+ rollback_role: :root,
99
+ post_verify: method(:verify_deploy_connection)
100
+ )
101
+ end
102
+
103
+ def ssh_operation
104
+ remote(
105
+ resource: "ssh.policy", script: script("ssh.sh"), arguments: [config.ssh.user],
106
+ post_verify: method(:verify_deploy_connection), finalize_action: "finalize", rollback_on_failure: true
107
+ )
108
+ end
109
+
110
+ def firewall_operation
111
+ remote(
112
+ resource: "firewall",
113
+ script: script("firewall.sh"),
114
+ arguments: [config.ssh.port, *config.ssh.allowed_cidrs],
115
+ post_verify: method(:verify_deploy_connection),
116
+ finalize_action: "finalize",
117
+ rollback_on_failure: true
118
+ )
119
+ end
120
+
121
+ def swap_operation
122
+ remote(
123
+ resource: "swap",
124
+ script: script("swap.sh"),
125
+ arguments: [config.system.swap_size_gb, config.system.swap_swappiness]
126
+ )
127
+ end
128
+
129
+ def docker_operation
130
+ remote(resource: "docker", script: script("docker.sh"), arguments: [config.ssh.user])
131
+ end
132
+
133
+ def verify_deploy_connection
134
+ return if @transport_factory.deploy.reachable?
135
+
136
+ raise Errors::VerificationError.new(
137
+ "the deploy user cannot open a second SSH connection",
138
+ hint: "Root access remains enabled; inspect authorized_keys and retry."
139
+ )
140
+ end
141
+
142
+ def script(name) = File.expand_path("scripts/#{name}", __dir__)
143
+
144
+ def remote(resource:, script:, arguments: [], role: :deploy, rollback_role: nil, post_verify: nil,
145
+ finalize_action: nil, rollback_on_failure: false)
146
+ Operations::RemoteScript.new(
147
+ config: config,
148
+ transport_factory: @transport_factory,
149
+ state_store: state_store,
150
+ resource: resource,
151
+ script_path: script,
152
+ arguments: arguments,
153
+ transport_roles: { apply: role, rollback: rollback_role || role },
154
+ verification: {
155
+ callback: post_verify, finalize_action: finalize_action, rollback_on_failure: rollback_on_failure
156
+ }
157
+ )
158
+ end
159
+
160
+ def service_operations
161
+ %w[postgres redis].filter_map do |type|
162
+ service = config.services.public_send(type)
163
+ next unless service.enabled && service.mode == "managed"
164
+
165
+ service_operation(type)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitsune
4
+ module Kit
5
+ class Cancellation
6
+ class Cancelled < StandardError; end
7
+
8
+ def initialize
9
+ @cancelled = false
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def cancel! = @mutex.synchronize { @cancelled = true }
14
+ def cancelled? = @mutex.synchronize { @cancelled }
15
+
16
+ def check!
17
+ raise Cancelled, "operation cancelled" if cancelled?
18
+ end
19
+ end
20
+ end
21
+ end