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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -5
  3. data/Gemfile.lock +17 -8
  4. data/Rakefile +1 -1
  5. data/lib/nvoi/cli/config/command.rb +46 -41
  6. data/lib/nvoi/cli/credentials/edit/command.rb +20 -20
  7. data/lib/nvoi/cli/credentials/show/command.rb +1 -1
  8. data/lib/nvoi/cli/db/command.rb +10 -10
  9. data/lib/nvoi/cli/delete/command.rb +2 -2
  10. data/lib/nvoi/cli/deploy/command.rb +29 -13
  11. data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
  12. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +2 -2
  13. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +3 -13
  14. data/lib/nvoi/cli/deploy/steps/provision_server.rb +1 -1
  15. data/lib/nvoi/cli/deploy/steps/provision_volume.rb +1 -1
  16. data/lib/nvoi/cli/exec/command.rb +3 -3
  17. data/lib/nvoi/cli/logs/command.rb +2 -2
  18. data/lib/nvoi/cli/onboard/command.rb +176 -622
  19. data/lib/nvoi/cli/onboard/steps/app.rb +108 -0
  20. data/lib/nvoi/cli/onboard/steps/app_name.rb +26 -0
  21. data/lib/nvoi/cli/onboard/steps/compute.rb +139 -0
  22. data/lib/nvoi/cli/onboard/steps/database.rb +97 -0
  23. data/lib/nvoi/cli/onboard/steps/domain.rb +48 -0
  24. data/lib/nvoi/cli/onboard/steps/env.rb +67 -0
  25. data/lib/nvoi/cli/onboard/ui.rb +84 -0
  26. data/lib/nvoi/cli/unlock/command.rb +2 -2
  27. data/lib/nvoi/cli.rb +0 -32
  28. data/lib/nvoi/configuration/app_service.rb +54 -0
  29. data/lib/nvoi/configuration/application.rb +44 -0
  30. data/lib/nvoi/configuration/builder.rb +417 -0
  31. data/lib/nvoi/configuration/database.rb +56 -0
  32. data/lib/nvoi/configuration/deploy.rb +15 -0
  33. data/lib/nvoi/{objects/service_spec.rb → configuration/deployment.rb} +4 -3
  34. data/lib/nvoi/{objects/config_override.rb → configuration/override.rb} +4 -4
  35. data/lib/nvoi/configuration/providers.rb +78 -0
  36. data/lib/nvoi/configuration/result.rb +43 -0
  37. data/lib/nvoi/configuration/root.rb +234 -0
  38. data/lib/nvoi/configuration/server.rb +39 -0
  39. data/lib/nvoi/configuration/service.rb +62 -0
  40. data/lib/nvoi/external/cloud/aws.rb +12 -12
  41. data/lib/nvoi/external/cloud/hetzner.rb +7 -7
  42. data/lib/nvoi/external/cloud/scaleway.rb +7 -7
  43. data/lib/nvoi/external/cloud/types.rb +42 -0
  44. data/lib/nvoi/external/containerd.rb +1 -48
  45. data/lib/nvoi/external/database/mysql.rb +1 -1
  46. data/lib/nvoi/external/database/postgres.rb +1 -1
  47. data/lib/nvoi/external/database/provider.rb +1 -1
  48. data/lib/nvoi/external/database/sqlite.rb +1 -1
  49. data/lib/nvoi/external/database/types.rb +55 -0
  50. data/lib/nvoi/external/dns/cloudflare.rb +6 -6
  51. data/lib/nvoi/external/dns/types.rb +24 -0
  52. data/lib/nvoi/external/ssh.rb +0 -12
  53. data/lib/nvoi/external/ssh_tunnel.rb +100 -0
  54. data/lib/nvoi/utils/config_loader.rb +12 -12
  55. data/lib/nvoi/utils/credential_store.rb +4 -4
  56. data/lib/nvoi/utils/env_resolver.rb +3 -3
  57. data/lib/nvoi/utils/namer.rb +2 -2
  58. data/lib/nvoi/utils/presence.rb +23 -0
  59. data/lib/nvoi/version.rb +1 -1
  60. data/lib/nvoi.rb +2 -17
  61. metadata +96 -57
  62. data/.claude/todo/refactor/00-overview.md +0 -171
  63. data/.claude/todo/refactor/01-objects.md +0 -96
  64. data/.claude/todo/refactor/02-utils.md +0 -143
  65. data/.claude/todo/refactor/03-external-cloud.md +0 -164
  66. data/.claude/todo/refactor/04-external-dns.md +0 -104
  67. data/.claude/todo/refactor/05-external.md +0 -133
  68. data/.claude/todo/refactor/06-cli.md +0 -123
  69. data/.claude/todo/refactor/07-cli-deploy-command.md +0 -177
  70. data/.claude/todo/refactor/08-cli-deploy-steps.md +0 -201
  71. data/.claude/todo/refactor/09-cli-delete-command.md +0 -169
  72. data/.claude/todo/refactor/10-cli-exec-command.md +0 -157
  73. data/.claude/todo/refactor/11-cli-credentials-command.md +0 -190
  74. data/.claude/todo/refactor/12-cli-db-command.md +0 -128
  75. data/.claude/todo/refactor/_target.md +0 -79
  76. data/.claude/todo/refactor-execution/00-entrypoint.md +0 -49
  77. data/.claude/todo/refactor-execution/01-objects.md +0 -42
  78. data/.claude/todo/refactor-execution/02-utils.md +0 -41
  79. data/.claude/todo/refactor-execution/03-external-cloud.md +0 -38
  80. data/.claude/todo/refactor-execution/04-external-dns.md +0 -35
  81. data/.claude/todo/refactor-execution/05-external-other.md +0 -46
  82. data/.claude/todo/refactor-execution/06-cli-deploy.md +0 -45
  83. data/.claude/todo/refactor-execution/07-cli-delete.md +0 -43
  84. data/.claude/todo/refactor-execution/08-cli-exec.md +0 -30
  85. data/.claude/todo/refactor-execution/09-cli-credentials.md +0 -34
  86. data/.claude/todo/refactor-execution/10-cli-db.md +0 -31
  87. data/.claude/todo/refactor-execution/11-cli-router.md +0 -44
  88. data/.claude/todo/refactor-execution/12-cleanup.md +0 -120
  89. data/.claude/todo/refactor-execution/_monitoring-strategy.md +0 -126
  90. data/.claude/todo/scaleway.impl.md +0 -644
  91. data/.claude/todo/scaleway.reference.md +0 -520
  92. data/.claude/todos.md +0 -550
  93. data/ingest +0 -0
  94. data/lib/nvoi/config_api/actions/app.rb +0 -53
  95. data/lib/nvoi/config_api/actions/compute_provider.rb +0 -55
  96. data/lib/nvoi/config_api/actions/database.rb +0 -70
  97. data/lib/nvoi/config_api/actions/domain_provider.rb +0 -40
  98. data/lib/nvoi/config_api/actions/env.rb +0 -32
  99. data/lib/nvoi/config_api/actions/init.rb +0 -67
  100. data/lib/nvoi/config_api/actions/secret.rb +0 -32
  101. data/lib/nvoi/config_api/actions/server.rb +0 -66
  102. data/lib/nvoi/config_api/actions/service.rb +0 -52
  103. data/lib/nvoi/config_api/actions/volume.rb +0 -40
  104. data/lib/nvoi/config_api/base.rb +0 -38
  105. data/lib/nvoi/config_api/result.rb +0 -26
  106. data/lib/nvoi/config_api.rb +0 -93
  107. data/lib/nvoi/objects/configuration.rb +0 -483
  108. data/lib/nvoi/objects/database.rb +0 -56
  109. data/lib/nvoi/objects/dns.rb +0 -14
  110. data/lib/nvoi/objects/firewall.rb +0 -11
  111. data/lib/nvoi/objects/network.rb +0 -11
  112. data/lib/nvoi/objects/server.rb +0 -14
  113. data/lib/nvoi/objects/tunnel.rb +0 -14
  114. data/lib/nvoi/objects/volume.rb +0 -17
@@ -1,164 +0,0 @@
1
- # 03 - External: Cloud Providers
2
-
3
- ## Priority: THIRD
4
-
5
- External adapters depend on Objects and Utils.
6
-
7
- ---
8
-
9
- ## Current State
10
-
11
- | File | Lines | Purpose |
12
- | ------------------------------ | ----- | ---------------------------- |
13
- | `providers/base.rb` | 112 | Abstract interface + Structs |
14
- | `providers/hetzner.rb` | 289 | Hetzner Cloud implementation |
15
- | `providers/hetzner_client.rb` | ~200 | Hetzner HTTP client |
16
- | `providers/aws.rb` | ~250 | AWS implementation |
17
- | `providers/scaleway.rb` | ~280 | Scaleway implementation |
18
- | `providers/scaleway_client.rb` | ~200 | Scaleway HTTP client |
19
- | `service/provider.rb` | ~50 | Provider factory helper |
20
-
21
- **Total: ~1380 lines across 7 files**
22
-
23
- ---
24
-
25
- ## Target Structure
26
-
27
- ```
28
- lib/nvoi/external/cloud/
29
- ├── base.rb # Abstract interface (methods only, no Structs)
30
- ├── hetzner.rb # Hetzner (merge provider + client)
31
- ├── aws.rb # AWS
32
- ├── scaleway.rb # Scaleway (merge provider + client)
33
- └── factory.rb # Provider initialization helper
34
- ```
35
-
36
- ---
37
-
38
- ## What Each Provider Does
39
-
40
- All providers implement:
41
-
42
- ```ruby
43
- # Network operations
44
- find_or_create_network(name)
45
- get_network_by_name(name)
46
- delete_network(id)
47
-
48
- # Firewall operations
49
- find_or_create_firewall(name)
50
- get_firewall_by_name(name)
51
- delete_firewall(id)
52
-
53
- # Server operations
54
- find_server(name)
55
- list_servers
56
- create_server(opts)
57
- wait_for_server(server_id, max_attempts)
58
- delete_server(id)
59
-
60
- # Volume operations
61
- create_volume(opts)
62
- get_volume(id)
63
- get_volume_by_name(name)
64
- delete_volume(id)
65
- attach_volume(volume_id, server_id)
66
- detach_volume(volume_id)
67
-
68
- # Validation
69
- validate_instance_type(instance_type)
70
- validate_region(region)
71
- validate_credentials
72
- ```
73
-
74
- ---
75
-
76
- ## DRY Opportunities
77
-
78
- ### 1. Merge Provider + Client
79
-
80
- Current pattern:
81
-
82
- - `hetzner.rb` = high-level operations
83
- - `hetzner_client.rb` = HTTP calls
84
-
85
- Merge into single file. The "client" is just private methods:
86
-
87
- ```ruby
88
- # external/cloud/hetzner.rb
89
- module Nvoi
90
- module External
91
- module Cloud
92
- class Hetzner < Base
93
- def find_server(name) ... end # public
94
-
95
- private
96
-
97
- def get(path) ... end # HTTP helpers
98
- def post(path, body) ... end
99
- end
100
- end
101
- end
102
- end
103
- ```
104
-
105
- ### 2. Extract Common HTTP Client
106
-
107
- All providers do similar HTTP work. Extract base:
108
-
109
- ```ruby
110
- # external/cloud/base.rb
111
- class Base
112
- private
113
-
114
- def http_get(url, headers = {}) ... end
115
- def http_post(url, body, headers = {}) ... end
116
- def handle_response(response) ... end
117
- end
118
- ```
119
-
120
- ### 3. Provider Factory
121
-
122
- Current `service/provider.rb` has `ProviderHelper` module. Move to:
123
-
124
- ```ruby
125
- # external/cloud/factory.rb
126
- module Nvoi
127
- module External
128
- module Cloud
129
- def self.for(config)
130
- case config.provider_name
131
- when "hetzner" then Hetzner.new(config.hetzner.api_token)
132
- when "aws" then AWS.new(config.aws)
133
- when "scaleway" then Scaleway.new(config.scaleway)
134
- end
135
- end
136
- end
137
- end
138
- end
139
- ```
140
-
141
- ### 4. Struct Extraction (already in 01-objects.md)
142
-
143
- Remove `Server`, `Network`, etc. from `base.rb` → use from `objects/`
144
-
145
- ---
146
-
147
- ## Migration Steps
148
-
149
- 1. Create `lib/nvoi/external/cloud/` directory
150
- 2. Create `base.rb` with interface only (no Structs)
151
- 3. Merge `hetzner.rb` + `hetzner_client.rb` → `external/cloud/hetzner.rb`
152
- 4. Move `aws.rb` → `external/cloud/aws.rb`
153
- 5. Merge `scaleway.rb` + `scaleway_client.rb` → `external/cloud/scaleway.rb`
154
- 6. Move `service/provider.rb` → `external/cloud/factory.rb`
155
- 7. Update requires to use `Objects::Server`, etc.
156
-
157
- ---
158
-
159
- ## Estimated Effort
160
-
161
- - **Lines to consolidate:** ~1380
162
- - **Files created:** 5
163
- - **Files deleted:** 7
164
- - **DRY savings:** ~100 lines (merged clients, shared HTTP base)
@@ -1,104 +0,0 @@
1
- # 04 - External: DNS Provider (Cloudflare)
2
-
3
- ## Priority: THIRD (parallel with cloud)
4
-
5
- ---
6
-
7
- ## Current State
8
-
9
- | File | Lines | Purpose |
10
- | ---------------------------- | ----- | -------------------------- |
11
- | `cloudflare/client.rb` | 288 | Cloudflare API client |
12
- | `deployer/tunnel_manager.rb` | 58 | Tunnel setup orchestration |
13
-
14
- **Total: ~346 lines across 2 files**
15
-
16
- ---
17
-
18
- ## Target Structure
19
-
20
- ```
21
- lib/nvoi/external/dns/
22
- └── cloudflare.rb # Full Cloudflare client (tunnels + DNS)
23
- ```
24
-
25
- ---
26
-
27
- ## What Cloudflare Client Does
28
-
29
- ### Tunnel Operations
30
-
31
- - `create_tunnel(name)` → Tunnel
32
- - `find_tunnel(name)` → Tunnel | nil
33
- - `get_tunnel_token(tunnel_id)` → String
34
- - `update_tunnel_configuration(tunnel_id, hostname, service_url)`
35
- - `verify_tunnel_configuration(tunnel_id, hostname, service_url, attempts)`
36
- - `delete_tunnel(tunnel_id)`
37
-
38
- ### DNS Operations
39
-
40
- - `find_zone(domain)` → Zone | nil
41
- - `find_dns_record(zone_id, name, type)` → DNSRecord | nil
42
- - `create_dns_record(zone_id, name, type, content, proxied:)`
43
- - `update_dns_record(zone_id, record_id, name, type, content, proxied:)`
44
- - `create_or_update_dns_record(...)` → convenience method
45
- - `delete_dns_record(zone_id, record_id)`
46
-
47
- ---
48
-
49
- ## DRY Opportunities
50
-
51
- ### 1. Absorb TunnelManager into Cloudflare Client
52
-
53
- `TunnelManager.setup_tunnel` does:
54
-
55
- 1. Find or create tunnel
56
- 2. Get token
57
- 3. Configure ingress
58
- 4. Verify configuration
59
- 5. Create DNS record
60
-
61
- This is just a convenience method. Move into client:
62
-
63
- ```ruby
64
- # external/dns/cloudflare.rb
65
- class Cloudflare
66
- # Existing low-level methods...
67
-
68
- # High-level convenience
69
- def setup_tunnel(name, hostname, service_url, domain)
70
- tunnel = find_tunnel(name) || create_tunnel(name)
71
- token = tunnel.token || get_tunnel_token(tunnel.id)
72
- update_tunnel_configuration(tunnel.id, hostname, service_url)
73
- verify_tunnel_configuration(tunnel.id, hostname, service_url, Constants::TUNNEL_CONFIG_VERIFY_ATTEMPTS)
74
-
75
- zone = find_zone(domain)
76
- create_or_update_dns_record(zone.id, hostname, "CNAME", "#{tunnel.id}.cfargotunnel.com")
77
-
78
- Objects::TunnelInfo.new(tunnel_id: tunnel.id, tunnel_token: token)
79
- end
80
- end
81
- ```
82
-
83
- ### 2. Remove Struct Definitions
84
-
85
- Move `Tunnel`, `Zone`, `DNSRecord` to `objects/` (done in 01-objects.md)
86
-
87
- ---
88
-
89
- ## Migration Steps
90
-
91
- 1. Create `lib/nvoi/external/dns/` directory
92
- 2. Move `cloudflare/client.rb` → `external/dns/cloudflare.rb`
93
- 3. Merge `deployer/tunnel_manager.rb` into Cloudflare as `setup_tunnel` method
94
- 4. Remove Struct definitions (use from `objects/`)
95
- 5. Update namespace from `Cloudflare::Client` to `External::DNS::Cloudflare`
96
-
97
- ---
98
-
99
- ## Estimated Effort
100
-
101
- - **Lines to consolidate:** ~346
102
- - **Files created:** 1
103
- - **Files deleted:** 2
104
- - **DRY savings:** ~20 lines (TunnelManager overhead)
@@ -1,133 +0,0 @@
1
- # 05 - External: SSH, Kubectl, Containerd
2
-
3
- ## Priority: THIRD (parallel with cloud/dns)
4
-
5
- ---
6
-
7
- ## Current State
8
-
9
- | File | Lines | Purpose |
10
- | -------------------------- | ----- | ---------------------------- |
11
- | `remote/ssh_executor.rb` | 73 | SSH command execution |
12
- | `remote/docker_manager.rb` | 204 | Docker/containerd operations |
13
- | `remote/volume_manager.rb` | 104 | Volume mount operations |
14
- | `k8s/renderer.rb` | 45 | kubectl apply wrapper |
15
-
16
- **Total: ~426 lines across 4 files**
17
-
18
- ---
19
-
20
- ## Target Structure
21
-
22
- ```
23
- lib/nvoi/external/
24
- ├── ssh.rb # SSH executor
25
- ├── kubectl.rb # kubectl wrapper (from k8s/renderer.rb)
26
- └── containerd.rb # containerd/Docker operations (from docker_manager.rb + volume_manager.rb)
27
- ```
28
-
29
- ---
30
-
31
- ## What Each Does
32
-
33
- ### SSH Executor (`remote/ssh_executor.rb`)
34
-
35
- - `execute(command, stream: false)` → String
36
- - `execute_quiet(command)` → nil (ignore errors)
37
- - `open_shell` → exec into SSH
38
-
39
- ### Docker Manager (`remote/docker_manager.rb`)
40
-
41
- - `create_network(name)`
42
- - `build_image(path, tag, cache_from)` → local build, rsync, ctr import
43
- - `run_container(opts)`
44
- - `exec(container, command)`
45
- - `wait_for_health(opts)`
46
- - `container_status(name)`, `container_logs(name, lines)`
47
- - `stop_container(name)`, `remove_container(name)`
48
- - `list_containers(filter)`, `list_images(filter)`
49
- - `cleanup_old_images(prefix, keep_tags)`
50
- - `container_running?(name)`
51
- - `setup_cloudflared(network, token, name)`
52
-
53
- ### Volume Manager (`remote/volume_manager.rb`)
54
-
55
- - `mount(opts)` → format + mount + fstab
56
- - `unmount(mount_path)`
57
- - `mounted?(mount_path)`
58
- - `remove_from_fstab(mount_path)`
59
-
60
- ### K8s Renderer (`k8s/renderer.rb`)
61
-
62
- - `render_template(name, data)` → String
63
- - `apply_manifest(ssh, template_name, data)` → kubectl apply
64
- - `wait_for_deployment(ssh, name, namespace, timeout)`
65
-
66
- ---
67
-
68
- ## DRY Opportunities
69
-
70
- ### 1. Rename docker_manager → containerd
71
-
72
- It's not really Docker anymore. It uses `ctr` (containerd). Name it accurately:
73
-
74
- ```ruby
75
- # external/containerd.rb
76
- class Containerd
77
- def initialize(ssh) ... end
78
- def build_and_import(path, tag) ... end
79
- def list_images(filter) ... end
80
- def cleanup_images(prefix, keep_tags) ... end
81
- end
82
- ```
83
-
84
- ### 2. Extract Volume Logic
85
-
86
- Volume mounting is separate from container runtime. Could stay in containerd or be separate.
87
- Decision: Keep in containerd since it's all "server-side stuff via SSH".
88
-
89
- ### 3. Simplify K8s Renderer
90
-
91
- Move template rendering to `utils/templates.rb`. Keep only kubectl operations:
92
-
93
- ```ruby
94
- # external/kubectl.rb
95
- class Kubectl
96
- def initialize(ssh) ... end
97
- def apply(manifest) ... end
98
- def wait_for_deployment(name, namespace, timeout) ... end
99
- def wait_for_statefulset(name, namespace, timeout) ... end
100
- def get_pod_name(label) ... end
101
- def exec(pod, command) ... end
102
- end
103
- ```
104
-
105
- ### 4. SSH as Dependency
106
-
107
- All external adapters that run on remote servers need SSH:
108
-
109
- - `Containerd.new(ssh)`
110
- - `Kubectl.new(ssh)`
111
- - `VolumeManager.new(ssh)` (if kept separate)
112
-
113
- This is correct. SSH is the transport, others are domain-specific.
114
-
115
- ---
116
-
117
- ## Migration Steps
118
-
119
- 1. Move `remote/ssh_executor.rb` → `external/ssh.rb`
120
- 2. Rename `remote/docker_manager.rb` → `external/containerd.rb`
121
- 3. Merge `remote/volume_manager.rb` into `external/containerd.rb` (or keep separate)
122
- 4. Extract kubectl operations from `k8s/renderer.rb` → `external/kubectl.rb`
123
- 5. Move template rendering to `utils/templates.rb` (already planned in 02-utils.md)
124
- 6. Update namespace from `Remote::SSHExecutor` to `External::SSH`
125
-
126
- ---
127
-
128
- ## Estimated Effort
129
-
130
- - **Lines to consolidate:** ~426
131
- - **Files created:** 3
132
- - **Files deleted:** 4
133
- - **DRY savings:** ~30 lines (simplified renderer, naming cleanup)
@@ -1,123 +0,0 @@
1
- # 06 - CLI Entry Point (Thor Routing)
2
-
3
- ## Priority: FOURTH
4
-
5
- CLI depends on all command implementations.
6
-
7
- ---
8
-
9
- ## Current State
10
-
11
- | File | Lines | Purpose |
12
- | -------- | ----- | ----------------------------------------- |
13
- | `cli.rb` | 191 | Thor commands + CredentialsCLI subcommand |
14
-
15
- ---
16
-
17
- ## Target Structure
18
-
19
- ```
20
- lib/nvoi/
21
- └── cli.rb # Thor routing only (~50 lines)
22
- ```
23
-
24
- ---
25
-
26
- ## What CLI Currently Does
27
-
28
- 1. **Thor setup** - class options, exit behavior
29
- 2. **Command definitions** - `deploy`, `delete`, `exec`, `credentials`
30
- 3. **Config path resolution** - `resolve_config_path`, `resolve_working_dir`
31
- 4. **Instantiate services** - creates `DeployService`, `DeleteService`, etc.
32
-
33
- ---
34
-
35
- ## Target: Thin Router
36
-
37
- CLI should ONLY:
38
-
39
- 1. Define Thor commands
40
- 2. Parse options
41
- 3. Delegate to `cli/<command>/command.rb`
42
-
43
- ```ruby
44
- # cli.rb
45
- module Nvoi
46
- class CLI < Thor
47
- class_option :config, aliases: "-c", default: "deploy.enc"
48
- class_option :dir, aliases: "-d", default: "."
49
-
50
- desc "deploy", "Deploy application"
51
- option :dockerfile_path
52
- def deploy
53
- require_relative "cli/deploy/command"
54
- CLI::Deploy::Command.new(options).run
55
- end
56
-
57
- desc "delete", "Delete infrastructure"
58
- def delete
59
- require_relative "cli/delete/command"
60
- CLI::Delete::Command.new(options).run
61
- end
62
-
63
- desc "exec [COMMAND...]", "Execute command on server"
64
- option :server, default: "main"
65
- option :all, type: :boolean
66
- option :interactive, aliases: "-i", type: :boolean
67
- def exec(*args)
68
- require_relative "cli/exec/command"
69
- CLI::Exec::Command.new(options).run(args)
70
- end
71
-
72
- desc "credentials SUBCOMMAND", "Manage credentials"
73
- subcommand "credentials", CLI::Credentials
74
- end
75
-
76
- class CLI::Credentials < Thor
77
- desc "edit", "Edit encrypted credentials"
78
- def edit
79
- require_relative "cli/credentials/edit/command"
80
- CLI::Credentials::Edit::Command.new(options).run
81
- end
82
-
83
- desc "show", "Show decrypted credentials"
84
- def show
85
- require_relative "cli/credentials/show/command"
86
- CLI::Credentials::Show::Command.new(options).run
87
- end
88
- end
89
- end
90
- ```
91
-
92
- ---
93
-
94
- ## DRY Opportunities
95
-
96
- ### 1. Move Logic to Commands
97
-
98
- All the `resolve_config_path`, validation, service instantiation moves INTO each command.
99
-
100
- ### 2. Lazy Requires
101
-
102
- Use `require_relative` inside methods. Faster CLI startup, only loads what's needed.
103
-
104
- ### 3. CredentialsCLI → Nested Class
105
-
106
- Currently separate class, can be nested under CLI.
107
-
108
- ---
109
-
110
- ## Migration Steps
111
-
112
- 1. Create command files first (07-10)
113
- 2. Strip `cli.rb` down to routing only
114
- 3. Move `CredentialsCLI` inline as `CLI::Credentials`
115
- 4. Add lazy requires for each command
116
-
117
- ---
118
-
119
- ## Estimated Effort
120
-
121
- - **Lines after refactor:** ~50 (down from 191)
122
- - **Logic moved to:** `cli/*/command.rb` files
123
- - **DRY savings:** 141 lines moved (not deleted, redistributed)
@@ -1,177 +0,0 @@
1
- # 07 - CLI: Deploy Command
2
-
3
- ## Priority: FIFTH
4
-
5
- Depends on external adapters, utils, objects.
6
-
7
- ---
8
-
9
- ## Current State
10
-
11
- | File | Lines | Purpose |
12
- | ------------------- | ----- | --------------------------- |
13
- | `service/deploy.rb` | 81 | DeployService orchestration |
14
-
15
- ---
16
-
17
- ## Target Structure
18
-
19
- ```
20
- lib/nvoi/cli/deploy/
21
- ├── command.rb # Entry point + step orchestration
22
- └── steps/
23
- ├── provision_network.rb
24
- ├── provision_server.rb
25
- ├── provision_volume.rb
26
- ├── setup_k3s.rb
27
- ├── configure_tunnel.rb
28
- ├── build_image.rb
29
- ├── deploy_database.rb
30
- ├── deploy_service.rb
31
- └── cleanup_images.rb
32
- ```
33
-
34
- ---
35
-
36
- ## What Deploy Does (Current Flow)
37
-
38
- ```
39
- 1. Load config
40
- 2. Init provider
41
- 3. Validate provider config
42
- 4. provision_server:
43
- a. ServerProvisioner.run → provisions network, firewall, servers
44
- b. VolumeProvisioner.run → creates/attaches/mounts volumes
45
- c. K3sClusterSetup.run → installs K3s on master + workers
46
- 5. configure_tunnels:
47
- a. TunnelConfigurator.run → creates Cloudflare tunnels + DNS
48
- 6. deploy_application:
49
- a. Orchestrator.run:
50
- - Acquire lock
51
- - ImageBuilder.build_and_push
52
- - Push to registry
53
- - ServiceDeployer.deploy_app_secret
54
- - ServiceDeployer.deploy_database
55
- - ServiceDeployer.deploy_service (for each service)
56
- - ServiceDeployer.deploy_app_service (for each app)
57
- - ServiceDeployer.deploy_cloudflared
58
- - ServiceDeployer.verify_traffic_switchover
59
- - Cleaner.cleanup_old_images
60
- - Release lock
61
- ```
62
-
63
- ---
64
-
65
- ## Target Flow (Simplified)
66
-
67
- ```ruby
68
- # cli/deploy/command.rb
69
- class Command
70
- def run
71
- config = Utils::ConfigLoader.new.load(config_path)
72
- provider = External::Cloud.for(config)
73
- log = Utils::Logger.new
74
-
75
- Steps::ProvisionNetwork.new(config, provider, log).run
76
- Steps::ProvisionServer.new(config, provider, log).run
77
- Steps::ProvisionVolume.new(config, provider, log).run
78
- Steps::SetupK3s.new(config, provider, log).run
79
- tunnels = Steps::ConfigureTunnel.new(config, log).run
80
- Steps::BuildImage.new(config, log).run
81
- Steps::DeployService.new(config, tunnels, log).run
82
- Steps::CleanupImages.new(config, log).run
83
- end
84
- end
85
- ```
86
-
87
- ---
88
-
89
- ## DRY Opportunities
90
-
91
- ### 1. Flatten the Hierarchy
92
-
93
- Current: `DeployService` → `Steps::*` → `Deployer::*`
94
- Target: `Command` → `Steps::*` directly
95
-
96
- Kill `Deployer::Orchestrator`. Its logic moves into `Steps::DeployService`.
97
-
98
- ### 2. Remove Thin Wrappers
99
-
100
- `Steps::ApplicationDeployer` is just:
101
-
102
- ```ruby
103
- def run
104
- orchestrator = Deployer::Orchestrator.new(@config, @provider, @log)
105
- orchestrator.run(@server_ip, @tunnels, @working_dir)
106
- end
107
- ```
108
-
109
- Delete it. Put that logic in `Steps::DeployService`.
110
-
111
- ### 3. Consolidate ServiceDeployer
112
-
113
- `Deployer::ServiceDeployer` (312 lines) does too much:
114
-
115
- - Deploy app secret
116
- - Deploy app service
117
- - Deploy database
118
- - Deploy generic service
119
- - Deploy cloudflared
120
- - Verify traffic
121
- - Run pre-run commands
122
-
123
- Split into focused steps or keep as internal helper in `Steps::DeployService`.
124
-
125
- ### 4. Lock Management
126
-
127
- Deployment lock is in `Orchestrator`. Move to `Command`:
128
-
129
- ```ruby
130
- def run
131
- acquire_lock(ssh)
132
- # ... steps ...
133
- ensure
134
- release_lock(ssh)
135
- end
136
- ```
137
-
138
- ---
139
-
140
- ## Migration Steps
141
-
142
- 1. Create `lib/nvoi/cli/deploy/command.rb`
143
- 2. Create `lib/nvoi/cli/deploy/steps/` directory
144
- 3. Move `steps/server_provisioner.rb` → `cli/deploy/steps/provision_server.rb`
145
- 4. Move `steps/volume_provisioner.rb` → `cli/deploy/steps/provision_volume.rb`
146
- 5. Merge `steps/k3s_cluster_setup.rb` + `steps/k3s_provisioner.rb` → `cli/deploy/steps/setup_k3s.rb`
147
- 6. Move `steps/tunnel_configurator.rb` → `cli/deploy/steps/configure_tunnel.rb`
148
- 7. Move `deployer/image_builder.rb` → `cli/deploy/steps/build_image.rb`
149
- 8. Merge `deployer/orchestrator.rb` + `deployer/service_deployer.rb` → `cli/deploy/steps/deploy_service.rb`
150
- 9. Move `deployer/cleaner.rb` → `cli/deploy/steps/cleanup_images.rb`
151
- 10. Extract network/firewall from ServerProvisioner → `cli/deploy/steps/provision_network.rb`
152
- 11. Delete old files: `service/deploy.rb`, `deployer/orchestrator.rb`, `steps/application_deployer.rb`
153
-
154
- ---
155
-
156
- ## Estimated Effort
157
-
158
- **Files involved:**
159
-
160
- - `service/deploy.rb` (81 lines)
161
- - `steps/server_provisioner.rb` (44 lines)
162
- - `steps/volume_provisioner.rb` (155 lines)
163
- - `steps/k3s_provisioner.rb` (352 lines)
164
- - `steps/k3s_cluster_setup.rb` (106 lines)
165
- - `steps/tunnel_configurator.rb` (67 lines)
166
- - `steps/application_deployer.rb` (27 lines)
167
- - `deployer/orchestrator.rb` (147 lines)
168
- - `deployer/infrastructure.rb` (127 lines)
169
- - `deployer/image_builder.rb` (24 lines)
170
- - `deployer/service_deployer.rb` (312 lines)
171
- - `deployer/cleaner.rb` (37 lines)
172
-
173
- **Total: ~1479 lines to reorganize**
174
-
175
- - **Files created:** 9 (command.rb + 8 steps)
176
- - **Files deleted:** 12
177
- - **DRY savings:** ~150 lines (removed wrappers, flattened hierarchy)