nvoi 0.1.6 → 0.1.8

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/todo/refactor/12-cli-db-command.md +128 -0
  3. data/.claude/todo/refactor-execution/00-entrypoint.md +49 -0
  4. data/.claude/todo/refactor-execution/01-objects.md +42 -0
  5. data/.claude/todo/refactor-execution/02-utils.md +41 -0
  6. data/.claude/todo/refactor-execution/03-external-cloud.md +38 -0
  7. data/.claude/todo/refactor-execution/04-external-dns.md +35 -0
  8. data/.claude/todo/refactor-execution/05-external-other.md +46 -0
  9. data/.claude/todo/refactor-execution/06-cli-deploy.md +45 -0
  10. data/.claude/todo/refactor-execution/07-cli-delete.md +43 -0
  11. data/.claude/todo/refactor-execution/08-cli-exec.md +30 -0
  12. data/.claude/todo/refactor-execution/09-cli-credentials.md +34 -0
  13. data/.claude/todo/refactor-execution/10-cli-db.md +31 -0
  14. data/.claude/todo/refactor-execution/11-cli-router.md +44 -0
  15. data/.claude/todo/refactor-execution/12-cleanup.md +120 -0
  16. data/.claude/todo/refactor-execution/_monitoring-strategy.md +126 -0
  17. data/.claude/todos/buckets.md +41 -0
  18. data/.claude/todos.md +550 -0
  19. data/Gemfile +5 -0
  20. data/Gemfile.lock +35 -4
  21. data/Rakefile +1 -1
  22. data/ingest +0 -0
  23. data/lib/nvoi/cli/config/command.rb +219 -0
  24. data/lib/nvoi/cli/delete/steps/teardown_dns.rb +12 -11
  25. data/lib/nvoi/cli/deploy/command.rb +27 -11
  26. data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
  27. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +15 -13
  28. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +8 -15
  29. data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +10 -1
  30. data/lib/nvoi/cli/logs/command.rb +66 -0
  31. data/lib/nvoi/cli/onboard/command.rb +761 -0
  32. data/lib/nvoi/cli/unlock/command.rb +72 -0
  33. data/lib/nvoi/cli.rb +257 -0
  34. data/lib/nvoi/config_api/actions/app.rb +30 -30
  35. data/lib/nvoi/config_api/actions/compute_provider.rb +31 -31
  36. data/lib/nvoi/config_api/actions/database.rb +42 -42
  37. data/lib/nvoi/config_api/actions/domain_provider.rb +40 -0
  38. data/lib/nvoi/config_api/actions/env.rb +12 -12
  39. data/lib/nvoi/config_api/actions/init.rb +67 -0
  40. data/lib/nvoi/config_api/actions/secret.rb +12 -12
  41. data/lib/nvoi/config_api/actions/server.rb +35 -35
  42. data/lib/nvoi/config_api/actions/service.rb +52 -0
  43. data/lib/nvoi/config_api/actions/volume.rb +18 -18
  44. data/lib/nvoi/config_api/base.rb +15 -21
  45. data/lib/nvoi/config_api/result.rb +5 -5
  46. data/lib/nvoi/config_api.rb +51 -28
  47. data/lib/nvoi/external/cloud/aws.rb +26 -1
  48. data/lib/nvoi/external/cloud/hetzner.rb +27 -1
  49. data/lib/nvoi/external/cloud/scaleway.rb +32 -6
  50. data/lib/nvoi/external/containerd.rb +1 -44
  51. data/lib/nvoi/external/dns/cloudflare.rb +34 -16
  52. data/lib/nvoi/external/ssh.rb +0 -12
  53. data/lib/nvoi/external/ssh_tunnel.rb +100 -0
  54. data/lib/nvoi/objects/configuration.rb +20 -0
  55. data/lib/nvoi/utils/namer.rb +9 -0
  56. data/lib/nvoi/utils/retry.rb +1 -1
  57. data/lib/nvoi/version.rb +1 -1
  58. data/lib/nvoi.rb +16 -0
  59. data/templates/app-ingress.yaml.erb +3 -1
  60. metadata +27 -1
@@ -127,7 +127,7 @@ module Nvoi
127
127
 
128
128
  def create_server(opts)
129
129
  # Validate server type
130
- server_types = list_server_types
130
+ server_types = list_server_types_api
131
131
  unless server_types.key?(opts.type)
132
132
  raise Errors::ValidationError, "invalid server type: #{opts.type}"
133
133
  end
@@ -170,7 +170,7 @@ module Nvoi
170
170
  end
171
171
 
172
172
  def wait_for_server(server_id, max_attempts)
173
- server = Utils::Retry.poll(max_attempts: max_attempts, interval: Utils::Constants::SERVER_READY_INTERVAL) do
173
+ server = Utils::Retry.poll(max_attempts:, interval: Utils::Constants::SERVER_READY_INTERVAL) do
174
174
  s = get_server_api(server_id)
175
175
  to_server(s) if s["state"] == "running" && s.dig("public_ip", "address")
176
176
  end
@@ -281,7 +281,7 @@ module Nvoi
281
281
  # Validation operations
282
282
 
283
283
  def validate_instance_type(instance_type)
284
- server_types = list_server_types
284
+ server_types = list_server_types_api
285
285
  unless server_types.key?(instance_type)
286
286
  raise Errors::ValidationError, "invalid scaleway server type: #{instance_type}"
287
287
  end
@@ -298,7 +298,7 @@ module Nvoi
298
298
  end
299
299
 
300
300
  def validate_credentials
301
- list_server_types
301
+ list_server_types_api
302
302
  true
303
303
  rescue Errors::AuthenticationError => e
304
304
  raise Errors::ValidationError, "scaleway credentials invalid: #{e.message}"
@@ -310,6 +310,32 @@ module Nvoi
310
310
  server&.public_ipv4
311
311
  end
312
312
 
313
+ # List available server types for onboarding
314
+ def list_server_types
315
+ list_server_types_api.map do |name, info|
316
+ {
317
+ name:,
318
+ cores: info.dig("ncpus"),
319
+ ram: info.dig("ram"),
320
+ hourly_price: info.dig("hourly_price")
321
+ }
322
+ end
323
+ end
324
+
325
+ # List available zones for onboarding
326
+ def list_zones
327
+ VALID_ZONES.map do |z|
328
+ parts = z.split("-")
329
+ city = case parts[0..1].join("-")
330
+ when "fr-par" then "Paris"
331
+ when "nl-ams" then "Amsterdam"
332
+ when "pl-waw" then "Warsaw"
333
+ else parts[0..1].join("-")
334
+ end
335
+ { name: z, city: }
336
+ end
337
+ end
338
+
313
339
  private
314
340
 
315
341
  def zone_to_region(zone)
@@ -402,7 +428,7 @@ module Nvoi
402
428
  post(instance_url("/servers/#{id}/action"), { action: })
403
429
  end
404
430
 
405
- def list_server_types
431
+ def list_server_types_api
406
432
  get(instance_url("/products/servers"))["servers"] || {}
407
433
  end
408
434
 
@@ -450,7 +476,7 @@ module Nvoi
450
476
  end
451
477
 
452
478
  def wait_for_server_state(server_id, target_state, max_attempts)
453
- Utils::Retry.poll(max_attempts: max_attempts, interval: 2) do
479
+ Utils::Retry.poll(max_attempts:, interval: 2) do
454
480
  server = get_server_api(server_id)
455
481
  server if server["state"] == target_state
456
482
  end
@@ -3,6 +3,7 @@
3
3
  module Nvoi
4
4
  module External
5
5
  # Containerd manages container operations on remote servers via containerd/ctr
6
+ # Used for image listing and cleanup on the remote server
6
7
  class Containerd
7
8
  attr_reader :ssh
8
9
 
@@ -10,50 +11,6 @@ module Nvoi
10
11
  @ssh = ssh
11
12
  end
12
13
 
13
- # Build image locally, save to tar, rsync to remote, load with containerd
14
- def build_and_deploy_image(path, tag, cache_from: nil)
15
- cache_args = cache_from ? "--cache-from #{cache_from}" : ""
16
- local_build_cmd = "cd #{path} && DOCKER_BUILDKIT=1 docker build --platform linux/amd64 #{cache_args} --build-arg BUILDKIT_INLINE_CACHE=1 -t #{tag} ."
17
-
18
- unless system("bash", "-c", local_build_cmd)
19
- raise Errors::SshError, "local build failed"
20
- end
21
-
22
- tar_file = "/tmp/#{tag.tr(':', '_')}.tar"
23
- unless system("docker", "save", tag, "-o", tar_file)
24
- raise Errors::SshError, "docker save failed"
25
- end
26
-
27
- begin
28
- remote_tar_path = "/tmp/#{tag.tr(':', '_')}.tar"
29
- rsync_cmd = [
30
- "rsync", "-avz",
31
- "-e", "ssh -i #{@ssh.ssh_key} -o StrictHostKeyChecking=no",
32
- tar_file,
33
- "#{@ssh.user}@#{@ssh.ip}:#{remote_tar_path}"
34
- ]
35
-
36
- unless system(*rsync_cmd)
37
- raise Errors::SshError, "rsync failed"
38
- end
39
-
40
- @ssh.execute("sudo ctr -n k8s.io images import #{remote_tar_path}")
41
-
42
- full_image_ref = "docker.io/library/#{tag}"
43
-
44
- begin
45
- @ssh.execute("sudo ctr -n k8s.io images tag #{full_image_ref} #{tag}")
46
- rescue Errors::SshCommandError => e
47
- list_output = @ssh.execute("sudo ctr -n k8s.io images ls") rescue ""
48
- raise Errors::SshError, "failed to tag imported image: #{e.message}\nAvailable images:\n#{list_output}"
49
- end
50
-
51
- @ssh.execute_ignore_errors("rm #{remote_tar_path}")
52
- ensure
53
- File.delete(tar_file) if File.exist?(tar_file)
54
- end
55
- end
56
-
57
14
  def list_images(filter)
58
15
  output = @ssh.execute("sudo ctr -n k8s.io images ls -q | grep '#{filter}' | sort -r")
59
16
  return [] if output.empty?
@@ -64,24 +64,25 @@ module Nvoi
64
64
  response["result"]
65
65
  end
66
66
 
67
- def update_tunnel_configuration(tunnel_id, hostname, service_url)
67
+ def update_tunnel_configuration(tunnel_id, hostnames, service_url)
68
+ hostnames = Array(hostnames)
68
69
  url = "accounts/#{@account_id}/cfd_tunnel/#{tunnel_id}/configurations"
69
70
 
70
- config = {
71
- ingress: [
72
- {
73
- hostname:,
74
- service: service_url,
75
- originRequest: { httpHostHeader: hostname }
76
- },
77
- { service: "http_status:404" }
78
- ]
79
- }
71
+ ingress_rules = hostnames.map do |hostname|
72
+ {
73
+ hostname:,
74
+ service: service_url,
75
+ originRequest: { httpHostHeader: hostname.sub(/^\*\./, "") } # Use apex for wildcard
76
+ }
77
+ end
78
+ ingress_rules << { service: "http_status:404" }
80
79
 
80
+ config = { ingress: ingress_rules }
81
81
  put(url, { config: })
82
82
  end
83
83
 
84
- def verify_tunnel_configuration(tunnel_id, expected_hostname, expected_service, max_attempts)
84
+ def verify_tunnel_configuration(tunnel_id, expected_hostnames, expected_service, max_attempts)
85
+ expected_hostnames = Array(expected_hostnames)
85
86
  url = "accounts/#{@account_id}/cfd_tunnel/#{tunnel_id}/configurations"
86
87
 
87
88
  max_attempts.times do
@@ -90,10 +91,11 @@ module Nvoi
90
91
 
91
92
  if response["success"]
92
93
  config = response.dig("result", "config")
93
- config&.dig("ingress")&.each do |rule|
94
- if rule["hostname"] == expected_hostname && rule["service"] == expected_service
95
- return true
96
- end
94
+ ingress = config&.dig("ingress") || []
95
+ configured_hostnames = ingress.map { |r| r["hostname"] }.compact
96
+
97
+ if expected_hostnames.all? { |h| configured_hostnames.include?(h) }
98
+ return true
97
99
  end
98
100
  end
99
101
  rescue StandardError
@@ -122,6 +124,16 @@ module Nvoi
122
124
 
123
125
  # DNS operations
124
126
 
127
+ def list_zones
128
+ url = "zones"
129
+ response = get(url)
130
+
131
+ results = response["result"] || []
132
+ results.map do |z|
133
+ { id: z["id"], name: z["name"], status: z["status"] }
134
+ end
135
+ end
136
+
125
137
  def find_zone(domain)
126
138
  url = "zones"
127
139
  response = get(url)
@@ -135,6 +147,12 @@ module Nvoi
135
147
  Objects::Dns::Zone.new(id: zone_data["id"], name: zone_data["name"])
136
148
  end
137
149
 
150
+ def subdomain_available?(zone_id, subdomain, domain)
151
+ fqdn = subdomain.empty? ? domain : "#{subdomain}.#{domain}"
152
+ # Check for CNAME or A record
153
+ !find_dns_record(zone_id, fqdn, "CNAME") && !find_dns_record(zone_id, fqdn, "A")
154
+ end
155
+
138
156
  def find_dns_record(zone_id, name, record_type)
139
157
  url = "zones/#{zone_id}/dns_records"
140
158
  response = get(url)
@@ -62,18 +62,6 @@ module Nvoi
62
62
  raise Errors::SshCommandError, "SCP download failed: #{output}" unless status.success?
63
63
  end
64
64
 
65
- def rsync(local_path, remote_path)
66
- rsync_args = [
67
- "-avz",
68
- "-e", "ssh #{build_ssh_args.join(' ')}",
69
- local_path,
70
- "#{@user}@#{@ip}:#{remote_path}"
71
- ]
72
-
73
- output, status = Open3.capture2e("rsync", *rsync_args)
74
- raise Errors::SshCommandError, "rsync failed: #{output}" unless status.success?
75
- end
76
-
77
65
  private
78
66
 
79
67
  def build_ssh_args
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/ssh"
4
+
5
+ module Nvoi
6
+ module External
7
+ # SshTunnel manages SSH port forwarding using net-ssh
8
+ class SshTunnel
9
+ attr_reader :local_port, :remote_port
10
+
11
+ def initialize(ip:, user:, key_path:, local_port:, remote_port:)
12
+ @ip = ip
13
+ @user = user
14
+ @key_path = key_path
15
+ @local_port = local_port
16
+ @remote_port = remote_port
17
+ @session = nil
18
+ @thread = nil
19
+ @running = false
20
+ end
21
+
22
+ def start
23
+ Nvoi.logger.info "Starting SSH tunnel: localhost:%d -> %s:%d", @local_port, @ip, @remote_port
24
+
25
+ @session = Net::SSH.start(
26
+ @ip,
27
+ @user,
28
+ keys: [@key_path],
29
+ non_interactive: true,
30
+ verify_host_key: :never
31
+ )
32
+
33
+ @session.forward.local(@local_port, "localhost", @remote_port)
34
+ @running = true
35
+
36
+ @thread = Thread.new do
37
+ Thread.current.report_on_exception = false
38
+ @session.loop { @running }
39
+ rescue IOError, Net::SSH::Disconnect, Errno::EBADF
40
+ # Session closed during shutdown, exit gracefully
41
+ end
42
+
43
+ # Wait for tunnel to establish
44
+ sleep 0.3
45
+ verify_tunnel!
46
+
47
+ Nvoi.logger.success "SSH tunnel established"
48
+ end
49
+
50
+ def stop
51
+ return unless @running
52
+
53
+ Nvoi.logger.info "Stopping SSH tunnel"
54
+ @running = false
55
+
56
+ # Give the event loop a moment to see @running = false
57
+ sleep 0.1
58
+
59
+ begin
60
+ @session&.forward&.cancel_local(@local_port)
61
+ rescue StandardError
62
+ # Ignore errors during cleanup
63
+ end
64
+
65
+ begin
66
+ @session&.close
67
+ rescue StandardError
68
+ # Ignore errors during cleanup
69
+ end
70
+
71
+ # Wait for thread to exit gracefully
72
+ @thread&.join(1)
73
+
74
+ @session = nil
75
+ @thread = nil
76
+
77
+ Nvoi.logger.success "SSH tunnel closed"
78
+ end
79
+
80
+ def alive?
81
+ @running && @thread&.alive? && @session && !@session.closed?
82
+ end
83
+
84
+ private
85
+
86
+ def verify_tunnel!
87
+ unless alive?
88
+ raise Errors::SshError, "SSH tunnel failed to start"
89
+ end
90
+
91
+ # Verify the port is actually listening
92
+ require "socket"
93
+ socket = TCPSocket.new("localhost", @local_port)
94
+ socket.close
95
+ rescue Errno::ECONNREFUSED
96
+ raise Errors::SshError, "SSH tunnel started but port #{@local_port} not accessible"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -28,6 +28,7 @@ module Nvoi
28
28
  validate_database_secrets(app.database) if app.database
29
29
  inject_database_env_vars
30
30
  validate_service_server_bindings
31
+ validate_domain_uniqueness
31
32
  end
32
33
 
33
34
  def provider_name
@@ -211,6 +212,25 @@ module Nvoi
211
212
  )
212
213
  end
213
214
  end
215
+
216
+ def validate_domain_uniqueness
217
+ app = @deploy.application
218
+ return unless app.app
219
+
220
+ seen = {}
221
+ app.app.each do |name, cfg|
222
+ next unless cfg.domain && !cfg.domain.empty?
223
+
224
+ hostnames = Utils::Namer.build_hostnames(cfg.subdomain, cfg.domain)
225
+ hostnames.each do |hostname|
226
+ if seen[hostname]
227
+ raise Errors::ConfigValidationError,
228
+ "domain '#{hostname}' used by both '#{seen[hostname]}' and '#{name}'"
229
+ end
230
+ seen[hostname] = name
231
+ end
232
+ end
233
+ end
214
234
  end
215
235
 
216
236
  # Deploy represents the root deployment configuration
@@ -185,6 +185,15 @@ module Nvoi
185
185
  end
186
186
  end
187
187
 
188
+ # Returns array of hostnames - apex returns [domain, *.domain], subdomain returns [sub.domain]
189
+ def self.build_hostnames(subdomain, domain)
190
+ if subdomain.nil? || subdomain.empty? || subdomain == "@"
191
+ [domain, "*.#{domain}"]
192
+ else
193
+ ["#{subdomain}.#{domain}"]
194
+ end
195
+ end
196
+
188
197
  private
189
198
 
190
199
  def hash_string(str)
@@ -77,7 +77,7 @@ module Nvoi
77
77
 
78
78
  # Poll with error on timeout
79
79
  def self.poll!(max_attempts: 30, interval: 2, error_message: "operation timed out")
80
- result = poll(max_attempts: max_attempts, interval: interval) { yield }
80
+ result = poll(max_attempts:, interval:) { yield }
81
81
  raise Errors::TimeoutError, error_message unless result
82
82
 
83
83
  result
data/lib/nvoi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nvoi
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  end
data/lib/nvoi.rb CHANGED
@@ -16,8 +16,24 @@ require "faraday"
16
16
 
17
17
  loader = Zeitwerk::Loader.for_gem
18
18
  loader.ignore("#{__dir__}/nvoi/cli") # CLI commands are lazy-loaded
19
+ loader.ignore("#{__dir__}/nvoi/config_api") # ConfigApi uses non-standard naming
19
20
  loader.setup
20
21
 
22
+ # Load ConfigApi manually (uses non-standard naming convention)
23
+ require_relative "nvoi/config_api/result"
24
+ require_relative "nvoi/config_api/base"
25
+ require_relative "nvoi/config_api/actions/init"
26
+ require_relative "nvoi/config_api/actions/domain_provider"
27
+ require_relative "nvoi/config_api/actions/compute_provider"
28
+ require_relative "nvoi/config_api/actions/server"
29
+ require_relative "nvoi/config_api/actions/volume"
30
+ require_relative "nvoi/config_api/actions/app"
31
+ require_relative "nvoi/config_api/actions/database"
32
+ require_relative "nvoi/config_api/actions/secret"
33
+ require_relative "nvoi/config_api/actions/env"
34
+ require_relative "nvoi/config_api/actions/service"
35
+ require_relative "nvoi/config_api"
36
+
21
37
  module Nvoi
22
38
  class << self
23
39
  attr_accessor :logger
@@ -8,7 +8,8 @@ metadata:
8
8
  spec:
9
9
  ingressClassName: nginx
10
10
  rules:
11
- - host: <%= domain %>
11
+ <% domains.each do |domain| -%>
12
+ - host: "<%= domain %>"
12
13
  http:
13
14
  paths:
14
15
  - path: /
@@ -18,3 +19,4 @@ spec:
18
19
  name: <%= name %>
19
20
  port:
20
21
  number: <%= port %>
22
+ <% end -%>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nvoi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - NVOI
@@ -173,6 +173,20 @@ executables:
173
173
  extensions: []
174
174
  extra_rdoc_files: []
175
175
  files:
176
+ - ".claude/todo/refactor-execution/00-entrypoint.md"
177
+ - ".claude/todo/refactor-execution/01-objects.md"
178
+ - ".claude/todo/refactor-execution/02-utils.md"
179
+ - ".claude/todo/refactor-execution/03-external-cloud.md"
180
+ - ".claude/todo/refactor-execution/04-external-dns.md"
181
+ - ".claude/todo/refactor-execution/05-external-other.md"
182
+ - ".claude/todo/refactor-execution/06-cli-deploy.md"
183
+ - ".claude/todo/refactor-execution/07-cli-delete.md"
184
+ - ".claude/todo/refactor-execution/08-cli-exec.md"
185
+ - ".claude/todo/refactor-execution/09-cli-credentials.md"
186
+ - ".claude/todo/refactor-execution/10-cli-db.md"
187
+ - ".claude/todo/refactor-execution/11-cli-router.md"
188
+ - ".claude/todo/refactor-execution/12-cleanup.md"
189
+ - ".claude/todo/refactor-execution/_monitoring-strategy.md"
176
190
  - ".claude/todo/refactor/00-overview.md"
177
191
  - ".claude/todo/refactor/01-objects.md"
178
192
  - ".claude/todo/refactor/02-utils.md"
@@ -185,9 +199,12 @@ files:
185
199
  - ".claude/todo/refactor/09-cli-delete-command.md"
186
200
  - ".claude/todo/refactor/10-cli-exec-command.md"
187
201
  - ".claude/todo/refactor/11-cli-credentials-command.md"
202
+ - ".claude/todo/refactor/12-cli-db-command.md"
188
203
  - ".claude/todo/refactor/_target.md"
189
204
  - ".claude/todo/scaleway.impl.md"
190
205
  - ".claude/todo/scaleway.reference.md"
206
+ - ".claude/todos.md"
207
+ - ".claude/todos/buckets.md"
191
208
  - ".rubocop.yml"
192
209
  - Gemfile
193
210
  - Gemfile.lock
@@ -311,8 +328,10 @@ files:
311
328
  - examples/rails-single/vendor/.keep
312
329
  - examples/rails-single/yarn.lock
313
330
  - exe/nvoi
331
+ - ingest
314
332
  - lib/nvoi.rb
315
333
  - lib/nvoi/cli.rb
334
+ - lib/nvoi/cli/config/command.rb
316
335
  - lib/nvoi/cli/credentials/edit/command.rb
317
336
  - lib/nvoi/cli/credentials/show/command.rb
318
337
  - lib/nvoi/cli/db/command.rb
@@ -334,13 +353,19 @@ files:
334
353
  - lib/nvoi/cli/deploy/steps/provision_volume.rb
335
354
  - lib/nvoi/cli/deploy/steps/setup_k3s.rb
336
355
  - lib/nvoi/cli/exec/command.rb
356
+ - lib/nvoi/cli/logs/command.rb
357
+ - lib/nvoi/cli/onboard/command.rb
358
+ - lib/nvoi/cli/unlock/command.rb
337
359
  - lib/nvoi/config_api.rb
338
360
  - lib/nvoi/config_api/actions/app.rb
339
361
  - lib/nvoi/config_api/actions/compute_provider.rb
340
362
  - lib/nvoi/config_api/actions/database.rb
363
+ - lib/nvoi/config_api/actions/domain_provider.rb
341
364
  - lib/nvoi/config_api/actions/env.rb
365
+ - lib/nvoi/config_api/actions/init.rb
342
366
  - lib/nvoi/config_api/actions/secret.rb
343
367
  - lib/nvoi/config_api/actions/server.rb
368
+ - lib/nvoi/config_api/actions/service.rb
344
369
  - lib/nvoi/config_api/actions/volume.rb
345
370
  - lib/nvoi/config_api/base.rb
346
371
  - lib/nvoi/config_api/result.rb
@@ -360,6 +385,7 @@ files:
360
385
  - lib/nvoi/external/dns/cloudflare.rb
361
386
  - lib/nvoi/external/kubectl.rb
362
387
  - lib/nvoi/external/ssh.rb
388
+ - lib/nvoi/external/ssh_tunnel.rb
363
389
  - lib/nvoi/objects/config_override.rb
364
390
  - lib/nvoi/objects/configuration.rb
365
391
  - lib/nvoi/objects/database.rb