nvoi 0.1.7 → 0.2.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/Gemfile +1 -5
- data/Gemfile.lock +17 -8
- data/Rakefile +1 -1
- data/lib/nvoi/cli/config/command.rb +46 -41
- data/lib/nvoi/cli/credentials/edit/command.rb +20 -20
- data/lib/nvoi/cli/credentials/show/command.rb +1 -1
- data/lib/nvoi/cli/db/command.rb +10 -10
- data/lib/nvoi/cli/delete/command.rb +2 -2
- data/lib/nvoi/cli/deploy/command.rb +29 -13
- data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
- data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +2 -2
- data/lib/nvoi/cli/deploy/steps/deploy_service.rb +3 -13
- data/lib/nvoi/cli/deploy/steps/provision_server.rb +1 -1
- data/lib/nvoi/cli/deploy/steps/provision_volume.rb +1 -1
- data/lib/nvoi/cli/exec/command.rb +3 -3
- data/lib/nvoi/cli/logs/command.rb +2 -2
- data/lib/nvoi/cli/onboard/command.rb +176 -622
- data/lib/nvoi/cli/onboard/steps/app.rb +108 -0
- data/lib/nvoi/cli/onboard/steps/app_name.rb +26 -0
- data/lib/nvoi/cli/onboard/steps/compute.rb +139 -0
- data/lib/nvoi/cli/onboard/steps/database.rb +97 -0
- data/lib/nvoi/cli/onboard/steps/domain.rb +48 -0
- data/lib/nvoi/cli/onboard/steps/env.rb +67 -0
- data/lib/nvoi/cli/onboard/ui.rb +84 -0
- data/lib/nvoi/cli/unlock/command.rb +2 -2
- data/lib/nvoi/cli.rb +0 -32
- data/lib/nvoi/configuration/app_service.rb +54 -0
- data/lib/nvoi/configuration/application.rb +44 -0
- data/lib/nvoi/configuration/builder.rb +417 -0
- data/lib/nvoi/configuration/database.rb +56 -0
- data/lib/nvoi/configuration/deploy.rb +15 -0
- data/lib/nvoi/{objects/service_spec.rb → configuration/deployment.rb} +4 -3
- data/lib/nvoi/{objects/config_override.rb → configuration/override.rb} +4 -4
- data/lib/nvoi/configuration/providers.rb +78 -0
- data/lib/nvoi/configuration/result.rb +43 -0
- data/lib/nvoi/configuration/root.rb +234 -0
- data/lib/nvoi/configuration/server.rb +39 -0
- data/lib/nvoi/configuration/service.rb +62 -0
- data/lib/nvoi/external/cloud/aws.rb +12 -12
- data/lib/nvoi/external/cloud/hetzner.rb +7 -7
- data/lib/nvoi/external/cloud/scaleway.rb +7 -7
- data/lib/nvoi/external/cloud/types.rb +42 -0
- data/lib/nvoi/external/containerd.rb +1 -48
- data/lib/nvoi/external/database/mysql.rb +1 -1
- data/lib/nvoi/external/database/postgres.rb +1 -1
- data/lib/nvoi/external/database/provider.rb +1 -1
- data/lib/nvoi/external/database/sqlite.rb +1 -1
- data/lib/nvoi/external/database/types.rb +55 -0
- data/lib/nvoi/external/dns/cloudflare.rb +6 -6
- data/lib/nvoi/external/dns/types.rb +24 -0
- data/lib/nvoi/external/ssh.rb +0 -12
- data/lib/nvoi/external/ssh_tunnel.rb +100 -0
- data/lib/nvoi/utils/config_loader.rb +12 -12
- data/lib/nvoi/utils/credential_store.rb +4 -4
- data/lib/nvoi/utils/env_resolver.rb +3 -3
- data/lib/nvoi/utils/namer.rb +2 -2
- data/lib/nvoi/utils/presence.rb +23 -0
- data/lib/nvoi/version.rb +1 -1
- data/lib/nvoi.rb +2 -17
- metadata +96 -57
- data/.claude/todo/refactor/00-overview.md +0 -171
- data/.claude/todo/refactor/01-objects.md +0 -96
- data/.claude/todo/refactor/02-utils.md +0 -143
- data/.claude/todo/refactor/03-external-cloud.md +0 -164
- data/.claude/todo/refactor/04-external-dns.md +0 -104
- data/.claude/todo/refactor/05-external.md +0 -133
- data/.claude/todo/refactor/06-cli.md +0 -123
- data/.claude/todo/refactor/07-cli-deploy-command.md +0 -177
- data/.claude/todo/refactor/08-cli-deploy-steps.md +0 -201
- data/.claude/todo/refactor/09-cli-delete-command.md +0 -169
- data/.claude/todo/refactor/10-cli-exec-command.md +0 -157
- data/.claude/todo/refactor/11-cli-credentials-command.md +0 -190
- data/.claude/todo/refactor/12-cli-db-command.md +0 -128
- data/.claude/todo/refactor/_target.md +0 -79
- data/.claude/todo/refactor-execution/00-entrypoint.md +0 -49
- data/.claude/todo/refactor-execution/01-objects.md +0 -42
- data/.claude/todo/refactor-execution/02-utils.md +0 -41
- data/.claude/todo/refactor-execution/03-external-cloud.md +0 -38
- data/.claude/todo/refactor-execution/04-external-dns.md +0 -35
- data/.claude/todo/refactor-execution/05-external-other.md +0 -46
- data/.claude/todo/refactor-execution/06-cli-deploy.md +0 -45
- data/.claude/todo/refactor-execution/07-cli-delete.md +0 -43
- data/.claude/todo/refactor-execution/08-cli-exec.md +0 -30
- data/.claude/todo/refactor-execution/09-cli-credentials.md +0 -34
- data/.claude/todo/refactor-execution/10-cli-db.md +0 -31
- data/.claude/todo/refactor-execution/11-cli-router.md +0 -44
- data/.claude/todo/refactor-execution/12-cleanup.md +0 -120
- data/.claude/todo/refactor-execution/_monitoring-strategy.md +0 -126
- data/.claude/todo/scaleway.impl.md +0 -644
- data/.claude/todo/scaleway.reference.md +0 -520
- data/.claude/todos.md +0 -550
- data/ingest +0 -0
- data/lib/nvoi/config_api/actions/app.rb +0 -53
- data/lib/nvoi/config_api/actions/compute_provider.rb +0 -55
- data/lib/nvoi/config_api/actions/database.rb +0 -70
- data/lib/nvoi/config_api/actions/domain_provider.rb +0 -40
- data/lib/nvoi/config_api/actions/env.rb +0 -32
- data/lib/nvoi/config_api/actions/init.rb +0 -67
- data/lib/nvoi/config_api/actions/secret.rb +0 -32
- data/lib/nvoi/config_api/actions/server.rb +0 -66
- data/lib/nvoi/config_api/actions/service.rb +0 -52
- data/lib/nvoi/config_api/actions/volume.rb +0 -40
- data/lib/nvoi/config_api/base.rb +0 -38
- data/lib/nvoi/config_api/result.rb +0 -26
- data/lib/nvoi/config_api.rb +0 -93
- data/lib/nvoi/objects/configuration.rb +0 -483
- data/lib/nvoi/objects/database.rb +0 -56
- data/lib/nvoi/objects/dns.rb +0 -14
- data/lib/nvoi/objects/firewall.rb +0 -11
- data/lib/nvoi/objects/network.rb +0 -11
- data/lib/nvoi/objects/server.rb +0 -14
- data/lib/nvoi/objects/tunnel.rb +0 -14
- data/lib/nvoi/objects/volume.rb +0 -17
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nvoi
|
|
4
|
+
module External
|
|
5
|
+
module Database
|
|
6
|
+
module Types
|
|
7
|
+
# Parsed credentials from database URL
|
|
8
|
+
Credentials = Struct.new(:user, :password, :host, :port, :database, :path, :host_path, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
# Options for dumping a database
|
|
11
|
+
DumpOptions = Struct.new(:pod_name, :database, :user, :password, :host_path, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
# Options for restoring a database
|
|
14
|
+
RestoreOptions = Struct.new(:pod_name, :database, :user, :password, :source_db, :host_path, keyword_init: true)
|
|
15
|
+
|
|
16
|
+
# Options for creating a database
|
|
17
|
+
CreateOptions = Struct.new(:pod_name, :database, :user, :password, keyword_init: true)
|
|
18
|
+
|
|
19
|
+
# Branch represents a database branch (snapshot)
|
|
20
|
+
Branch = Struct.new(:id, :created_at, :size, :adapter, :database, keyword_init: true) do
|
|
21
|
+
def to_h
|
|
22
|
+
{ id:, created_at:, size:, adapter:, database: }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# BranchMetadata holds all branches for an app
|
|
27
|
+
class BranchMetadata
|
|
28
|
+
attr_accessor :branches
|
|
29
|
+
|
|
30
|
+
def initialize(branches = [])
|
|
31
|
+
@branches = branches
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_json(*_args)
|
|
35
|
+
JSON.pretty_generate({ branches: @branches.map(&:to_h) })
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.from_json(json_str)
|
|
39
|
+
data = JSON.parse(json_str)
|
|
40
|
+
branches = (data["branches"] || []).map do |b|
|
|
41
|
+
Branch.new(
|
|
42
|
+
id: b["id"],
|
|
43
|
+
created_at: b["created_at"],
|
|
44
|
+
size: b["size"],
|
|
45
|
+
adapter: b["adapter"],
|
|
46
|
+
database: b["database"]
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
new(branches)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -36,7 +36,7 @@ module Nvoi
|
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
result = response["result"]
|
|
39
|
-
|
|
39
|
+
Types::Tunnel::Record.new(
|
|
40
40
|
id: result["id"],
|
|
41
41
|
name: result["name"],
|
|
42
42
|
token: result["token"]
|
|
@@ -51,7 +51,7 @@ module Nvoi
|
|
|
51
51
|
return nil if results.nil? || results.empty?
|
|
52
52
|
|
|
53
53
|
result = results[0]
|
|
54
|
-
|
|
54
|
+
Types::Tunnel::Record.new(
|
|
55
55
|
id: result["id"],
|
|
56
56
|
name: result["name"],
|
|
57
57
|
token: result["token"]
|
|
@@ -144,7 +144,7 @@ module Nvoi
|
|
|
144
144
|
zone_data = results.find { |z| z["name"] == domain }
|
|
145
145
|
return nil unless zone_data
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
Types::Zone.new(id: zone_data["id"], name: zone_data["name"])
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
def subdomain_available?(zone_id, subdomain, domain)
|
|
@@ -163,7 +163,7 @@ module Nvoi
|
|
|
163
163
|
record_data = results.find { |r| r["name"] == name && r["type"] == record_type }
|
|
164
164
|
return nil unless record_data
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
Types::Record.new(
|
|
167
167
|
id: record_data["id"],
|
|
168
168
|
type: record_data["type"],
|
|
169
169
|
name: record_data["name"],
|
|
@@ -185,7 +185,7 @@ module Nvoi
|
|
|
185
185
|
})
|
|
186
186
|
|
|
187
187
|
result = response["result"]
|
|
188
|
-
|
|
188
|
+
Types::Record.new(
|
|
189
189
|
id: result["id"],
|
|
190
190
|
type: result["type"],
|
|
191
191
|
name: result["name"],
|
|
@@ -207,7 +207,7 @@ module Nvoi
|
|
|
207
207
|
})
|
|
208
208
|
|
|
209
209
|
result = response["result"]
|
|
210
|
-
|
|
210
|
+
Types::Record.new(
|
|
211
211
|
id: result["id"],
|
|
212
212
|
type: result["type"],
|
|
213
213
|
name: result["name"],
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nvoi
|
|
4
|
+
module External
|
|
5
|
+
module Dns
|
|
6
|
+
module Types
|
|
7
|
+
# Zone represents a Cloudflare DNS zone
|
|
8
|
+
Zone = Struct.new(:id, :name, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
# Record represents a Cloudflare DNS record
|
|
11
|
+
Record = Struct.new(:id, :type, :name, :content, :proxied, :ttl, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
# Tunnel-related structs (Cloudflare tunnels)
|
|
14
|
+
module Tunnel
|
|
15
|
+
# Record represents a Cloudflare tunnel
|
|
16
|
+
Record = Struct.new(:id, :name, :token, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
# Info holds information about a configured tunnel
|
|
19
|
+
Info = Struct.new(:service_name, :hostname, :tunnel_id, :tunnel_token, :port, keyword_init: true)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/nvoi/external/ssh.rb
CHANGED
|
@@ -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
|
|
@@ -10,8 +10,8 @@ module Nvoi
|
|
|
10
10
|
class << self
|
|
11
11
|
# Load reads and parses the deployment configuration from encrypted file
|
|
12
12
|
def load(config_path, credentials_path: nil, master_key_path: nil)
|
|
13
|
-
working_dir = config_path
|
|
14
|
-
enc_path = credentials_path.
|
|
13
|
+
working_dir = config_path.blank? ? "." : File.dirname(config_path)
|
|
14
|
+
enc_path = credentials_path.blank? ? config_path : credentials_path
|
|
15
15
|
|
|
16
16
|
manager = CredentialStore.new(working_dir, enc_path, master_key_path)
|
|
17
17
|
plaintext = manager.read
|
|
@@ -20,8 +20,8 @@ module Nvoi
|
|
|
20
20
|
data = YAML.safe_load(plaintext, permitted_classes: [Symbol])
|
|
21
21
|
raise Errors::ConfigError, "Invalid config format" unless data.is_a?(Hash)
|
|
22
22
|
|
|
23
|
-
deploy_config =
|
|
24
|
-
cfg =
|
|
23
|
+
deploy_config = Configuration::Deploy.new(data)
|
|
24
|
+
cfg = Configuration::Root.new(deploy_config)
|
|
25
25
|
|
|
26
26
|
load_ssh_keys(cfg)
|
|
27
27
|
cfg.validate_config
|
|
@@ -39,7 +39,7 @@ module Nvoi
|
|
|
39
39
|
|
|
40
40
|
provider = External::Database.provider_for(adapter)
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
unless db_config.url.blank?
|
|
43
43
|
creds = provider.parse_url(db_config.url)
|
|
44
44
|
host_path = nil
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ module Nvoi
|
|
|
47
47
|
host_path = resolve_sqlite_host_path(db_config, namer, creds.database || "app.db")
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
return
|
|
50
|
+
return External::Database::Types::Credentials.new(
|
|
51
51
|
user: creds.user,
|
|
52
52
|
password: creds.password,
|
|
53
53
|
host: creds.host,
|
|
@@ -61,21 +61,21 @@ module Nvoi
|
|
|
61
61
|
# Fall back to secrets-based credentials
|
|
62
62
|
case adapter
|
|
63
63
|
when "postgres", "postgresql"
|
|
64
|
-
|
|
64
|
+
External::Database::Types::Credentials.new(
|
|
65
65
|
port: provider.default_port,
|
|
66
66
|
user: db_config.secrets["POSTGRES_USER"],
|
|
67
67
|
password: db_config.secrets["POSTGRES_PASSWORD"],
|
|
68
68
|
database: db_config.secrets["POSTGRES_DB"]
|
|
69
69
|
)
|
|
70
70
|
when "mysql", "mysql2"
|
|
71
|
-
|
|
71
|
+
External::Database::Types::Credentials.new(
|
|
72
72
|
port: provider.default_port,
|
|
73
73
|
user: db_config.secrets["MYSQL_USER"],
|
|
74
74
|
password: db_config.secrets["MYSQL_PASSWORD"],
|
|
75
75
|
database: db_config.secrets["MYSQL_DATABASE"]
|
|
76
76
|
)
|
|
77
77
|
when "sqlite", "sqlite3"
|
|
78
|
-
|
|
78
|
+
External::Database::Types::Credentials.new(
|
|
79
79
|
database: "app.db",
|
|
80
80
|
host_path: resolve_sqlite_host_path(db_config, namer, "app.db")
|
|
81
81
|
)
|
|
@@ -93,8 +93,8 @@ module Nvoi
|
|
|
93
93
|
raise Errors::ConfigError, "ssh_keys section is required in config. Run 'nvoi credentials edit' to generate keys."
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
raise Errors::ConfigError, "ssh_keys.private_key is required"
|
|
97
|
-
raise Errors::ConfigError, "ssh_keys.public_key is required"
|
|
96
|
+
raise Errors::ConfigError, "ssh_keys.private_key is required" if ssh_keys.private_key.blank?
|
|
97
|
+
raise Errors::ConfigError, "ssh_keys.public_key is required" if ssh_keys.public_key.blank?
|
|
98
98
|
|
|
99
99
|
temp_dir = Dir.mktmpdir("nvoi-ssh-")
|
|
100
100
|
|
|
@@ -136,7 +136,7 @@ module Nvoi
|
|
|
136
136
|
server_name = db_config.servers.first
|
|
137
137
|
mount = db_config.mount
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
unless mount.blank?
|
|
140
140
|
vol_name = mount.keys.first
|
|
141
141
|
base_path = namer.server_volume_host_path(server_name, vol_name)
|
|
142
142
|
return "#{base_path}/#{filename}"
|
|
@@ -17,7 +17,7 @@ module Nvoi
|
|
|
17
17
|
# key_path: explicit path to key file (optional, nil = auto-discover)
|
|
18
18
|
def initialize(working_dir, encrypted_path = nil, key_path = nil)
|
|
19
19
|
@working_dir = working_dir
|
|
20
|
-
@encrypted_path = encrypted_path
|
|
20
|
+
@encrypted_path = encrypted_path.blank? ? find_encrypted_file : encrypted_path
|
|
21
21
|
@key_path = nil
|
|
22
22
|
@master_key = nil
|
|
23
23
|
|
|
@@ -41,7 +41,7 @@ module Nvoi
|
|
|
41
41
|
|
|
42
42
|
# Check if the store has a master key loaded
|
|
43
43
|
def has_key?
|
|
44
|
-
!@master_key.
|
|
44
|
+
!@master_key.blank?
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Decrypt and return the credentials content
|
|
@@ -131,7 +131,7 @@ module Nvoi
|
|
|
131
131
|
|
|
132
132
|
def resolve_key(explicit_key_path)
|
|
133
133
|
# Priority 1: Explicit key file path
|
|
134
|
-
|
|
134
|
+
unless explicit_key_path.blank?
|
|
135
135
|
@master_key = load_key_from_file(explicit_key_path)
|
|
136
136
|
@key_path = explicit_key_path
|
|
137
137
|
return
|
|
@@ -139,7 +139,7 @@ module Nvoi
|
|
|
139
139
|
|
|
140
140
|
# Priority 2: Environment variable
|
|
141
141
|
env_key = ENV[MASTER_KEY_ENV_VAR]
|
|
142
|
-
|
|
142
|
+
unless env_key.blank?
|
|
143
143
|
Crypto.validate_key(env_key)
|
|
144
144
|
@master_key = env_key
|
|
145
145
|
return
|
|
@@ -44,12 +44,12 @@ module Nvoi
|
|
|
44
44
|
db = @config.deploy.application.database
|
|
45
45
|
return unless db
|
|
46
46
|
|
|
47
|
-
env["DATABASE_ADAPTER"] = db.adapter
|
|
47
|
+
env["DATABASE_ADAPTER"] = db.adapter unless db.adapter.blank?
|
|
48
48
|
|
|
49
49
|
# Handle database URL
|
|
50
50
|
if db.adapter == "sqlite3"
|
|
51
51
|
env["DATABASE_URL"] = sqlite_database_url(db)
|
|
52
|
-
elsif
|
|
52
|
+
elsif !db.url.blank?
|
|
53
53
|
env["DATABASE_URL"] = db.url
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -60,7 +60,7 @@ module Nvoi
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def sqlite_database_url(db)
|
|
63
|
-
raise Errors::ConfigError, "sqlite3 requires database.mount to be configured" if db.mount.
|
|
63
|
+
raise Errors::ConfigError, "sqlite3 requires database.mount to be configured" if db.mount.blank?
|
|
64
64
|
|
|
65
65
|
mount_path = db.mount.values.first
|
|
66
66
|
app_name = @config.deploy.application.name
|
data/lib/nvoi/utils/namer.rb
CHANGED
|
@@ -178,7 +178,7 @@ module Nvoi
|
|
|
178
178
|
|
|
179
179
|
# Class method for building hostname without instance
|
|
180
180
|
def self.build_hostname(subdomain, domain)
|
|
181
|
-
if subdomain.
|
|
181
|
+
if subdomain.blank? || subdomain == "@"
|
|
182
182
|
domain
|
|
183
183
|
else
|
|
184
184
|
"#{subdomain}.#{domain}"
|
|
@@ -187,7 +187,7 @@ module Nvoi
|
|
|
187
187
|
|
|
188
188
|
# Returns array of hostnames - apex returns [domain, *.domain], subdomain returns [sub.domain]
|
|
189
189
|
def self.build_hostnames(subdomain, domain)
|
|
190
|
-
if subdomain.
|
|
190
|
+
if subdomain.blank? || subdomain == "@"
|
|
191
191
|
[domain, "*.#{domain}"]
|
|
192
192
|
else
|
|
193
193
|
["#{subdomain}.#{domain}"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Core extensions for blank?/present? checks
|
|
4
|
+
|
|
5
|
+
class NilClass
|
|
6
|
+
def blank? = true
|
|
7
|
+
def present? = false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class String
|
|
11
|
+
def blank? = empty?
|
|
12
|
+
def present? = !empty?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Array
|
|
16
|
+
def blank? = empty?
|
|
17
|
+
def present? = !empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Hash
|
|
21
|
+
def blank? = empty?
|
|
22
|
+
def present? = !empty?
|
|
23
|
+
end
|
data/lib/nvoi/version.rb
CHANGED
data/lib/nvoi.rb
CHANGED
|
@@ -13,26 +13,11 @@ require "fileutils"
|
|
|
13
13
|
require "tempfile"
|
|
14
14
|
require "open3"
|
|
15
15
|
require "faraday"
|
|
16
|
+
require_relative "nvoi/utils/presence"
|
|
16
17
|
|
|
17
18
|
loader = Zeitwerk::Loader.for_gem
|
|
18
|
-
loader.ignore("#{__dir__}/nvoi/cli") # CLI commands are lazy-loaded
|
|
19
|
-
loader.ignore("#{__dir__}/nvoi/config_api") # ConfigApi uses non-standard naming
|
|
20
19
|
loader.setup
|
|
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"
|
|
20
|
+
loader.eager_load_namespace(Nvoi::Cli)
|
|
36
21
|
|
|
37
22
|
module Nvoi
|
|
38
23
|
class << self
|