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
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# NVOI Gem - Rails Optimization TODO
|
|
2
|
+
|
|
3
|
+
Changes needed in the `nvoi` gem to support **optional** dashboard integration.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Modes
|
|
8
|
+
|
|
9
|
+
### Standalone Mode (default)
|
|
10
|
+
|
|
11
|
+
No `callback_url` in config. Gem works exactly as today:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
nvoi deploy
|
|
15
|
+
↓
|
|
16
|
+
logs to stdout
|
|
17
|
+
↓
|
|
18
|
+
done
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Dashboard Mode (optional)
|
|
22
|
+
|
|
23
|
+
When `callback_url` is set in config:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
nvoi deploy
|
|
27
|
+
↓
|
|
28
|
+
logs to stdout AND POSTs to callback_url
|
|
29
|
+
↓
|
|
30
|
+
Rails dashboard receives logs in real-time
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**The gem remains 100% standalone.** Dashboard integration is opt-in via config.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Architecture (Dashboard Mode)
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
CI (GitHub Actions, GitLab CI, etc.)
|
|
41
|
+
↓
|
|
42
|
+
gem install nvoi && nvoi deploy
|
|
43
|
+
↓
|
|
44
|
+
gem decrypts deploy.enc with DEPLOY_KEY
|
|
45
|
+
↓
|
|
46
|
+
gem POSTs logs to callback_url (signed with DEPLOY_KEY)
|
|
47
|
+
↓
|
|
48
|
+
Rails receives, stores, broadcasts via Turbo
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**One key, two purposes (when dashboard enabled):**
|
|
52
|
+
|
|
53
|
+
1. Decrypt `deploy.enc`
|
|
54
|
+
2. HMAC-sign API callbacks to Rails
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 1. Callback Configuration
|
|
59
|
+
|
|
60
|
+
Add optional callback URL to deploy config schema:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# lib/nvoi/configuration/deploy.rb
|
|
64
|
+
class Deploy
|
|
65
|
+
# Existing fields...
|
|
66
|
+
|
|
67
|
+
# New: callback URL for log streaming
|
|
68
|
+
def callback_url
|
|
69
|
+
@data["callback_url"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def callback_enabled?
|
|
73
|
+
callback_url.present?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Config example:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
# In decrypted deploy config
|
|
82
|
+
callback_url: "https://myapp.com/api/deploys"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 2. HTTP Logger Adapter
|
|
88
|
+
|
|
89
|
+
New adapter that POSTs logs to Rails:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# lib/nvoi/adapters/logger/http.rb
|
|
93
|
+
module Nvoi
|
|
94
|
+
module Adapters
|
|
95
|
+
module Logger
|
|
96
|
+
class Http < Base
|
|
97
|
+
def initialize(url:, key:, deploy_id:, fallback: nil)
|
|
98
|
+
@url = url
|
|
99
|
+
@key = key
|
|
100
|
+
@deploy_id = deploy_id
|
|
101
|
+
@fallback = fallback || Stdout.new
|
|
102
|
+
@buffer = []
|
|
103
|
+
@flush_thread = start_flush_thread
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def info(message, *args)
|
|
107
|
+
log(:info, format_message(message, args))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def success(message, *args)
|
|
111
|
+
log(:success, format_message(message, args))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def error(message, *args)
|
|
115
|
+
log(:error, format_message(message, args))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def warning(message, *args)
|
|
119
|
+
log(:warning, format_message(message, args))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def step(message, *args)
|
|
123
|
+
log(:step, format_message(message, args))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def ok(message, *args)
|
|
127
|
+
log(:ok, format_message(message, args))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def flush
|
|
131
|
+
return if @buffer.empty?
|
|
132
|
+
|
|
133
|
+
logs = @buffer.dup
|
|
134
|
+
@buffer.clear
|
|
135
|
+
|
|
136
|
+
send_logs(logs)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def close
|
|
140
|
+
@flush_thread&.kill
|
|
141
|
+
flush
|
|
142
|
+
send_status(:completed)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def fail!(error_message)
|
|
146
|
+
flush
|
|
147
|
+
send_status(:failed, error: error_message)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def log(level, message)
|
|
153
|
+
@fallback&.public_send(level, message)
|
|
154
|
+
|
|
155
|
+
@buffer << {
|
|
156
|
+
level: level,
|
|
157
|
+
message: message,
|
|
158
|
+
logged_at: Time.now.iso8601(3)
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def start_flush_thread
|
|
163
|
+
Thread.new do
|
|
164
|
+
loop do
|
|
165
|
+
sleep 1
|
|
166
|
+
flush
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def send_logs(logs)
|
|
172
|
+
payload = { logs: logs }
|
|
173
|
+
post("#{@url}/#{@deploy_id}/logs", payload)
|
|
174
|
+
rescue => e
|
|
175
|
+
@fallback&.warning("Callback failed: #{e.message}")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def send_status(status, error: nil)
|
|
179
|
+
payload = { status: status, error: error }
|
|
180
|
+
post("#{@url}/#{@deploy_id}/status", payload)
|
|
181
|
+
rescue => e
|
|
182
|
+
@fallback&.warning("Status callback failed: #{e.message}")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def post(url, payload)
|
|
186
|
+
body = payload.to_json
|
|
187
|
+
signature = sign(body)
|
|
188
|
+
|
|
189
|
+
uri = URI(url)
|
|
190
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
191
|
+
http.use_ssl = uri.scheme == "https"
|
|
192
|
+
|
|
193
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
194
|
+
request["Content-Type"] = "application/json"
|
|
195
|
+
request["X-Nvoi-Signature"] = signature
|
|
196
|
+
request["X-Nvoi-Deploy-Id"] = @deploy_id
|
|
197
|
+
request.body = body
|
|
198
|
+
|
|
199
|
+
response = http.request(request)
|
|
200
|
+
|
|
201
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
202
|
+
raise "HTTP #{response.code}: #{response.body}"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def sign(body)
|
|
207
|
+
"sha256=" + OpenSSL::HMAC.hexdigest("SHA256", @key, body)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 3. CLI Integration
|
|
218
|
+
|
|
219
|
+
Update deploy command to use HTTP logger when callback configured:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# lib/nvoi/cli/deploy/command.rb
|
|
223
|
+
def run
|
|
224
|
+
@log = build_logger
|
|
225
|
+
|
|
226
|
+
# ... existing deploy logic ...
|
|
227
|
+
|
|
228
|
+
@log.close # flush remaining logs + send completed status
|
|
229
|
+
rescue => e
|
|
230
|
+
@log.fail!(e.message)
|
|
231
|
+
raise
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
def build_logger
|
|
237
|
+
if @config.callback_enabled?
|
|
238
|
+
deploy_id = ENV["NVOI_DEPLOY_ID"] || ENV["GITHUB_RUN_ID"] || SecureRandom.uuid
|
|
239
|
+
|
|
240
|
+
Adapters::Logger::Http.new(
|
|
241
|
+
url: @config.callback_url,
|
|
242
|
+
key: load_deploy_key,
|
|
243
|
+
deploy_id: deploy_id,
|
|
244
|
+
fallback: Utils::Logger.new # still print to stdout
|
|
245
|
+
)
|
|
246
|
+
else
|
|
247
|
+
Utils::Logger.new
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def load_deploy_key
|
|
252
|
+
key_path = resolve_key_path
|
|
253
|
+
File.read(key_path).strip
|
|
254
|
+
end
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 4. Status Callbacks
|
|
260
|
+
|
|
261
|
+
Send deploy lifecycle events:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# Callback payloads:
|
|
265
|
+
|
|
266
|
+
# POST /api/deploys/:id/logs
|
|
267
|
+
{
|
|
268
|
+
"logs": [
|
|
269
|
+
{ "level": "info", "message": "Starting deploy...", "logged_at": "2024-01-15T10:30:00.123Z" },
|
|
270
|
+
{ "level": "success", "message": "Server provisioned", "logged_at": "2024-01-15T10:30:05.456Z" }
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# POST /api/deploys/:id/status
|
|
275
|
+
{
|
|
276
|
+
"status": "started",
|
|
277
|
+
"git_sha": "abc123",
|
|
278
|
+
"git_ref": "main",
|
|
279
|
+
"ci_provider": "github_actions",
|
|
280
|
+
"ci_run_url": "https://github.com/user/repo/actions/runs/12345"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# POST /api/deploys/:id/status (on complete)
|
|
284
|
+
{
|
|
285
|
+
"status": "completed",
|
|
286
|
+
"tunnels": [
|
|
287
|
+
{ "service_name": "web", "hostname": "www.example.com" }
|
|
288
|
+
],
|
|
289
|
+
"duration_seconds": 120
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# POST /api/deploys/:id/status (on failure)
|
|
293
|
+
{
|
|
294
|
+
"status": "failed",
|
|
295
|
+
"error": "SSH connection failed"
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 5. Delete Command Callbacks
|
|
302
|
+
|
|
303
|
+
Same pattern for delete:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
# lib/nvoi/cli/delete/command.rb
|
|
307
|
+
def run
|
|
308
|
+
@log = build_logger
|
|
309
|
+
send_status(:started)
|
|
310
|
+
|
|
311
|
+
# ... existing delete logic ...
|
|
312
|
+
|
|
313
|
+
send_status(:completed)
|
|
314
|
+
@log.close
|
|
315
|
+
rescue => e
|
|
316
|
+
send_status(:failed, error: e.message)
|
|
317
|
+
@log.close
|
|
318
|
+
raise
|
|
319
|
+
end
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 6. Environment Variables
|
|
325
|
+
|
|
326
|
+
CI provides context:
|
|
327
|
+
|
|
328
|
+
| Variable | Source | Purpose |
|
|
329
|
+
| ------------------- | --------------------------- | ------------------------ |
|
|
330
|
+
| `NVOI_DEPLOY_ID` | Set by CI or auto-generated | Unique deploy identifier |
|
|
331
|
+
| `GITHUB_RUN_ID` | GitHub Actions | Fallback deploy ID |
|
|
332
|
+
| `GITHUB_SHA` | GitHub Actions | Git commit SHA |
|
|
333
|
+
| `GITHUB_REF_NAME` | GitHub Actions | Branch name |
|
|
334
|
+
| `GITHUB_SERVER_URL` | GitHub Actions | Build CI run URL |
|
|
335
|
+
| `GITHUB_REPOSITORY` | GitHub Actions | repo owner/name |
|
|
336
|
+
| `GITHUB_RUN_ID` | GitHub Actions | Run ID for URL |
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 7. File Structure
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
lib/nvoi/
|
|
344
|
+
adapters/
|
|
345
|
+
logger/
|
|
346
|
+
base.rb
|
|
347
|
+
http.rb # NEW
|
|
348
|
+
stdout.rb # renamed from utils/logger.rb
|
|
349
|
+
null.rb
|
|
350
|
+
cli/
|
|
351
|
+
deploy/
|
|
352
|
+
command.rb # MODIFIED - use callback logger
|
|
353
|
+
delete/
|
|
354
|
+
command.rb # MODIFIED - use callback logger
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 8. Onboard Wizard Enhancement
|
|
360
|
+
|
|
361
|
+
Add callback URL step:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
# lib/nvoi/cli/onboard/steps/callback_step.rb
|
|
365
|
+
class CallbackStep
|
|
366
|
+
def run(state)
|
|
367
|
+
use_callback = prompt.yes?("Stream deploy logs to a dashboard?")
|
|
368
|
+
return state unless use_callback
|
|
369
|
+
|
|
370
|
+
url = prompt.ask("Callback URL:", required: true)
|
|
371
|
+
|
|
372
|
+
state.merge(callback_url: url)
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## 9. Testing
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
# test/nvoi/adapters/logger/http_test.rb
|
|
383
|
+
class HttpLoggerTest < Minitest::Test
|
|
384
|
+
def setup
|
|
385
|
+
@url = "https://example.com/api/deploys"
|
|
386
|
+
@key = "test-key-123"
|
|
387
|
+
@deploy_id = "run-456"
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def test_signs_requests_with_hmac
|
|
391
|
+
stub_request(:post, "#{@url}/#{@deploy_id}/logs")
|
|
392
|
+
.with { |req|
|
|
393
|
+
signature = req.headers["X-Nvoi-Signature"]
|
|
394
|
+
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", @key, req.body)
|
|
395
|
+
signature == expected
|
|
396
|
+
}
|
|
397
|
+
.to_return(status: 200)
|
|
398
|
+
|
|
399
|
+
logger = Nvoi::Adapters::Logger::Http.new(
|
|
400
|
+
url: @url, key: @key, deploy_id: @deploy_id
|
|
401
|
+
)
|
|
402
|
+
logger.info("test")
|
|
403
|
+
logger.flush
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def test_buffers_and_flushes
|
|
407
|
+
stub = stub_request(:post, "#{@url}/#{@deploy_id}/logs")
|
|
408
|
+
.to_return(status: 200)
|
|
409
|
+
|
|
410
|
+
logger = Nvoi::Adapters::Logger::Http.new(
|
|
411
|
+
url: @url, key: @key, deploy_id: @deploy_id
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
logger.info("one")
|
|
415
|
+
logger.info("two")
|
|
416
|
+
logger.flush
|
|
417
|
+
|
|
418
|
+
assert_requested(stub, times: 1)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 10. Migration Path
|
|
426
|
+
|
|
427
|
+
1. Add HTTP logger adapter (non-breaking)
|
|
428
|
+
2. Add callback_url config option (non-breaking)
|
|
429
|
+
3. Update CLI to use callback logger when configured (non-breaking)
|
|
430
|
+
4. Update onboard wizard (non-breaking)
|
|
431
|
+
5. Release as 0.3.0
|
|
432
|
+
|
|
433
|
+
No breaking changes for existing CLI users.
|
data/doc/config-schema.yaml
CHANGED
|
@@ -127,6 +127,7 @@ components:
|
|
|
127
127
|
description: Hetzner Cloud provider configuration
|
|
128
128
|
required:
|
|
129
129
|
- api_token
|
|
130
|
+
- architecture
|
|
130
131
|
properties:
|
|
131
132
|
api_token:
|
|
132
133
|
type: string
|
|
@@ -141,6 +142,11 @@ components:
|
|
|
141
142
|
description: Default datacenter location
|
|
142
143
|
enum: [fsn1, nbg1, hel1, ash, hil]
|
|
143
144
|
example: fsn1
|
|
145
|
+
architecture:
|
|
146
|
+
type: string
|
|
147
|
+
description: CPU architecture for Docker builds
|
|
148
|
+
enum: [x86, arm64]
|
|
149
|
+
example: x86
|
|
144
150
|
|
|
145
151
|
AWSConfig:
|
|
146
152
|
type: object
|
|
@@ -149,6 +155,7 @@ components:
|
|
|
149
155
|
- access_key_id
|
|
150
156
|
- secret_access_key
|
|
151
157
|
- region
|
|
158
|
+
- architecture
|
|
152
159
|
properties:
|
|
153
160
|
access_key_id:
|
|
154
161
|
type: string
|
|
@@ -164,6 +171,11 @@ components:
|
|
|
164
171
|
type: string
|
|
165
172
|
description: Default EC2 instance type
|
|
166
173
|
example: t3.medium
|
|
174
|
+
architecture:
|
|
175
|
+
type: string
|
|
176
|
+
description: CPU architecture for Docker builds
|
|
177
|
+
enum: [x86, arm64]
|
|
178
|
+
example: x86
|
|
167
179
|
|
|
168
180
|
ServerConfig:
|
|
169
181
|
type: object
|
|
@@ -13,6 +13,7 @@ application:
|
|
|
13
13
|
api_token: $HETZNER_API_TOKEN
|
|
14
14
|
server_type: cx22 # Default for all servers
|
|
15
15
|
server_location: nbg1
|
|
16
|
+
architecture: x86
|
|
16
17
|
|
|
17
18
|
# Multi-instance server configuration: 3 servers total
|
|
18
19
|
# 1 master (K3s control plane) + 2 workers (app + database)
|
|
@@ -206,6 +206,7 @@ module Nvoi
|
|
|
206
206
|
return "application.compute_provider.hetzner.api_token is required" if h["api_token"].blank?
|
|
207
207
|
return "application.compute_provider.hetzner.server_type is required" if h["server_type"].blank?
|
|
208
208
|
return "application.compute_provider.hetzner.server_location is required" if h["server_location"].blank?
|
|
209
|
+
return "application.compute_provider.hetzner.architecture is required" if h["architecture"].blank?
|
|
209
210
|
end
|
|
210
211
|
|
|
211
212
|
if (a = compute_provider&.dig("aws"))
|
|
@@ -213,12 +214,14 @@ module Nvoi
|
|
|
213
214
|
return "application.compute_provider.aws.secret_access_key is required" if a["secret_access_key"].blank?
|
|
214
215
|
return "application.compute_provider.aws.region is required" if a["region"].blank?
|
|
215
216
|
return "application.compute_provider.aws.instance_type is required" if a["instance_type"].blank?
|
|
217
|
+
return "application.compute_provider.aws.architecture is required" if a["architecture"].blank?
|
|
216
218
|
end
|
|
217
219
|
|
|
218
220
|
if (s = compute_provider&.dig("scaleway"))
|
|
219
221
|
return "application.compute_provider.scaleway.secret_key is required" if s["secret_key"].blank?
|
|
220
222
|
return "application.compute_provider.scaleway.project_id is required" if s["project_id"].blank?
|
|
221
223
|
return "application.compute_provider.scaleway.server_type is required" if s["server_type"].blank?
|
|
224
|
+
return "application.compute_provider.scaleway.architecture is required" if s["architecture"].blank?
|
|
222
225
|
end
|
|
223
226
|
|
|
224
227
|
# Servers (if any services defined)
|
|
@@ -317,6 +320,7 @@ module Nvoi
|
|
|
317
320
|
api_token: YOUR_HETZNER_API_TOKEN
|
|
318
321
|
server_type: cx22
|
|
319
322
|
server_location: fsn1
|
|
323
|
+
architecture: x86
|
|
320
324
|
|
|
321
325
|
servers:
|
|
322
326
|
master:
|
|
@@ -28,11 +28,12 @@ module Nvoi
|
|
|
28
28
|
def build_image(working_dir, tag)
|
|
29
29
|
cache_from = @config.namer.latest_image_tag
|
|
30
30
|
cache_args = "--cache-from #{cache_from}"
|
|
31
|
+
platform = @config.docker_platform
|
|
31
32
|
|
|
32
33
|
build_cmd = [
|
|
33
34
|
"cd #{working_dir} &&",
|
|
34
35
|
"DOCKER_BUILDKIT=1 docker build",
|
|
35
|
-
"--platform
|
|
36
|
+
"--platform #{platform}",
|
|
36
37
|
cache_args,
|
|
37
38
|
"--build-arg BUILDKIT_INLINE_CACHE=1",
|
|
38
39
|
"-t #{tag} ."
|
|
@@ -345,18 +345,21 @@ module Nvoi
|
|
|
345
345
|
end
|
|
346
346
|
|
|
347
347
|
def check_public_url(url)
|
|
348
|
-
|
|
348
|
+
# -L follows redirects, -s silent, -i include headers, -m timeout
|
|
349
|
+
curl_cmd = "curl -Lsi -m 10 '#{url}' 2>/dev/null"
|
|
349
350
|
output = @ssh.execute(curl_cmd).strip
|
|
350
351
|
|
|
351
|
-
|
|
352
|
+
# With -L, last HTTP line is the final response after redirects
|
|
353
|
+
http_lines = output.lines.select { |l| l.match?(/^HTTP\/[\d.]+\s+\d+/) }
|
|
354
|
+
http_code = http_lines.last&.match(/HTTP\/[\d.]+ (\d+)/)&.captures&.first || "000"
|
|
352
355
|
has_error_header = output.lines.any? { |line| line.downcase.start_with?("x-nvoi-error:") }
|
|
353
356
|
|
|
354
|
-
if http_code
|
|
357
|
+
if http_code.start_with?("2") && !has_error_header
|
|
355
358
|
{ success: true, http_code:, message: "OK" }
|
|
356
359
|
elsif has_error_header
|
|
357
360
|
{ success: false, http_code:, message: "Error backend responding (X-Nvoi-Error header present) - app is down" }
|
|
358
361
|
else
|
|
359
|
-
{ success: false, http_code:, message: "HTTP #{http_code} (expected:
|
|
362
|
+
{ success: false, http_code:, message: "HTTP #{http_code} (expected: 2xx)" }
|
|
360
363
|
end
|
|
361
364
|
end
|
|
362
365
|
|