nvoi 0.1.5 → 0.1.6

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/todo/refactor/00-overview.md +171 -0
  3. data/.claude/todo/refactor/01-objects.md +96 -0
  4. data/.claude/todo/refactor/02-utils.md +143 -0
  5. data/.claude/todo/refactor/03-external-cloud.md +164 -0
  6. data/.claude/todo/refactor/04-external-dns.md +104 -0
  7. data/.claude/todo/refactor/05-external.md +133 -0
  8. data/.claude/todo/refactor/06-cli.md +123 -0
  9. data/.claude/todo/refactor/07-cli-deploy-command.md +177 -0
  10. data/.claude/todo/refactor/08-cli-deploy-steps.md +201 -0
  11. data/.claude/todo/refactor/09-cli-delete-command.md +169 -0
  12. data/.claude/todo/refactor/10-cli-exec-command.md +157 -0
  13. data/.claude/todo/refactor/11-cli-credentials-command.md +190 -0
  14. data/.claude/todo/refactor/_target.md +79 -0
  15. data/.claude/todo/scaleway.impl.md +644 -0
  16. data/.claude/todo/scaleway.reference.md +520 -0
  17. data/Gemfile +1 -0
  18. data/Gemfile.lock +12 -2
  19. data/doc/config-schema.yaml +44 -11
  20. data/examples/golang/deploy.enc +0 -0
  21. data/examples/golang/main.go +18 -0
  22. data/exe/nvoi +3 -1
  23. data/lib/nvoi/cli/credentials/edit/command.rb +384 -0
  24. data/lib/nvoi/cli/credentials/show/command.rb +35 -0
  25. data/lib/nvoi/cli/db/command.rb +308 -0
  26. data/lib/nvoi/cli/delete/command.rb +75 -0
  27. data/lib/nvoi/cli/delete/steps/detach_volumes.rb +98 -0
  28. data/lib/nvoi/cli/delete/steps/teardown_dns.rb +49 -0
  29. data/lib/nvoi/cli/delete/steps/teardown_firewall.rb +46 -0
  30. data/lib/nvoi/cli/delete/steps/teardown_network.rb +30 -0
  31. data/lib/nvoi/cli/delete/steps/teardown_server.rb +50 -0
  32. data/lib/nvoi/cli/delete/steps/teardown_tunnel.rb +44 -0
  33. data/lib/nvoi/cli/delete/steps/teardown_volume.rb +61 -0
  34. data/lib/nvoi/cli/deploy/command.rb +184 -0
  35. data/lib/nvoi/cli/deploy/steps/build_image.rb +27 -0
  36. data/lib/nvoi/cli/deploy/steps/cleanup_images.rb +42 -0
  37. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +100 -0
  38. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +396 -0
  39. data/lib/nvoi/cli/deploy/steps/provision_network.rb +44 -0
  40. data/lib/nvoi/cli/deploy/steps/provision_server.rb +143 -0
  41. data/lib/nvoi/cli/deploy/steps/provision_volume.rb +171 -0
  42. data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +481 -0
  43. data/lib/nvoi/cli/exec/command.rb +173 -0
  44. data/lib/nvoi/cli.rb +83 -142
  45. data/lib/nvoi/config_api/actions/app.rb +53 -0
  46. data/lib/nvoi/config_api/actions/compute_provider.rb +55 -0
  47. data/lib/nvoi/config_api/actions/database.rb +70 -0
  48. data/lib/nvoi/config_api/actions/env.rb +32 -0
  49. data/lib/nvoi/config_api/actions/secret.rb +32 -0
  50. data/lib/nvoi/config_api/actions/server.rb +66 -0
  51. data/lib/nvoi/config_api/actions/volume.rb +40 -0
  52. data/lib/nvoi/config_api/base.rb +44 -0
  53. data/lib/nvoi/config_api/result.rb +26 -0
  54. data/lib/nvoi/config_api.rb +70 -0
  55. data/lib/nvoi/errors.rb +68 -50
  56. data/lib/nvoi/external/cloud/aws.rb +425 -0
  57. data/lib/nvoi/external/cloud/base.rb +99 -0
  58. data/lib/nvoi/external/cloud/factory.rb +48 -0
  59. data/lib/nvoi/external/cloud/hetzner.rb +376 -0
  60. data/lib/nvoi/external/cloud/scaleway.rb +533 -0
  61. data/lib/nvoi/external/cloud.rb +15 -0
  62. data/lib/nvoi/external/containerd.rb +82 -0
  63. data/lib/nvoi/external/database/mysql.rb +84 -0
  64. data/lib/nvoi/external/database/postgres.rb +82 -0
  65. data/lib/nvoi/external/database/provider.rb +65 -0
  66. data/lib/nvoi/external/database/sqlite.rb +72 -0
  67. data/lib/nvoi/external/database.rb +22 -0
  68. data/lib/nvoi/external/dns/cloudflare.rb +292 -0
  69. data/lib/nvoi/external/kubectl.rb +65 -0
  70. data/lib/nvoi/external/ssh.rb +106 -0
  71. data/lib/nvoi/objects/config_override.rb +60 -0
  72. data/lib/nvoi/objects/configuration.rb +463 -0
  73. data/lib/nvoi/objects/database.rb +56 -0
  74. data/lib/nvoi/objects/dns.rb +14 -0
  75. data/lib/nvoi/objects/firewall.rb +11 -0
  76. data/lib/nvoi/objects/network.rb +11 -0
  77. data/lib/nvoi/objects/server.rb +14 -0
  78. data/lib/nvoi/objects/service_spec.rb +26 -0
  79. data/lib/nvoi/objects/tunnel.rb +14 -0
  80. data/lib/nvoi/objects/volume.rb +17 -0
  81. data/lib/nvoi/utils/config_loader.rb +172 -0
  82. data/lib/nvoi/utils/constants.rb +61 -0
  83. data/lib/nvoi/{credentials/manager.rb → utils/credential_store.rb} +16 -16
  84. data/lib/nvoi/{credentials → utils}/crypto.rb +8 -5
  85. data/lib/nvoi/{config → utils}/env_resolver.rb +10 -2
  86. data/lib/nvoi/utils/logger.rb +84 -0
  87. data/lib/nvoi/{config/naming.rb → utils/namer.rb} +28 -25
  88. data/lib/nvoi/{deployer → utils}/retry.rb +23 -3
  89. data/lib/nvoi/utils/templates.rb +62 -0
  90. data/lib/nvoi/version.rb +1 -1
  91. data/lib/nvoi.rb +10 -54
  92. data/templates/error-backend.yaml.erb +134 -0
  93. metadata +97 -44
  94. data/examples/golang/deploy.yml +0 -54
  95. data/lib/nvoi/cloudflare/client.rb +0 -287
  96. data/lib/nvoi/config/config.rb +0 -248
  97. data/lib/nvoi/config/loader.rb +0 -102
  98. data/lib/nvoi/config/ssh_keys.rb +0 -82
  99. data/lib/nvoi/config/types.rb +0 -274
  100. data/lib/nvoi/constants.rb +0 -59
  101. data/lib/nvoi/credentials/editor.rb +0 -272
  102. data/lib/nvoi/deployer/cleaner.rb +0 -36
  103. data/lib/nvoi/deployer/image_builder.rb +0 -23
  104. data/lib/nvoi/deployer/infrastructure.rb +0 -126
  105. data/lib/nvoi/deployer/orchestrator.rb +0 -146
  106. data/lib/nvoi/deployer/service_deployer.rb +0 -311
  107. data/lib/nvoi/deployer/tunnel_manager.rb +0 -57
  108. data/lib/nvoi/deployer/types.rb +0 -8
  109. data/lib/nvoi/k8s/renderer.rb +0 -44
  110. data/lib/nvoi/k8s/templates.rb +0 -29
  111. data/lib/nvoi/logger.rb +0 -72
  112. data/lib/nvoi/providers/aws.rb +0 -403
  113. data/lib/nvoi/providers/base.rb +0 -111
  114. data/lib/nvoi/providers/hetzner.rb +0 -288
  115. data/lib/nvoi/providers/hetzner_client.rb +0 -170
  116. data/lib/nvoi/remote/docker_manager.rb +0 -203
  117. data/lib/nvoi/remote/ssh_executor.rb +0 -72
  118. data/lib/nvoi/remote/volume_manager.rb +0 -103
  119. data/lib/nvoi/service/delete.rb +0 -234
  120. data/lib/nvoi/service/deploy.rb +0 -80
  121. data/lib/nvoi/service/exec.rb +0 -144
  122. data/lib/nvoi/service/provider.rb +0 -36
  123. data/lib/nvoi/steps/application_deployer.rb +0 -26
  124. data/lib/nvoi/steps/database_provisioner.rb +0 -60
  125. data/lib/nvoi/steps/k3s_cluster_setup.rb +0 -105
  126. data/lib/nvoi/steps/k3s_provisioner.rb +0 -351
  127. data/lib/nvoi/steps/server_provisioner.rb +0 -43
  128. data/lib/nvoi/steps/services_provisioner.rb +0 -29
  129. data/lib/nvoi/steps/tunnel_configurator.rb +0 -66
  130. data/lib/nvoi/steps/volume_provisioner.rb +0 -154
@@ -0,0 +1,133 @@
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)
@@ -0,0 +1,123 @@
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)
@@ -0,0 +1,177 @@
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)
@@ -0,0 +1,201 @@
1
+ # 08 - CLI: Deploy Steps (Details)
2
+
3
+ ## Priority: FIFTH (part of deploy)
4
+
5
+ Each step is a focused, single-responsibility class.
6
+
7
+ ---
8
+
9
+ ## Step 1: provision_network.rb (~50 lines)
10
+
11
+ **Source:** Extract from `deployer/infrastructure.rb`
12
+
13
+ **Does:**
14
+
15
+ - Find or create network
16
+ - Find or create firewall
17
+
18
+ ```ruby
19
+ # cli/deploy/steps/provision_network.rb
20
+ module Nvoi
21
+ module CLI
22
+ module Deploy
23
+ module Steps
24
+ class ProvisionNetwork
25
+ def initialize(config, provider, log)
26
+ @config = config
27
+ @provider = provider
28
+ @log = log
29
+ end
30
+
31
+ def run
32
+ @log.info "Provisioning network: %s", @config.network_name
33
+ network = @provider.find_or_create_network(@config.network_name)
34
+
35
+ @log.info "Provisioning firewall: %s", @config.firewall_name
36
+ firewall = @provider.find_or_create_firewall(@config.firewall_name)
37
+
38
+ { network: network, firewall: firewall }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Step 2: provision_server.rb (~80 lines)
50
+
51
+ **Source:** `steps/server_provisioner.rb` + `deployer/infrastructure.rb`
52
+
53
+ **Does:**
54
+
55
+ - Create servers for each group
56
+ - Wait for SSH ready
57
+ - Return main server IP
58
+
59
+ ---
60
+
61
+ ## Step 3: provision_volume.rb (~150 lines)
62
+
63
+ **Source:** `steps/volume_provisioner.rb`
64
+
65
+ **Does:**
66
+
67
+ - Collect all volumes needed (database, services, app)
68
+ - Create volumes via provider
69
+ - Attach to servers
70
+ - Mount via SSH (format if needed, add to fstab)
71
+
72
+ ---
73
+
74
+ ## Step 4: setup_k3s.rb (~400 lines)
75
+
76
+ **Source:** Merge `steps/k3s_provisioner.rb` + `steps/k3s_cluster_setup.rb`
77
+
78
+ **Does:**
79
+
80
+ - Wait for cloud-init
81
+ - Install K3s server on master
82
+ - Setup kubeconfig
83
+ - Setup in-cluster registry
84
+ - Setup NGINX ingress
85
+ - Install K3s agent on workers
86
+ - Label nodes
87
+ - Get cluster token
88
+
89
+ **Note:** This is the largest step. Consider keeping as single file with private methods, or split into:
90
+
91
+ - `setup_k3s/master.rb`
92
+ - `setup_k3s/worker.rb`
93
+ - `setup_k3s/registry.rb`
94
+
95
+ ---
96
+
97
+ ## Step 5: configure_tunnel.rb (~70 lines)
98
+
99
+ **Source:** `steps/tunnel_configurator.rb`
100
+
101
+ **Does:**
102
+
103
+ - For each app service with domain:
104
+ - Create/find Cloudflare tunnel
105
+ - Configure tunnel ingress
106
+ - Create DNS CNAME record
107
+ - Return array of TunnelInfo
108
+
109
+ ---
110
+
111
+ ## Step 6: build_image.rb (~100 lines)
112
+
113
+ **Source:** `deployer/image_builder.rb` + parts of `remote/docker_manager.rb`
114
+
115
+ **Does:**
116
+
117
+ - Build Docker image locally
118
+ - Save to tar
119
+ - Rsync to remote server
120
+ - Import into containerd
121
+ - Tag image
122
+ - Push to in-cluster registry
123
+
124
+ ---
125
+
126
+ ## Step 7: deploy_database.rb (~80 lines)
127
+
128
+ **Source:** `steps/database_provisioner.rb` + `deployer/service_deployer.rb#deploy_database`
129
+
130
+ **Does:**
131
+
132
+ - Skip if SQLite (handled by app volumes)
133
+ - Deploy database secret (POSTGRES_USER, etc.)
134
+ - Deploy StatefulSet with hostPath volume
135
+ - Wait for database pod to be Running
136
+
137
+ ---
138
+
139
+ ## Step 8: deploy_service.rb (~300 lines)
140
+
141
+ **Source:** Merge `deployer/orchestrator.rb` + `deployer/service_deployer.rb`
142
+
143
+ **Does:**
144
+
145
+ - Deploy app secret (env vars)
146
+ - Deploy additional services (redis, etc.)
147
+ - Deploy app services (web, worker)
148
+ - Deploy cloudflared sidecars
149
+ - Verify traffic routing
150
+ - Run pre-run commands (migrations)
151
+
152
+ **Note:** Uses `External::Kubectl` for all manifest operations.
153
+
154
+ ---
155
+
156
+ ## Step 9: cleanup_images.rb (~40 lines)
157
+
158
+ **Source:** `deployer/cleaner.rb`
159
+
160
+ **Does:**
161
+
162
+ - List all images with prefix
163
+ - Keep newest N images
164
+ - Delete the rest
165
+
166
+ ---
167
+
168
+ ## Step Dependencies
169
+
170
+ ```
171
+ provision_network ─┐
172
+ ├─► provision_server ─► provision_volume ─► setup_k3s
173
+
174
+ configure_tunnel ──┼─► build_image ─► deploy_service ─► cleanup_images
175
+
176
+ └── (parallel where possible)
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Common Patterns Across Steps
182
+
183
+ ### Constructor Signature
184
+
185
+ All steps follow:
186
+
187
+ ```ruby
188
+ def initialize(config, provider_or_deps, log)
189
+ ```
190
+
191
+ ### Return Values
192
+
193
+ - Most return `nil` (side effects only)
194
+ - Some return data for next step:
195
+ - `provision_server` → main_server_ip
196
+ - `configure_tunnel` → [TunnelInfo]
197
+ - `provision_network` → { network:, firewall: }
198
+
199
+ ### Error Handling
200
+
201
+ Steps raise specific errors. Command catches and logs.