rails_caddy_dev 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6731cf32e6419b5637b8a1764a06e045f6487af31ced9245e61b7c597d560774
4
+ data.tar.gz: '0898b5cc6acc282a396d581934c9f53a308d9387c1ac6027d3be9517b1ea65ad'
5
+ SHA512:
6
+ metadata.gz: e1e435b65fd2211860ae00652ea4c5557ae992c490810bcf3975864e965eef2cf98af9d22e8155e74220d8dc6fcfccfc4009eaa24b642bbbc2dca5eb36fc4f4a
7
+ data.tar.gz: ffffa2e702d9d2b30bb394b95965dc44e80ab134a9682ddefb6aeab39d781ee6168c7077db94c9deab802207aa967e96c546ebccb0a6e2455a70405650b074ec
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # RailsCaddyDev
2
+
3
+ Automatically configures [Caddy](https://caddyserver.com/) as a local reverse proxy for Rails development. When you start `rails server`, it registers routes via the Caddy admin API so that `<project_name>.localhost` proxies to your locally running Rails server with HTTPS.
4
+
5
+ ## Prerequisites
6
+
7
+ - [Caddy](https://caddyserver.com/) installed and running locally
8
+ - Ruby >= 3.1
9
+
10
+ ## Installation
11
+
12
+ Add it to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'rails_caddy_dev', group: :development
16
+ ```
17
+
18
+ Then run:
19
+
20
+ ```bash
21
+ bundle install
22
+ ```
23
+
24
+ ## Setup
25
+
26
+ In your application's `bin/rails` file, replace this:
27
+
28
+ ```ruby
29
+ require "rails/commands"
30
+ ```
31
+
32
+ with this:
33
+
34
+ ```ruby
35
+ require "rails_caddy_dev/commands"
36
+ ```
37
+
38
+ This will ensure your Caddy configuration is updated each time you start your Rails server in development.
39
+
40
+ ## Usage
41
+
42
+ Start your Rails server with the `DEVCADDY` and `PROJECT_NAME` environment variables:
43
+
44
+ ```bash
45
+ DEVCADDY=1 PROJECT_NAME=myapp rails server
46
+ ```
47
+
48
+ This will configure Caddy to proxy `myapp.localhost` to your Rails server, accessible over HTTPS.
49
+
50
+ ### Environment Variables
51
+
52
+ | Variable | Description | Default |
53
+ |---|---|---|
54
+ | `DEVCADDY` | Enables Caddy configuration (must be set) | — |
55
+ | `PROJECT_NAME` | Used to derive the `.localhost` hostname (required) | — |
56
+ | `PORT` | Rails server port to proxy to | `3000` |
57
+ | `CADDY_HOST` | Caddy admin API host | `localhost` |
58
+ | `CADDY_PORT` | Caddy admin API port | `2019` |
59
+
60
+ ### Dynamic Port Allocation
61
+
62
+ The gem also provides a CLI command to find an available TCP port, which can be useful if you want to run multiple Rails servers without port conflicts:
63
+
64
+ ```bash
65
+ export PORT=$(bundle exec rails rails_caddy_dev:available_port)
66
+ ```
67
+
68
+ RailsCaddyDev will then configure Caddy to proxy to the dynamically allocated port, and start your Rails server on that port.
69
+
70
+ ### Subdomains
71
+
72
+ Subdomains are also supported due to a wildcard Caddy route that will be configured. For example, if you set `PROJECT_NAME=myapp`, then `api.myapp.localhost` will also proxy to your Rails server.
73
+
74
+ ### Usage with Mise
75
+
76
+ If you're using [Mise](https://github.com/joelmoss/mise), you can integrate RailsCaddyDev by setting the `PORT` environment variable dynamically using the CLI command provided by RailsCaddyDev. Add this to your Mise config (eg. `mise.toml`):
77
+
78
+ ```toml
79
+ [env]
80
+ PORT = { value = "${env.PORT:-{{exec(command='bundle exec rails rails_caddy_dev:available_port')}}}", tools = true }
81
+ ```
82
+
83
+ Now the `PORT` environment variable will be set to an available port each time you start your Mise environment, allowing you to run multiple Rails servers without port conflicts.
84
+
85
+ ## Development
86
+
87
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
88
+
89
+ ## Contributing
90
+
91
+ Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/rails_caddy_dev.
92
+
93
+ ## License
94
+
95
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is responsible for loading the Caddy configuration update logic when the Rails server is
4
+ # started in development mode with the DEVCADDY environment variable set. It should be required from
5
+ # the application's 'bin/rails' file, as a replacement for the default 'require "rails/commands"'
6
+ # line, making it look something like this:
7
+ #
8
+ # #!/usr/bin/env ruby
9
+ #
10
+ # APP_PATH = File.expand_path("../config/application", __dir__)
11
+ # require_relative "../config/boot"
12
+ #
13
+ # # require "rails/commands" # <-- This line should be removed, and replaced with the line below.
14
+ # require "rails_caddy_dev/commands" # <-- This line is added to load the Caddy config logic
15
+ #
16
+ if ENV.fetch('RAILS_ENV', 'development') == 'development' &&
17
+ ENV.key?('DEVCADDY') &&
18
+ %w[s server].include?(ARGV.first)
19
+ require 'rails_caddy_dev/update_config'
20
+ end
21
+
22
+ require 'rails/commands'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module RailsCaddyDev
6
+ class Railtie < ::Rails::Engine
7
+ isolate_namespace RailsCaddyDev
8
+
9
+ rake_tasks do
10
+ load 'rails_caddy_dev/railties/available_port.rake'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :rails_caddy_dev do
4
+ desc 'Find and return an available TCP port on localhost'
5
+ task available_port: :environment do
6
+ require 'socket'
7
+ puts(Addrinfo.tcp('', 0).bind { |s| s.local_address.ip_port })
8
+ end
9
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Configures Caddy as a local reverse proxy to your Rails server during development.
4
+ #
5
+ # Uses the Caddy admin API to register a 'route' configuration that proxies requests for
6
+ # `<project_name>.localhost` to the locally running Rails server.
7
+ #
8
+ # This file is intended to be run as part of the `rails server` startup process, and will only
9
+ # execute in the development environment, and when the `DEVCADDY` environment variable is set. It
10
+ # should be required from the application's 'bin/rails' file., making it look something like this:
11
+ #
12
+ # #!/usr/bin/env ruby
13
+ #
14
+ # APP_PATH = File.expand_path("../config/application", __dir__)
15
+ # require_relative "../config/boot"
16
+ #
17
+ # if ENV['RAILS_ENV'] == 'development' && ENV.key?('DEVCADDY') &&
18
+ # (ARGV.first == 's' || ARGV.first == 'server')
19
+ # require 'rails_caddy_dev/update_config'
20
+ # end
21
+ #
22
+ # require "rails/commands"
23
+ #
24
+ #
25
+ # The script checks if a Caddy config for the project already exists by querying the admin API. If
26
+ # it does, it sends a PATCH request to update it; if not, it attempts to append a new route to the
27
+ # existing config or initialize a new config structure if necessary.
28
+ #
29
+ # The script expects the following environment variables:
30
+ # - PROJECT_NAME: Used to derive subdomain hostnames (e.g. "my_project" → my_project.localhost).
31
+ # This is required to ensure unique routing for each project.
32
+ # - PORT: Rails server port (default: 3000)
33
+ # - CADDY_HOST: Caddy admin API host (default: localhost)
34
+ # - CADDY_PORT: Caddy admin API port (default: 2019)
35
+ #
36
+ # If Caddy is not running or the API request fails, the script will print an error message and exit
37
+ # with status 1.
38
+
39
+ require 'net/http'
40
+ require 'json'
41
+
42
+ return if ENV.fetch('RAILS_ENV', 'development') != 'development' || !ENV.key?('DEVCADDY')
43
+
44
+ PORT = ENV.fetch('PORT', '3000')
45
+ CADDY_HOST = ENV.fetch('CADDY_HOST', 'localhost')
46
+ CADDY_PORT = ENV.fetch('CADDY_PORT', '2019').to_i
47
+ PROJECT_NAME = ENV.fetch('PROJECT_NAME')&.downcase
48
+
49
+ domains = [
50
+ "#{PROJECT_NAME}.localhost",
51
+ "admin.#{PROJECT_NAME}.localhost",
52
+ "clients.#{PROJECT_NAME}.localhost",
53
+ "therapists.#{PROJECT_NAME}.localhost"
54
+ ]
55
+
56
+ # Check if config already exists via Caddy API
57
+ uri = URI("http://#{CADDY_HOST}:#{CADDY_PORT}/id/#{PROJECT_NAME}")
58
+ config_exists = false
59
+ begin
60
+ response = Net::HTTP.get_response(uri)
61
+ config_exists = response.is_a?(Net::HTTPSuccess)
62
+ rescue Errno::ECONNREFUSED
63
+ puts "⚠️ Caddy admin API not available at #{CADDY_HOST}:#{CADDY_PORT}. Is caddy running?"
64
+ exit 1
65
+ end
66
+
67
+ config = {
68
+ '@id': PROJECT_NAME,
69
+ handle: [
70
+ {
71
+ handler: 'subroute',
72
+ routes: [
73
+ {
74
+ handle: [
75
+ {
76
+ handler: 'reverse_proxy',
77
+ upstreams: [
78
+ {
79
+ dial: ":#{PORT}"
80
+ }
81
+ ]
82
+ }
83
+ ]
84
+ }
85
+ ]
86
+ }
87
+ ],
88
+ match: [
89
+ {
90
+ host: domains
91
+ }
92
+ ],
93
+ terminal: true
94
+ }
95
+
96
+ # Caddy admin API
97
+ http = Net::HTTP.new(CADDY_HOST, CADDY_PORT)
98
+
99
+ if config_exists
100
+ # Update existing config via @id endpoint
101
+ request = Net::HTTP::Patch.new("/id/#{PROJECT_NAME}", 'Content-Type' => 'application/json')
102
+ request.body = config.to_json
103
+ action = 'updated'
104
+ else
105
+ # Check if routes path exists, if not initialize the full structure
106
+ routes_uri = URI("http://#{CADDY_HOST}:#{CADDY_PORT}/config/apps/http/servers/srv0/routes")
107
+ routes_response = Net::HTTP.get_response(routes_uri)
108
+
109
+ if routes_response.is_a?(Net::HTTPSuccess)
110
+ # Routes exist, append to them
111
+ request = Net::HTTP::Post.new(routes_uri.path, 'Content-Type' => 'application/json')
112
+ request.body = config.to_json
113
+ else
114
+ # Initialize full config structure
115
+ request = Net::HTTP::Post.new('/config/', 'Content-Type' => 'application/json')
116
+ request.body = {
117
+ apps: {
118
+ http: {
119
+ servers: {
120
+ srv0: {
121
+ listen: [':443'],
122
+ routes: [config]
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }.to_json
128
+ end
129
+ action = 'created'
130
+ end
131
+
132
+ response = http.request(request)
133
+
134
+ if response.is_a?(Net::HTTPSuccess)
135
+ puts "=> Caddy config for '#{PROJECT_NAME}:#{PORT}' #{action}"
136
+ else
137
+ puts "=! Failed to #{action.chomp('d')} Caddy config: #{response.body}"
138
+ exit 1
139
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsCaddyDev
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_caddy_dev/railtie'
4
+
5
+ module RailsCaddyDev
6
+ class Error < StandardError; end
7
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_caddy_dev
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joel Moss
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 7.1.0
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 7.1.0
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ email:
33
+ - joel@developwithstyle.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - README.md
39
+ - lib/rails_caddy_dev.rb
40
+ - lib/rails_caddy_dev/commands.rb
41
+ - lib/rails_caddy_dev/railtie.rb
42
+ - lib/rails_caddy_dev/railties/available_port.rake
43
+ - lib/rails_caddy_dev/update_config.rb
44
+ - lib/rails_caddy_dev/version.rb
45
+ homepage: https://github.com/joelmoss/rails_caddy_dev
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ rubygems_mfa_required: 'true'
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 3.1.0
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 4.0.3
65
+ specification_version: 4
66
+ summary: Automatic Caddy config for Rails development.
67
+ test_files: []