smart_domain 0.1.0 → 0.1.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -8
  3. data/README.md +47 -0
  4. data/lib/generators/{active_domain → smart_domain}/domain/templates/events/created_event.rb.tt +1 -1
  5. data/lib/generators/{active_domain → smart_domain}/domain/templates/events/deleted_event.rb.tt +1 -1
  6. data/lib/generators/{active_domain → smart_domain}/domain/templates/events/updated_event.rb.tt +2 -2
  7. data/lib/generators/{active_domain → smart_domain}/domain/templates/service.rb.tt +5 -5
  8. data/lib/generators/{active_domain → smart_domain}/domain/templates/setup.rb.tt +4 -4
  9. data/lib/smart_domain/version.rb +1 -1
  10. data/smart_domain-0.1.0.gem +0 -0
  11. metadata +16 -27
  12. data/examples/blog_app/.kamal/hooks/docker-setup.sample +0 -3
  13. data/examples/blog_app/.kamal/hooks/post-app-boot.sample +0 -3
  14. data/examples/blog_app/.kamal/hooks/post-deploy.sample +0 -14
  15. data/examples/blog_app/.kamal/hooks/post-proxy-reboot.sample +0 -3
  16. data/examples/blog_app/.kamal/hooks/pre-app-boot.sample +0 -3
  17. data/examples/blog_app/.kamal/hooks/pre-build.sample +0 -51
  18. data/examples/blog_app/.kamal/hooks/pre-connect.sample +0 -47
  19. data/examples/blog_app/.kamal/hooks/pre-deploy.sample +0 -122
  20. data/examples/blog_app/.kamal/hooks/pre-proxy-reboot.sample +0 -3
  21. data/examples/blog_app/.kamal/secrets +0 -20
  22. data/examples/blog_app/bin/kamal +0 -27
  23. data/examples/blog_app/config/deploy.yml +0 -120
  24. /data/examples/blog_app/config/initializers/{active_domain.rb → smart_domain.rb} +0 -0
  25. /data/lib/generators/{active_domain → smart_domain}/domain/domain_generator.rb +0 -0
  26. /data/lib/generators/{active_domain → smart_domain}/domain/templates/policy.rb.tt +0 -0
  27. /data/lib/generators/{active_domain → smart_domain}/install/install_generator.rb +0 -0
  28. /data/lib/generators/{active_domain → smart_domain}/install/templates/README +0 -0
  29. /data/lib/generators/{active_domain → smart_domain}/install/templates/application_event.rb +0 -0
  30. /data/lib/generators/{active_domain → smart_domain}/install/templates/application_policy.rb +0 -0
  31. /data/lib/generators/{active_domain → smart_domain}/install/templates/application_service.rb +0 -0
  32. /data/lib/generators/{active_domain → smart_domain}/install/templates/initializer.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5e5a4574b90ca06fb1e2747b2da47b7a5168794f4e80901ee2688930f2e6ad8
4
- data.tar.gz: e1ada977da14eb9f8ec6092dcfee612f7283847d4a36b136faeca0dc4d51b92a
3
+ metadata.gz: 8c99a8063c81eea7d1de36a368d27803fd13807ae99271c4d184d1f58c82d67d
4
+ data.tar.gz: 41c04fc2729bfc7861dbbf396e65c4d04a843c353f493796e72b89af10b1f388
5
5
  SHA512:
6
- metadata.gz: 03d5fe8c7ad6be23ee69b9d11139ade594130151d12d7ea30f68156c90fa1dc05c1e133da427bc71e0a852eab0455a5c4d2d11f16345c05397b2856842898961
7
- data.tar.gz: 9aeb909c122d04cf2677aceb63d7f2b43b9b1a7d86c92264e9a4096cd1ee69470501c1e1e57b082fe73d6d48dbc5d4e40195d12df7a68116c12a73e0cf74a00c
6
+ metadata.gz: 512d73e5e5081d01a2aa204f2d02d27e217d87f143863a5ab890a738ccdd637706e6b21927f08f67c0bfd5bdf55990d3ac8a042a68b5442869d99da844dea9f5
7
+ data.tar.gz: d3b7c9bae2e7787d7b2bda3604cca244069879584d2d18ce0ef0f60437e4ad7d14d6e6bbbad919059ee3c215bb021d374f31719718ed6b8ae48aa57990f788cc
data/CHANGELOG.md CHANGED
@@ -7,14 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Added
11
- - Initial gem structure
12
- - Event system (DomainEvent, EventBus, Event Mixins)
13
- - Generic handlers (Audit, Metrics)
14
- - Domain service pattern
15
- - ActiveRecord integration
16
- - Rails generators for domain scaffolding
17
- - Comprehensive documentation
10
+ ## [0.1.1] - 2025-12-31
11
+
12
+ ### Changed
13
+ - Enhanced README with AI-augmented development advantages section
14
+ - Explained how DDD/EDA architecture reduces context windows by 93%
15
+ - Added concrete examples of reduced cognitive load for AI assistants
18
16
 
19
17
  ## [0.1.0] - 2025-12-29
20
18
 
data/README.md CHANGED
@@ -15,6 +15,53 @@ SmartDomain brings battle-tested DDD/EDA patterns from platform to Ruby on Rails
15
15
  - ✅ **Multi-tenancy Support** - Built-in support for multi-tenant applications
16
16
  - ✅ **Audit Compliance** - Automatic audit logging for compliance requirements
17
17
 
18
+ ## Why SmartDomain for AI-Augmented Development?
19
+
20
+ SmartDomain's architecture is uniquely suited for AI-augmented development workflows:
21
+
22
+ **Reduced Context Windows**
23
+ - Domain-driven design creates **loosely coupled bounded contexts**
24
+ - Each domain (User, Order, Product) is self-contained with its own services, events, and policies
25
+ - AI tools can focus on one domain at a time, drastically reducing context requirements
26
+ - Clear boundaries mean AI understands exactly what code is relevant
27
+
28
+ **Event-Driven Decoupling**
29
+ - Events decouple domains from each other
30
+ - Changes in one domain don't cascade through the codebase
31
+ - AI can modify a single domain without understanding the entire system
32
+ - Explicit event contracts make dependencies transparent
33
+
34
+ **Explicit Patterns**
35
+ - Standardized structure (Service → Events → Handlers) makes code predictable
36
+ - AI learns the pattern once, applies it everywhere
37
+ - Generators scaffold domains with consistent architecture
38
+ - Less cognitive load for both humans and AI
39
+
40
+ **Example: AI Working with SmartDomain**
41
+
42
+ When an AI needs to add a "suspend user" feature:
43
+
44
+ ```
45
+ Traditional monolithic approach:
46
+ - AI must understand: User model, callbacks, mailers, notifications, audit logs,
47
+ related models, 15+ files across different layers
48
+ - Context window: ~3000 lines of code
49
+
50
+ SmartDomain approach:
51
+ - AI focuses on: UserService (150 lines), UserSuspendedEvent (20 lines)
52
+ - Events automatically trigger audit, metrics, emails via handlers
53
+ - Context window: ~200 lines of code
54
+ - 93% reduction in context requirements
55
+ ```
56
+
57
+ **Benefits for Your Development Workflow**
58
+ - **Faster iterations** - AI assistants work with smaller, focused contexts
59
+ - **Better code quality** - Consistent patterns reduce hallucinations
60
+ - **Easier maintenance** - Clear boundaries make changes predictable
61
+ - **Natural collaboration** - AI and human developers work with the same mental model
62
+
63
+ SmartDomain isn't just better architecture—it's architecture optimized for the AI development era.
64
+
18
65
  ## Installation
19
66
 
20
67
  Add this line to your application's Gemfile:
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Event published when a <%= file_name %> is created
4
4
  class <%= class_name %>CreatedEvent < ApplicationEvent
5
- include ActiveDomain::Event::ActorMixin
5
+ include SmartDomain::Event::ActorMixin
6
6
 
7
7
  attribute :<%= file_name %>_id, :string
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Event published when a <%= file_name %> is deleted
4
4
  class <%= class_name %>DeletedEvent < ApplicationEvent
5
- include ActiveDomain::Event::ActorMixin
5
+ include SmartDomain::Event::ActorMixin
6
6
 
7
7
  attribute :<%= file_name %>_id, :string
8
8
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  # Event published when a <%= file_name %> is updated
4
4
  class <%= class_name %>UpdatedEvent < ApplicationEvent
5
- include ActiveDomain::Event::ActorMixin
6
- include ActiveDomain::Event::ChangeTrackingMixin
5
+ include SmartDomain::Event::ActorMixin
6
+ include SmartDomain::Event::ChangeTrackingMixin
7
7
 
8
8
  attribute :<%= file_name %>_id, :string
9
9
 
@@ -11,12 +11,12 @@ module <%= domain_module_name %>
11
11
  #
12
12
  # @param attributes [Hash] <%= class_name %> attributes
13
13
  # @return [<%= class_name %>] Created <%= file_name %>
14
- # @raise [ActiveDomain::Domain::AlreadyExistsError] If <%= file_name %> already exists
15
- # @raise [ActiveDomain::Domain::ValidationError] If validation fails
14
+ # @raise [SmartDomain::Domain::AlreadyExistsError] If <%= file_name %> already exists
15
+ # @raise [SmartDomain::Domain::ValidationError] If validation fails
16
16
  def create_<%= file_name %>(attributes)
17
17
  # Example business rule validation
18
18
  # if <%= class_name %>.exists?(email: attributes[:email])
19
- # raise ActiveDomain::Domain::AlreadyExistsError.new('<%= class_name %>', 'email', attributes[:email])
19
+ # raise SmartDomain::Domain::AlreadyExistsError.new('<%= class_name %>', 'email', attributes[:email])
20
20
  # end
21
21
 
22
22
  <%= class_name %>.transaction do
@@ -43,7 +43,7 @@ module <%= domain_module_name %>
43
43
  # @param attributes [Hash] Attributes to update
44
44
  # @return [<%= class_name %>] Updated <%= file_name %>
45
45
  # @raise [ActiveRecord::RecordNotFound] If <%= file_name %> not found
46
- # @raise [ActiveDomain::Domain::UnauthorizedError] If not authorized
46
+ # @raise [SmartDomain::Domain::UnauthorizedError] If not authorized
47
47
  def update_<%= file_name %>(<%= file_name %>_id, attributes)
48
48
  <%= file_name %> = <%= class_name %>.find(<%= file_name %>_id)
49
49
 
@@ -65,7 +65,7 @@ module <%= domain_module_name %>
65
65
  # @param <%= file_name %>_id [Integer, String] <%= class_name %> ID
66
66
  # @return [Boolean] True if deleted
67
67
  # @raise [ActiveRecord::RecordNotFound] If <%= file_name %> not found
68
- # @raise [ActiveDomain::Domain::UnauthorizedError] If not authorized
68
+ # @raise [SmartDomain::Domain::UnauthorizedError] If not authorized
69
69
  def delete_<%= file_name %>(<%= file_name %>_id)
70
70
  <%= file_name %> = <%= class_name %>.find(<%= file_name %>_id)
71
71
 
@@ -3,12 +3,12 @@
3
3
  module <%= domain_module_name %>
4
4
  # Setup event handlers for <%= file_name %> domain
5
5
  #
6
- # This file is automatically loaded by ActiveDomain::Railtie
6
+ # This file is automatically loaded by SmartDomain::Railtie
7
7
  # when the Rails application starts.
8
8
  def self.setup!
9
9
  # Register standard handlers (audit and metrics)
10
10
  # This one line replaces ~50 lines of boilerplate!
11
- ActiveDomain::Event::Registration.register_standard_handlers(
11
+ SmartDomain::Event::Registration.register_standard_handlers(
12
12
  domain: '<%= file_name %>',
13
13
  events: %w[created updated deleted],
14
14
  include_audit: true,
@@ -19,8 +19,8 @@ module <%= domain_module_name %>
19
19
  # Example:
20
20
  #
21
21
  # email_handler = <%= class_name %>EmailHandler.new
22
- # ActiveDomain::Event.bus.subscribe('<%= file_name %>.created', email_handler)
23
- # ActiveDomain::Event.bus.subscribe('<%= file_name %>.updated', email_handler)
22
+ # SmartDomain::Event.bus.subscribe('<%= file_name %>.created', email_handler)
23
+ # SmartDomain::Event.bus.subscribe('<%= file_name %>.updated', email_handler)
24
24
 
25
25
  Rails.logger.info "[<%= domain_module_name %>] Domain setup complete"
26
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmartDomain
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_domain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rachid Al Maach
@@ -199,16 +199,6 @@ files:
199
199
  - examples/blog_app/.github/dependabot.yml
200
200
  - examples/blog_app/.github/workflows/ci.yml
201
201
  - examples/blog_app/.gitignore
202
- - examples/blog_app/.kamal/hooks/docker-setup.sample
203
- - examples/blog_app/.kamal/hooks/post-app-boot.sample
204
- - examples/blog_app/.kamal/hooks/post-deploy.sample
205
- - examples/blog_app/.kamal/hooks/post-proxy-reboot.sample
206
- - examples/blog_app/.kamal/hooks/pre-app-boot.sample
207
- - examples/blog_app/.kamal/hooks/pre-build.sample
208
- - examples/blog_app/.kamal/hooks/pre-connect.sample
209
- - examples/blog_app/.kamal/hooks/pre-deploy.sample
210
- - examples/blog_app/.kamal/hooks/pre-proxy-reboot.sample
211
- - examples/blog_app/.kamal/secrets
212
202
  - examples/blog_app/.rubocop.yml
213
203
  - examples/blog_app/.ruby-version
214
204
  - examples/blog_app/Dockerfile
@@ -271,7 +261,6 @@ files:
271
261
  - examples/blog_app/bin/docker-entrypoint
272
262
  - examples/blog_app/bin/importmap
273
263
  - examples/blog_app/bin/jobs
274
- - examples/blog_app/bin/kamal
275
264
  - examples/blog_app/bin/rails
276
265
  - examples/blog_app/bin/rake
277
266
  - examples/blog_app/bin/rubocop
@@ -286,17 +275,16 @@ files:
286
275
  - examples/blog_app/config/ci.rb
287
276
  - examples/blog_app/config/credentials.yml.enc
288
277
  - examples/blog_app/config/database.yml
289
- - examples/blog_app/config/deploy.yml
290
278
  - examples/blog_app/config/environment.rb
291
279
  - examples/blog_app/config/environments/development.rb
292
280
  - examples/blog_app/config/environments/production.rb
293
281
  - examples/blog_app/config/environments/test.rb
294
282
  - examples/blog_app/config/importmap.rb
295
- - examples/blog_app/config/initializers/active_domain.rb
296
283
  - examples/blog_app/config/initializers/assets.rb
297
284
  - examples/blog_app/config/initializers/content_security_policy.rb
298
285
  - examples/blog_app/config/initializers/filter_parameter_logging.rb
299
286
  - examples/blog_app/config/initializers/inflections.rb
287
+ - examples/blog_app/config/initializers/smart_domain.rb
300
288
  - examples/blog_app/config/locales/en.yml
301
289
  - examples/blog_app/config/master.key
302
290
  - examples/blog_app/config/puma.rb
@@ -329,19 +317,19 @@ files:
329
317
  - examples/blog_app/tmp/.keep
330
318
  - examples/blog_app/vendor/.keep
331
319
  - examples/blog_app/vendor/javascript/.keep
332
- - lib/generators/active_domain/domain/domain_generator.rb
333
- - lib/generators/active_domain/domain/templates/events/created_event.rb.tt
334
- - lib/generators/active_domain/domain/templates/events/deleted_event.rb.tt
335
- - lib/generators/active_domain/domain/templates/events/updated_event.rb.tt
336
- - lib/generators/active_domain/domain/templates/policy.rb.tt
337
- - lib/generators/active_domain/domain/templates/service.rb.tt
338
- - lib/generators/active_domain/domain/templates/setup.rb.tt
339
- - lib/generators/active_domain/install/install_generator.rb
340
- - lib/generators/active_domain/install/templates/README
341
- - lib/generators/active_domain/install/templates/application_event.rb
342
- - lib/generators/active_domain/install/templates/application_policy.rb
343
- - lib/generators/active_domain/install/templates/application_service.rb
344
- - lib/generators/active_domain/install/templates/initializer.rb
320
+ - lib/generators/smart_domain/domain/domain_generator.rb
321
+ - lib/generators/smart_domain/domain/templates/events/created_event.rb.tt
322
+ - lib/generators/smart_domain/domain/templates/events/deleted_event.rb.tt
323
+ - lib/generators/smart_domain/domain/templates/events/updated_event.rb.tt
324
+ - lib/generators/smart_domain/domain/templates/policy.rb.tt
325
+ - lib/generators/smart_domain/domain/templates/service.rb.tt
326
+ - lib/generators/smart_domain/domain/templates/setup.rb.tt
327
+ - lib/generators/smart_domain/install/install_generator.rb
328
+ - lib/generators/smart_domain/install/templates/README
329
+ - lib/generators/smart_domain/install/templates/application_event.rb
330
+ - lib/generators/smart_domain/install/templates/application_policy.rb
331
+ - lib/generators/smart_domain/install/templates/application_service.rb
332
+ - lib/generators/smart_domain/install/templates/initializer.rb
345
333
  - lib/smart_domain.rb
346
334
  - lib/smart_domain/configuration.rb
347
335
  - lib/smart_domain/domain/exceptions.rb
@@ -361,6 +349,7 @@ files:
361
349
  - lib/smart_domain/railtie.rb
362
350
  - lib/smart_domain/tasks/domains.rake
363
351
  - lib/smart_domain/version.rb
352
+ - smart_domain-0.1.0.gem
364
353
  - smart_domain.gemspec
365
354
  homepage: https://github.com/rachid/smart_domain
366
355
  licenses:
@@ -1,3 +0,0 @@
1
- #!/bin/sh
2
-
3
- echo "Docker set up on $KAMAL_HOSTS..."
@@ -1,3 +0,0 @@
1
- #!/bin/sh
2
-
3
- echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -1,14 +0,0 @@
1
- #!/bin/sh
2
-
3
- # A sample post-deploy hook
4
- #
5
- # These environment variables are available:
6
- # KAMAL_RECORDED_AT
7
- # KAMAL_PERFORMER
8
- # KAMAL_VERSION
9
- # KAMAL_HOSTS
10
- # KAMAL_ROLES (if set)
11
- # KAMAL_DESTINATION (if set)
12
- # KAMAL_RUNTIME
13
-
14
- echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
@@ -1,3 +0,0 @@
1
- #!/bin/sh
2
-
3
- echo "Rebooted kamal-proxy on $KAMAL_HOSTS"
@@ -1,3 +0,0 @@
1
- #!/bin/sh
2
-
3
- echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -1,51 +0,0 @@
1
- #!/bin/sh
2
-
3
- # A sample pre-build hook
4
- #
5
- # Checks:
6
- # 1. We have a clean checkout
7
- # 2. A remote is configured
8
- # 3. The branch has been pushed to the remote
9
- # 4. The version we are deploying matches the remote
10
- #
11
- # These environment variables are available:
12
- # KAMAL_RECORDED_AT
13
- # KAMAL_PERFORMER
14
- # KAMAL_VERSION
15
- # KAMAL_HOSTS
16
- # KAMAL_ROLES (if set)
17
- # KAMAL_DESTINATION (if set)
18
-
19
- if [ -n "$(git status --porcelain)" ]; then
20
- echo "Git checkout is not clean, aborting..." >&2
21
- git status --porcelain >&2
22
- exit 1
23
- fi
24
-
25
- first_remote=$(git remote)
26
-
27
- if [ -z "$first_remote" ]; then
28
- echo "No git remote set, aborting..." >&2
29
- exit 1
30
- fi
31
-
32
- current_branch=$(git branch --show-current)
33
-
34
- if [ -z "$current_branch" ]; then
35
- echo "Not on a git branch, aborting..." >&2
36
- exit 1
37
- fi
38
-
39
- remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
40
-
41
- if [ -z "$remote_head" ]; then
42
- echo "Branch not pushed to remote, aborting..." >&2
43
- exit 1
44
- fi
45
-
46
- if [ "$KAMAL_VERSION" != "$remote_head" ]; then
47
- echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
48
- exit 1
49
- fi
50
-
51
- exit 0
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # A sample pre-connect check
4
- #
5
- # Warms DNS before connecting to hosts in parallel
6
- #
7
- # These environment variables are available:
8
- # KAMAL_RECORDED_AT
9
- # KAMAL_PERFORMER
10
- # KAMAL_VERSION
11
- # KAMAL_HOSTS
12
- # KAMAL_ROLES (if set)
13
- # KAMAL_DESTINATION (if set)
14
- # KAMAL_RUNTIME
15
-
16
- hosts = ENV["KAMAL_HOSTS"].split(",")
17
- results = nil
18
- max = 3
19
-
20
- elapsed = Benchmark.realtime do
21
- results = hosts.map do |host|
22
- Thread.new do
23
- tries = 1
24
-
25
- begin
26
- Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
27
- rescue SocketError
28
- if tries < max
29
- puts "Retrying DNS warmup: #{host}"
30
- tries += 1
31
- sleep rand
32
- retry
33
- else
34
- puts "DNS warmup failed: #{host}"
35
- host
36
- end
37
- end
38
-
39
- tries
40
- end
41
- end.map(&:value)
42
- end
43
-
44
- retries = results.sum - hosts.size
45
- nopes = results.count { |r| r == max }
46
-
47
- puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
@@ -1,122 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # A sample pre-deploy hook
4
- #
5
- # Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
6
- #
7
- # Fails unless the combined status is "success"
8
- #
9
- # These environment variables are available:
10
- # KAMAL_RECORDED_AT
11
- # KAMAL_PERFORMER
12
- # KAMAL_VERSION
13
- # KAMAL_HOSTS
14
- # KAMAL_COMMAND
15
- # KAMAL_SUBCOMMAND
16
- # KAMAL_ROLES (if set)
17
- # KAMAL_DESTINATION (if set)
18
-
19
- # Only check the build status for production deployments
20
- if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
21
- exit 0
22
- end
23
-
24
- require "bundler/inline"
25
-
26
- # true = install gems so this is fast on repeat invocations
27
- gemfile(true, quiet: true) do
28
- source "https://rubygems.org"
29
-
30
- gem "octokit"
31
- gem "faraday-retry"
32
- end
33
-
34
- MAX_ATTEMPTS = 72
35
- ATTEMPTS_GAP = 10
36
-
37
- def exit_with_error(message)
38
- $stderr.puts message
39
- exit 1
40
- end
41
-
42
- class GithubStatusChecks
43
- attr_reader :remote_url, :git_sha, :github_client, :combined_status
44
-
45
- def initialize
46
- @remote_url = github_repo_from_remote_url
47
- @git_sha = `git rev-parse HEAD`.strip
48
- @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
49
- refresh!
50
- end
51
-
52
- def refresh!
53
- @combined_status = github_client.combined_status(remote_url, git_sha)
54
- end
55
-
56
- def state
57
- combined_status[:state]
58
- end
59
-
60
- def first_status_url
61
- first_status = combined_status[:statuses].find { |status| status[:state] == state }
62
- first_status && first_status[:target_url]
63
- end
64
-
65
- def complete_count
66
- combined_status[:statuses].count { |status| status[:state] != "pending"}
67
- end
68
-
69
- def total_count
70
- combined_status[:statuses].count
71
- end
72
-
73
- def current_status
74
- if total_count > 0
75
- "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
76
- else
77
- "Build not started..."
78
- end
79
- end
80
-
81
- private
82
- def github_repo_from_remote_url
83
- url = `git config --get remote.origin.url`.strip.delete_suffix(".git")
84
- if url.start_with?("https://github.com/")
85
- url.delete_prefix("https://github.com/")
86
- elsif url.start_with?("git@github.com:")
87
- url.delete_prefix("git@github.com:")
88
- else
89
- url
90
- end
91
- end
92
- end
93
-
94
-
95
- $stdout.sync = true
96
-
97
- begin
98
- puts "Checking build status..."
99
-
100
- attempts = 0
101
- checks = GithubStatusChecks.new
102
-
103
- loop do
104
- case checks.state
105
- when "success"
106
- puts "Checks passed, see #{checks.first_status_url}"
107
- exit 0
108
- when "failure"
109
- exit_with_error "Checks failed, see #{checks.first_status_url}"
110
- when "pending"
111
- attempts += 1
112
- end
113
-
114
- exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
115
-
116
- puts checks.current_status
117
- sleep(ATTEMPTS_GAP)
118
- checks.refresh!
119
- end
120
- rescue Octokit::NotFound
121
- exit_with_error "Build status could not be found"
122
- end
@@ -1,3 +0,0 @@
1
- #!/bin/sh
2
-
3
- echo "Rebooting kamal-proxy on $KAMAL_HOSTS..."
@@ -1,20 +0,0 @@
1
- # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
2
- # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
3
- # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
4
-
5
- # Example of extracting secrets from 1password (or another compatible pw manager)
6
- # SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
7
- # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS})
8
- # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS})
9
-
10
- # Example of extracting secrets from Rails credentials
11
- # KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
12
-
13
- # Use a GITHUB_TOKEN if private repositories are needed for the image
14
- # GITHUB_TOKEN=$(gh config get -h github.com oauth_token)
15
-
16
- # Grab the registry password from ENV
17
- # KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
18
-
19
- # Improve security by using a password manager. Never check config/master.key into git!
20
- RAILS_MASTER_KEY=$(cat config/master.key)
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'kamal' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
-
13
- bundle_binstub = File.expand_path("bundle", __dir__)
14
-
15
- if File.file?(bundle_binstub)
16
- if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
- load(bundle_binstub)
18
- else
19
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
- end
22
- end
23
-
24
- require "rubygems"
25
- require "bundler/setup"
26
-
27
- load Gem.bin_path("kamal", "kamal")
@@ -1,120 +0,0 @@
1
- # Name of your application. Used to uniquely configure containers.
2
- service: blog_app
3
-
4
- # Name of the container image (use your-user/app-name on external registries).
5
- image: blog_app
6
-
7
- # Deploy to these servers.
8
- servers:
9
- web:
10
- - 192.168.0.1
11
- # job:
12
- # hosts:
13
- # - 192.168.0.1
14
- # cmd: bin/jobs
15
-
16
- # Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
17
- # If used with Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
18
- #
19
- # Using an SSL proxy like this requires turning on config.assume_ssl and config.force_ssl in production.rb!
20
- #
21
- # Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).
22
- #
23
- # proxy:
24
- # ssl: true
25
- # host: app.example.com
26
-
27
- # Where you keep your container images.
28
- registry:
29
- # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...
30
- server: localhost:5555
31
-
32
- # Needed for authenticated registries.
33
- # username: your-user
34
-
35
- # Always use an access token rather than real password when possible.
36
- # password:
37
- # - KAMAL_REGISTRY_PASSWORD
38
-
39
- # Inject ENV variables into containers (secrets come from .kamal/secrets).
40
- env:
41
- secret:
42
- - RAILS_MASTER_KEY
43
- clear:
44
- # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
45
- # When you start using multiple servers, you should split out job processing to a dedicated machine.
46
- SOLID_QUEUE_IN_PUMA: true
47
-
48
- # Set number of processes dedicated to Solid Queue (default: 1)
49
- # JOB_CONCURRENCY: 3
50
-
51
- # Set number of cores available to the application on each server (default: 1).
52
- # WEB_CONCURRENCY: 2
53
-
54
- # Match this to any external database server to configure Active Record correctly
55
- # Use blog_app-db for a db accessory server on same machine via local kamal docker network.
56
- # DB_HOST: 192.168.0.2
57
-
58
- # Log everything from Rails
59
- # RAILS_LOG_LEVEL: debug
60
-
61
- # Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
62
- # "bin/kamal logs -r job" will tail logs from the first server in the job section.
63
- aliases:
64
- console: app exec --interactive --reuse "bin/rails console"
65
- shell: app exec --interactive --reuse "bash"
66
- logs: app logs -f
67
- dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password"
68
-
69
- # Use a persistent storage volume for sqlite database files and local Active Storage files.
70
- # Recommended to change this to a mounted volume path that is backed up off server.
71
- volumes:
72
- - "blog_app_storage:/rails/storage"
73
-
74
- # Bridge fingerprinted assets, like JS and CSS, between versions to avoid
75
- # hitting 404 on in-flight requests. Combines all files from new and old
76
- # version inside the asset_path.
77
- asset_path: /rails/public/assets
78
-
79
-
80
- # Configure the image builder.
81
- builder:
82
- arch: amd64
83
-
84
- # # Build image via remote server (useful for faster amd64 builds on arm64 computers)
85
- # remote: ssh://docker@docker-builder-server
86
- #
87
- # # Pass arguments and secrets to the Docker build process
88
- # args:
89
- # RUBY_VERSION: 3.3.6
90
- # secrets:
91
- # - GITHUB_TOKEN
92
- # - RAILS_MASTER_KEY
93
-
94
- # Use a different ssh user than root
95
- # ssh:
96
- # user: app
97
-
98
- # Use accessory services (secrets come from .kamal/secrets).
99
- # accessories:
100
- # db:
101
- # image: mysql:8.0
102
- # host: 192.168.0.2
103
- # # Change to 3306 to expose port to the world instead of just local network.
104
- # port: "127.0.0.1:3306:3306"
105
- # env:
106
- # clear:
107
- # MYSQL_ROOT_HOST: '%'
108
- # secret:
109
- # - MYSQL_ROOT_PASSWORD
110
- # files:
111
- # - config/mysql/production.cnf:/etc/mysql/my.cnf
112
- # - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
113
- # directories:
114
- # - data:/var/lib/mysql
115
- # redis:
116
- # image: valkey/valkey:8
117
- # host: 192.168.0.2
118
- # port: 6379
119
- # directories:
120
- # - data:/data