nvoi 0.2.0 → 0.2.1
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.lock +1 -1
- data/_TODO-rails-example.md +816 -0
- data/_TODO-rails-optimization.md +433 -0
- data/doc/config-schema.yaml +12 -0
- data/examples/apex-wildcard/deploy.yml +1 -0
- data/examples/golang-postgres-multi/deploy.yml +1 -0
- data/examples/postgres-multi/deploy.yml +1 -0
- data/examples/postgres-single/deploy.yml +1 -0
- data/examples/rails-single/deploy.yml +1 -0
- data/lib/nvoi/cli/credentials/edit/command.rb +4 -0
- data/lib/nvoi/cli/deploy/steps/build_image.rb +2 -1
- data/lib/nvoi/cli/deploy/steps/deploy_service.rb +7 -4
- data/lib/nvoi/cli/onboard/steps/compute.rb +61 -14
- data/lib/nvoi/cli.rb +2 -1
- data/lib/nvoi/configuration/builder.rb +6 -3
- data/lib/nvoi/configuration/providers.rb +6 -3
- data/lib/nvoi/configuration/root.rb +18 -0
- data/lib/nvoi/configuration/service.rb +0 -11
- data/lib/nvoi/configuration/ssh_key.rb +16 -0
- data/lib/nvoi/external/cloud/aws.rb +14 -4
- data/lib/nvoi/external/cloud/hetzner.rb +33 -18
- data/lib/nvoi/external/cloud/scaleway.rb +3 -1
- data/lib/nvoi/external/dns/cloudflare.rb +5 -5
- data/lib/nvoi/utils/namer.rb +6 -1
- data/lib/nvoi/version.rb +1 -1
- metadata +5 -3
- data/Makefile +0 -26
|
@@ -40,31 +40,66 @@ module Nvoi
|
|
|
40
40
|
@client = client
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
type_choices = types.sort_by { |t| t[:name] }.map do |t|
|
|
48
|
-
price = t[:price] ? " - #{t[:price]}/mo" : ""
|
|
49
|
-
{ name: "#{t[:name]} (#{t[:cores]} vCPU, #{t[:memory] / 1024}GB#{price})", value: t[:name] }
|
|
43
|
+
locations = with_spinner("Fetching datacenters...") do
|
|
44
|
+
@client.list_locations
|
|
50
45
|
end
|
|
51
46
|
|
|
47
|
+
# Step 1: Pick datacenter first
|
|
52
48
|
location_choices = locations.map do |l|
|
|
53
49
|
{ name: "#{l[:name]} (#{l[:city]}, #{l[:country]})", value: l[:name] }
|
|
54
50
|
end
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
location = @prompt.select("Datacenter:", location_choices)
|
|
53
|
+
|
|
54
|
+
# Step 2: Fetch server types available at selected datacenter
|
|
55
|
+
available_types = with_spinner("Fetching available server types...") do
|
|
56
|
+
@client.list_server_types(location: location)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
type_choices = available_types.sort_by { |t| t[:name] }.map do |t|
|
|
60
|
+
price = price_for_datacenter(t[:prices], location)
|
|
61
|
+
price_str = price ? " - €#{price}/mo" : ""
|
|
62
|
+
memory_gb = t[:memory].to_f.round(1)
|
|
63
|
+
cpu_info = cpu_label(t[:cpu_type], t[:architecture])
|
|
64
|
+
{ name: "#{t[:name]} (#{t[:cores]} vCPU, #{memory_gb}GB, #{cpu_info}#{price_str})", value: t[:name] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
server_type = @prompt.select("Server type:", type_choices, per_page: 10, filter: true)
|
|
68
|
+
|
|
69
|
+
# Get architecture for selected server type
|
|
70
|
+
selected_type = available_types.find { |t| t[:name] == server_type }
|
|
71
|
+
arch = selected_type&.dig(:architecture) || "x86"
|
|
58
72
|
|
|
59
73
|
{
|
|
60
74
|
"hetzner" => {
|
|
61
75
|
"api_token" => token,
|
|
62
76
|
"server_type" => server_type,
|
|
63
|
-
"server_location" => location
|
|
77
|
+
"server_location" => location,
|
|
78
|
+
"architecture" => arch
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
81
|
end
|
|
67
82
|
|
|
83
|
+
def price_for_datacenter(prices, datacenter)
|
|
84
|
+
return nil unless prices
|
|
85
|
+
|
|
86
|
+
# Datacenter name is like "fsn1-dc14", location in prices is "fsn1"
|
|
87
|
+
location = datacenter.split("-").first
|
|
88
|
+
price_entry = prices.find { |p| p["location"] == location }
|
|
89
|
+
return nil unless price_entry
|
|
90
|
+
|
|
91
|
+
gross = price_entry.dig("price_monthly", "gross")
|
|
92
|
+
return nil unless gross
|
|
93
|
+
|
|
94
|
+
gross.to_f.round(2)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def cpu_label(cpu_type, architecture)
|
|
98
|
+
arch = architecture == "arm" ? "ARM" : "x86"
|
|
99
|
+
type = cpu_type == "dedicated" ? "dedicated" : "shared"
|
|
100
|
+
"#{arch}/#{type}"
|
|
101
|
+
end
|
|
102
|
+
|
|
68
103
|
def setup_aws
|
|
69
104
|
access_key = prompt_with_retry("AWS Access Key ID:") do |k|
|
|
70
105
|
raise Errors::ValidationError, "Invalid format" unless k.match?(/\AAKIA/)
|
|
@@ -86,17 +121,23 @@ module Nvoi
|
|
|
86
121
|
|
|
87
122
|
type_choices = types.map do |t|
|
|
88
123
|
mem = t[:memory] ? " #{t[:memory] / 1024}GB" : ""
|
|
89
|
-
|
|
124
|
+
arch_label = t[:architecture] == "arm64" ? " ARM" : ""
|
|
125
|
+
{ name: "#{t[:name]} (#{t[:vcpus]} vCPU#{mem}#{arch_label})", value: t[:name] }
|
|
90
126
|
end
|
|
91
127
|
|
|
92
128
|
instance_type = @prompt.select("Instance type:", type_choices)
|
|
93
129
|
|
|
130
|
+
# Get architecture for selected instance type
|
|
131
|
+
selected_type = types.find { |t| t[:name] == instance_type }
|
|
132
|
+
arch = selected_type&.dig(:architecture) || "x86"
|
|
133
|
+
|
|
94
134
|
{
|
|
95
135
|
"aws" => {
|
|
96
136
|
"access_key_id" => access_key,
|
|
97
137
|
"secret_access_key" => secret_key,
|
|
98
138
|
"region" => region,
|
|
99
|
-
"instance_type" => instance_type
|
|
139
|
+
"instance_type" => instance_type,
|
|
140
|
+
"architecture" => arch
|
|
100
141
|
}
|
|
101
142
|
}
|
|
102
143
|
end
|
|
@@ -118,17 +159,23 @@ module Nvoi
|
|
|
118
159
|
end
|
|
119
160
|
|
|
120
161
|
type_choices = types.map do |t|
|
|
121
|
-
|
|
162
|
+
arch_label = t[:architecture] == "arm64" ? " ARM" : ""
|
|
163
|
+
{ name: "#{t[:name]} (#{t[:cores]} cores#{arch_label})", value: t[:name] }
|
|
122
164
|
end
|
|
123
165
|
|
|
124
166
|
server_type = @prompt.select("Server type:", type_choices, per_page: 10, filter: true)
|
|
125
167
|
|
|
168
|
+
# Get architecture for selected server type
|
|
169
|
+
selected_type = types.find { |t| t[:name] == server_type }
|
|
170
|
+
arch = selected_type&.dig(:architecture) || "x86"
|
|
171
|
+
|
|
126
172
|
{
|
|
127
173
|
"scaleway" => {
|
|
128
174
|
"secret_key" => secret_key,
|
|
129
175
|
"project_id" => project_id,
|
|
130
176
|
"zone" => zone,
|
|
131
|
-
"server_type" => server_type
|
|
177
|
+
"server_type" => server_type,
|
|
178
|
+
"architecture" => arch
|
|
132
179
|
}
|
|
133
180
|
}
|
|
134
181
|
end
|
data/lib/nvoi/cli.rb
CHANGED
|
@@ -114,6 +114,7 @@ module Nvoi
|
|
|
114
114
|
option :api_token, desc: "API token (hetzner)"
|
|
115
115
|
option :server_type, desc: "Server type (cx22, etc)"
|
|
116
116
|
option :server_location, desc: "Location (fsn1, etc)"
|
|
117
|
+
option :architecture, desc: "CPU architecture (x86, arm64)"
|
|
117
118
|
option :access_key_id, desc: "AWS access key ID"
|
|
118
119
|
option :secret_access_key, desc: "AWS secret access key"
|
|
119
120
|
option :region, desc: "AWS region"
|
|
@@ -123,7 +124,7 @@ module Nvoi
|
|
|
123
124
|
option :zone, desc: "Scaleway zone"
|
|
124
125
|
def set(provider)
|
|
125
126
|
Nvoi::Cli::Config::Command.new(options).provider_set(provider, **options.slice(
|
|
126
|
-
:api_token, :server_type, :server_location,
|
|
127
|
+
:api_token, :server_type, :server_location, :architecture,
|
|
127
128
|
:access_key_id, :secret_access_key, :region, :instance_type,
|
|
128
129
|
:secret_key, :project_id, :zone
|
|
129
130
|
).transform_keys(&:to_sym).compact)
|
|
@@ -357,21 +357,24 @@ module Nvoi
|
|
|
357
357
|
{
|
|
358
358
|
"api_token" => opts[:api_token],
|
|
359
359
|
"server_type" => opts[:server_type],
|
|
360
|
-
"server_location" => opts[:server_location]
|
|
360
|
+
"server_location" => opts[:server_location],
|
|
361
|
+
"architecture" => opts[:architecture]
|
|
361
362
|
}.compact
|
|
362
363
|
when "aws"
|
|
363
364
|
{
|
|
364
365
|
"access_key_id" => opts[:access_key_id],
|
|
365
366
|
"secret_access_key" => opts[:secret_access_key],
|
|
366
367
|
"region" => opts[:region],
|
|
367
|
-
"instance_type" => opts[:instance_type]
|
|
368
|
+
"instance_type" => opts[:instance_type],
|
|
369
|
+
"architecture" => opts[:architecture]
|
|
368
370
|
}.compact
|
|
369
371
|
when "scaleway"
|
|
370
372
|
{
|
|
371
373
|
"secret_key" => opts[:secret_key],
|
|
372
374
|
"project_id" => opts[:project_id],
|
|
373
375
|
"zone" => opts[:zone],
|
|
374
|
-
"server_type" => opts[:server_type]
|
|
376
|
+
"server_type" => opts[:server_type],
|
|
377
|
+
"architecture" => opts[:architecture]
|
|
375
378
|
}.compact
|
|
376
379
|
end
|
|
377
380
|
end
|
|
@@ -38,19 +38,20 @@ module Nvoi
|
|
|
38
38
|
|
|
39
39
|
# Hetzner contains Hetzner-specific configuration
|
|
40
40
|
class Hetzner
|
|
41
|
-
attr_accessor :api_token, :server_type, :server_location
|
|
41
|
+
attr_accessor :api_token, :server_type, :server_location, :architecture
|
|
42
42
|
|
|
43
43
|
def initialize(data = nil)
|
|
44
44
|
data ||= {}
|
|
45
45
|
@api_token = data["api_token"]
|
|
46
46
|
@server_type = data["server_type"]
|
|
47
47
|
@server_location = data["server_location"]
|
|
48
|
+
@architecture = data["architecture"]
|
|
48
49
|
end
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
# AwsCfg contains AWS-specific configuration
|
|
52
53
|
class AwsCfg
|
|
53
|
-
attr_accessor :access_key_id, :secret_access_key, :region, :instance_type
|
|
54
|
+
attr_accessor :access_key_id, :secret_access_key, :region, :instance_type, :architecture
|
|
54
55
|
|
|
55
56
|
def initialize(data = nil)
|
|
56
57
|
data ||= {}
|
|
@@ -58,12 +59,13 @@ module Nvoi
|
|
|
58
59
|
@secret_access_key = data["secret_access_key"]
|
|
59
60
|
@region = data["region"]
|
|
60
61
|
@instance_type = data["instance_type"]
|
|
62
|
+
@architecture = data["architecture"]
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
# Scaleway contains Scaleway-specific configuration
|
|
65
67
|
class Scaleway
|
|
66
|
-
attr_accessor :secret_key, :project_id, :zone, :server_type
|
|
68
|
+
attr_accessor :secret_key, :project_id, :zone, :server_type, :architecture
|
|
67
69
|
|
|
68
70
|
def initialize(data = nil)
|
|
69
71
|
data ||= {}
|
|
@@ -71,6 +73,7 @@ module Nvoi
|
|
|
71
73
|
@project_id = data["project_id"]
|
|
72
74
|
@zone = data["zone"] || "fr-par-1"
|
|
73
75
|
@server_type = data["server_type"]
|
|
76
|
+
@architecture = data["architecture"]
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
end
|
|
@@ -53,6 +53,21 @@ module Nvoi
|
|
|
53
53
|
@deploy.application.domain_provider.cloudflare
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
def architecture
|
|
57
|
+
case provider_name
|
|
58
|
+
when "hetzner" then hetzner&.architecture
|
|
59
|
+
when "aws" then aws&.architecture
|
|
60
|
+
when "scaleway" then scaleway&.architecture
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def docker_platform
|
|
65
|
+
case architecture
|
|
66
|
+
when "arm", "arm64" then "linux/arm64"
|
|
67
|
+
else "linux/amd64"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
56
71
|
def keep_count_value
|
|
57
72
|
count = @deploy.application.keep_count
|
|
58
73
|
count && count.positive? ? count : 2
|
|
@@ -144,6 +159,7 @@ module Nvoi
|
|
|
144
159
|
raise Errors::ConfigValidationError, "hetzner api_token is required" if h.api_token.blank?
|
|
145
160
|
raise Errors::ConfigValidationError, "hetzner server_type is required" if h.server_type.blank?
|
|
146
161
|
raise Errors::ConfigValidationError, "hetzner server_location is required" if h.server_location.blank?
|
|
162
|
+
raise Errors::ConfigValidationError, "hetzner architecture is required" if h.architecture.blank?
|
|
147
163
|
end
|
|
148
164
|
|
|
149
165
|
if app.compute_provider.aws
|
|
@@ -153,6 +169,7 @@ module Nvoi
|
|
|
153
169
|
raise Errors::ConfigValidationError, "aws secret_access_key is required" if a.secret_access_key.blank?
|
|
154
170
|
raise Errors::ConfigValidationError, "aws region is required" if a.region.blank?
|
|
155
171
|
raise Errors::ConfigValidationError, "aws instance_type is required" if a.instance_type.blank?
|
|
172
|
+
raise Errors::ConfigValidationError, "aws architecture is required" if a.architecture.blank?
|
|
156
173
|
end
|
|
157
174
|
|
|
158
175
|
if app.compute_provider.scaleway
|
|
@@ -161,6 +178,7 @@ module Nvoi
|
|
|
161
178
|
raise Errors::ConfigValidationError, "scaleway secret_key is required" if s.secret_key.blank?
|
|
162
179
|
raise Errors::ConfigValidationError, "scaleway project_id is required" if s.project_id.blank?
|
|
163
180
|
raise Errors::ConfigValidationError, "scaleway server_type is required" if s.server_type.blank?
|
|
181
|
+
raise Errors::ConfigValidationError, "scaleway architecture is required" if s.architecture.blank?
|
|
164
182
|
end
|
|
165
183
|
|
|
166
184
|
raise Errors::ConfigValidationError, "compute provider required: hetzner, aws, or scaleway must be configured" unless has_provider
|
|
@@ -47,16 +47,5 @@ module Nvoi
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
|
-
|
|
51
|
-
# SshKey defines SSH key content (stored in encrypted config)
|
|
52
|
-
class SshKey
|
|
53
|
-
attr_accessor :private_key, :public_key
|
|
54
|
-
|
|
55
|
-
def initialize(data = nil)
|
|
56
|
-
data ||= {}
|
|
57
|
-
@private_key = data["private_key"]
|
|
58
|
-
@public_key = data["public_key"]
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
50
|
end
|
|
62
51
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nvoi
|
|
4
|
+
module Configuration
|
|
5
|
+
# SshKey defines SSH key content (stored in encrypted config)
|
|
6
|
+
class SshKey
|
|
7
|
+
attr_accessor :private_key, :public_key
|
|
8
|
+
|
|
9
|
+
def initialize(data = nil)
|
|
10
|
+
data ||= {}
|
|
11
|
+
@private_key = data["private_key"]
|
|
12
|
+
@public_key = data["public_key"]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -346,19 +346,29 @@ module Nvoi
|
|
|
346
346
|
|
|
347
347
|
# List available instance types for onboarding
|
|
348
348
|
def list_instance_types
|
|
349
|
-
# Common instance types
|
|
350
|
-
common_types = %w[
|
|
349
|
+
# Common instance types including ARM (Graviton)
|
|
350
|
+
common_types = %w[
|
|
351
|
+
t3.micro t3.small t3.medium t3.large t3.xlarge
|
|
352
|
+
t4g.micro t4g.small t4g.medium t4g.large t4g.xlarge
|
|
353
|
+
m5.large m5.xlarge m6g.large m6g.xlarge
|
|
354
|
+
c5.large c5.xlarge c6g.large c6g.xlarge
|
|
355
|
+
]
|
|
351
356
|
resp = @client.describe_instance_types(instance_types: common_types)
|
|
352
357
|
resp.instance_types.map do |t|
|
|
358
|
+
arch = t.processor_info&.supported_architectures&.first || "x86_64"
|
|
353
359
|
{
|
|
354
360
|
name: t.instance_type,
|
|
355
361
|
vcpus: t.v_cpu_info.default_v_cpus,
|
|
356
|
-
memory: t.memory_info.size_in_mi_b
|
|
362
|
+
memory: t.memory_info.size_in_mi_b,
|
|
363
|
+
architecture: arch.include?("arm") ? "arm64" : "x86"
|
|
357
364
|
}
|
|
358
365
|
end
|
|
359
366
|
rescue StandardError
|
|
360
367
|
# Fallback to static list if API fails
|
|
361
|
-
common_types.map
|
|
368
|
+
common_types.map do |t|
|
|
369
|
+
arch = t.include?("g.") ? "arm64" : "x86" # Graviton types have 'g' suffix
|
|
370
|
+
{ name: t, vcpus: nil, memory: nil, architecture: arch }
|
|
371
|
+
end
|
|
362
372
|
end
|
|
363
373
|
|
|
364
374
|
# List available regions for onboarding
|
|
@@ -109,14 +109,11 @@ module Nvoi
|
|
|
109
109
|
image = find_image(opts.image)
|
|
110
110
|
raise Errors::ValidationError, "invalid image: #{opts.image}" unless image
|
|
111
111
|
|
|
112
|
-
location = find_location(opts.location)
|
|
113
|
-
raise Errors::ValidationError, "invalid location: #{opts.location}" unless location
|
|
114
|
-
|
|
115
112
|
create_opts = {
|
|
116
113
|
name: opts.name,
|
|
117
114
|
server_type: server_type["name"],
|
|
118
115
|
image: image["name"],
|
|
119
|
-
|
|
116
|
+
datacenter: opts.location,
|
|
120
117
|
user_data: opts.user_data,
|
|
121
118
|
start_after_create: true
|
|
122
119
|
}
|
|
@@ -230,8 +227,8 @@ module Nvoi
|
|
|
230
227
|
end
|
|
231
228
|
|
|
232
229
|
def validate_region(region)
|
|
233
|
-
|
|
234
|
-
raise Errors::ValidationError, "invalid hetzner
|
|
230
|
+
datacenter = find_datacenter(region)
|
|
231
|
+
raise Errors::ValidationError, "invalid hetzner datacenter: #{region}" unless datacenter
|
|
235
232
|
|
|
236
233
|
true
|
|
237
234
|
end
|
|
@@ -244,27 +241,45 @@ module Nvoi
|
|
|
244
241
|
end
|
|
245
242
|
|
|
246
243
|
# List available server types for onboarding
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
244
|
+
# When location (datacenter) is provided, filters to only actually available types
|
|
245
|
+
def list_server_types(location: nil)
|
|
246
|
+
all_types = get("/server_types")["server_types"]
|
|
247
|
+
|
|
248
|
+
# If no location specified, return all with basic info
|
|
249
|
+
types_hash = all_types.each_with_object({}) do |t, h|
|
|
250
|
+
h[t["id"]] = {
|
|
251
|
+
id: t["id"],
|
|
250
252
|
name: t["name"],
|
|
251
253
|
description: t["description"],
|
|
252
254
|
cores: t["cores"],
|
|
253
255
|
memory: t["memory"],
|
|
254
256
|
disk: t["disk"],
|
|
255
|
-
|
|
257
|
+
cpu_type: t["cpu_type"],
|
|
258
|
+
architecture: t["architecture"],
|
|
259
|
+
prices: t["prices"]
|
|
256
260
|
}
|
|
257
261
|
end
|
|
262
|
+
|
|
263
|
+
if location
|
|
264
|
+
# Filter by datacenter's actually available server types
|
|
265
|
+
datacenter = get("/datacenters")["datacenters"].find { |d| d["name"] == location }
|
|
266
|
+
return [] unless datacenter
|
|
267
|
+
|
|
268
|
+
available_ids = datacenter.dig("server_types", "available") || []
|
|
269
|
+
types_hash.values_at(*available_ids).compact
|
|
270
|
+
else
|
|
271
|
+
types_hash.values
|
|
272
|
+
end
|
|
258
273
|
end
|
|
259
274
|
|
|
260
|
-
# List available
|
|
275
|
+
# List available datacenters for onboarding (returns datacenter-level granularity)
|
|
261
276
|
def list_locations
|
|
262
|
-
get("/
|
|
277
|
+
get("/datacenters")["datacenters"].map do |d|
|
|
263
278
|
{
|
|
264
|
-
name:
|
|
265
|
-
city:
|
|
266
|
-
country:
|
|
267
|
-
description:
|
|
279
|
+
name: d["name"],
|
|
280
|
+
city: d.dig("location", "city"),
|
|
281
|
+
country: d.dig("location", "country"),
|
|
282
|
+
description: d["description"]
|
|
268
283
|
}
|
|
269
284
|
end
|
|
270
285
|
end
|
|
@@ -331,8 +346,8 @@ module Nvoi
|
|
|
331
346
|
response["images"]&.first
|
|
332
347
|
end
|
|
333
348
|
|
|
334
|
-
def
|
|
335
|
-
get("/
|
|
349
|
+
def find_datacenter(name)
|
|
350
|
+
get("/datacenters")["datacenters"].find { |d| d["name"] == name }
|
|
336
351
|
end
|
|
337
352
|
|
|
338
353
|
def create_network_api(payload)
|
|
@@ -313,11 +313,13 @@ module Nvoi
|
|
|
313
313
|
# List available server types for onboarding
|
|
314
314
|
def list_server_types
|
|
315
315
|
list_server_types_api.map do |name, info|
|
|
316
|
+
arch = info.dig("arch") || "x86_64"
|
|
316
317
|
{
|
|
317
318
|
name:,
|
|
318
319
|
cores: info.dig("ncpus"),
|
|
319
320
|
ram: info.dig("ram"),
|
|
320
|
-
hourly_price: info.dig("hourly_price")
|
|
321
|
+
hourly_price: info.dig("hourly_price"),
|
|
322
|
+
architecture: arch.include?("arm") ? "arm64" : "x86"
|
|
321
323
|
}
|
|
322
324
|
end
|
|
323
325
|
end
|
|
@@ -69,11 +69,11 @@ module Nvoi
|
|
|
69
69
|
url = "accounts/#{@account_id}/cfd_tunnel/#{tunnel_id}/configurations"
|
|
70
70
|
|
|
71
71
|
ingress_rules = hostnames.map do |hostname|
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
rule = { hostname:, service: service_url }
|
|
73
|
+
# Only set httpHostHeader for non-wildcard hostnames
|
|
74
|
+
# Wildcards should pass through the original Host header
|
|
75
|
+
rule[:originRequest] = { httpHostHeader: hostname } unless hostname.start_with?("*.")
|
|
76
|
+
rule
|
|
77
77
|
end
|
|
78
78
|
ingress_rules << { service: "http_status:404" }
|
|
79
79
|
|
data/lib/nvoi/utils/namer.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Nvoi
|
|
|
33
33
|
|
|
34
34
|
# ServerName returns the server name for a given group and index
|
|
35
35
|
def server_name(group, index)
|
|
36
|
-
"#{@config.deploy.application.name}-#{group}-#{index}"
|
|
36
|
+
"#{sanitize_name(@config.deploy.application.name)}-#{group}-#{index}"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def firewall_name
|
|
@@ -196,6 +196,11 @@ module Nvoi
|
|
|
196
196
|
|
|
197
197
|
private
|
|
198
198
|
|
|
199
|
+
# Sanitize name for cloud provider compatibility (no underscores, lowercase, etc.)
|
|
200
|
+
def sanitize_name(name)
|
|
201
|
+
name.to_s.gsub("_", "-").downcase
|
|
202
|
+
end
|
|
203
|
+
|
|
199
204
|
def hash_string(str)
|
|
200
205
|
Digest::SHA256.hexdigest(str)[0, 16]
|
|
201
206
|
end
|
data/lib/nvoi/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nvoi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- NVOI
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
@@ -246,8 +246,9 @@ files:
|
|
|
246
246
|
- ".rubocop.yml"
|
|
247
247
|
- Gemfile
|
|
248
248
|
- Gemfile.lock
|
|
249
|
-
- Makefile
|
|
250
249
|
- Rakefile
|
|
250
|
+
- _TODO-rails-example.md
|
|
251
|
+
- _TODO-rails-optimization.md
|
|
251
252
|
- doc/config-schema.yaml
|
|
252
253
|
- examples/apex-wildcard/deploy.yml
|
|
253
254
|
- examples/golang-postgres-multi/.gitignore
|
|
@@ -412,6 +413,7 @@ files:
|
|
|
412
413
|
- lib/nvoi/configuration/root.rb
|
|
413
414
|
- lib/nvoi/configuration/server.rb
|
|
414
415
|
- lib/nvoi/configuration/service.rb
|
|
416
|
+
- lib/nvoi/configuration/ssh_key.rb
|
|
415
417
|
- lib/nvoi/errors.rb
|
|
416
418
|
- lib/nvoi/external/cloud.rb
|
|
417
419
|
- lib/nvoi/external/cloud/aws.rb
|
data/Makefile
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
NVOI = ruby -I$(PWD)/lib $(PWD)/exe/nvoi
|
|
2
|
-
EXAMPLES = $(PWD)/examples
|
|
3
|
-
|
|
4
|
-
deploy-golang:
|
|
5
|
-
cd $(EXAMPLES)/golang && $(NVOI) deploy
|
|
6
|
-
|
|
7
|
-
exec-golang:
|
|
8
|
-
cd $(EXAMPLES)/golang && $(NVOI) exec -i
|
|
9
|
-
|
|
10
|
-
show-golang:
|
|
11
|
-
cd $(EXAMPLES)/golang && $(NVOI) credentials show
|
|
12
|
-
|
|
13
|
-
delete-golang:
|
|
14
|
-
cd $(EXAMPLES)/golang && $(NVOI) delete
|
|
15
|
-
|
|
16
|
-
deploy-rails:
|
|
17
|
-
cd $(EXAMPLES)/rails-single && $(NVOI) deploy
|
|
18
|
-
|
|
19
|
-
delete-rails:
|
|
20
|
-
cd $(EXAMPLES)/rails-single && $(NVOI) delete
|
|
21
|
-
|
|
22
|
-
test:
|
|
23
|
-
bundle exec rake test
|
|
24
|
-
|
|
25
|
-
lint:
|
|
26
|
-
bundle exec rubocop
|