Module: EasyCaddy::Caddy
- Defined in:
- lib/easy_caddy/caddy.rb
Constant Summary collapse
- BINARY =
'caddy'- LOG_FILE_MODE =
Group-writable mode for Caddy log files. A root-run Caddy (needed to bind :443/:80) creates logs the unprivileged user can’t open during ‘caddy validate`/`reload`; 0660 + macOS staff-group inheritance keeps them openable. See the log-permission fix.
'0660'- ADMIN_ENDPOINT =
'http://localhost:2019/pki/ca/local'
Class Method Summary collapse
- .admin_endpoint_reachable? ⇒ Boolean
- .brew_service_pid ⇒ Object
- .install_via_brew ⇒ Object
- .installed? ⇒ Boolean
- .process_pid ⇒ Object
-
.reload(caddyfile = Paths.caddyfile) ⇒ Object
rubocop:enable Metrics/MethodLength.
- .restart_service ⇒ Object
- .running? ⇒ Boolean
- .start_service ⇒ Object
-
.translate_validate_error(output) ⇒ Object
Caddy validate emits a wall of JSON log lines on stderr.
- .trust ⇒ Object
-
.trust_with_output ⇒ Array(String, Boolean)
Runs ‘caddy trust` and captures stdout+stderr so callers can inspect failures.
- .validate(caddyfile = Paths.caddyfile) ⇒ Object
- .validate!(caddyfile = Paths.caddyfile) ⇒ Object
-
.wait_for_admin_endpoint(timeout: 5) ⇒ Boolean
Polls Caddy’s admin API until it responds or the timeout elapses.
Class Method Details
.admin_endpoint_reachable? ⇒ Boolean
97 98 99 100 101 102 103 104 |
# File 'lib/easy_caddy/caddy.rb', line 97 def self.admin_endpoint_reachable? uri = URI(ADMIN_ENDPOINT) Net::HTTP.start(uri.host, uri.port, open_timeout: 1, read_timeout: 1) do |http| http.get(uri.request_uri).is_a?(Net::HTTPSuccess) end rescue StandardError false end |
.brew_service_pid ⇒ Object
119 120 121 122 123 |
# File 'lib/easy_caddy/caddy.rb', line 119 def self.brew_service_pid output = `brew services list 2>/dev/null | grep '^caddy '` m = output.match(/(\d+)/) m&.captures&.first&.to_i end |
.install_via_brew ⇒ Object
132 133 134 |
# File 'lib/easy_caddy/caddy.rb', line 132 def self.install_via_brew system('brew install caddy') end |
.installed? ⇒ Boolean
18 19 20 |
# File 'lib/easy_caddy/caddy.rb', line 18 def self.installed? system('which caddy > /dev/null 2>&1') end |
.process_pid ⇒ Object
125 126 127 128 129 130 |
# File 'lib/easy_caddy/caddy.rb', line 125 def self.process_pid out = `pgrep -f 'caddy run' 2>/dev/null`.strip return nil if out.empty? out.lines.first.to_i end |
.reload(caddyfile = Paths.caddyfile) ⇒ Object
rubocop:enable Metrics/MethodLength
59 60 61 62 63 64 65 66 67 |
# File 'lib/easy_caddy/caddy.rb', line 59 def self.reload(caddyfile = Paths.caddyfile) unless caddyfile.exist? warn ' [ecaddy] Skipping reload — global Caddyfile not found. Run `ecaddy setup` first.' return end out = `#{BINARY} reload --config #{caddyfile} 2>&1` raise Error, "Caddy reload failed:\n#{out}" unless $CHILD_STATUS.success? end |
.restart_service ⇒ Object
115 116 117 |
# File 'lib/easy_caddy/caddy.rb', line 115 def self.restart_service system('brew services restart caddy') end |
.running? ⇒ Boolean
106 107 108 109 |
# File 'lib/easy_caddy/caddy.rb', line 106 def self.running? pid = brew_service_pid pid && pid > 0 end |
.start_service ⇒ Object
111 112 113 |
# File 'lib/easy_caddy/caddy.rb', line 111 def self.start_service system('brew services start caddy') end |
.translate_validate_error(output) ⇒ Object
Caddy validate emits a wall of JSON log lines on stderr. Pull out the actual error and, for common cases (log file permission), turn it into an actionable message. rubocop:disable Metrics/MethodLength
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/easy_caddy/caddy.rb', line 39 def self.translate_validate_error(output) error_line = output.lines.find { |l| l.start_with?('Error:') }&.strip if error_line && error_line.match?(/setting up custom log.*permission denied/i) path = error_line[%r{open\s+(/\S+):\s*permission denied}, 1] hint = if path "Caddy runs as root and created this log 0600; validation runs as you and can't " \ "open it.\nFix it once with: sudo chmod #{LOG_FILE_MODE} #{path}\n" \ 'or run `ecaddy audit --fix` to do it interactively.' else 'Check ownership of the log file referenced above, or run `ecaddy audit --fix`.' end return "Caddy config invalid — log file not writable:\n #{error_line}\n\n#{hint}" end "Caddy config invalid:\n#{error_line || output}" end |
.trust ⇒ Object
69 70 71 |
# File 'lib/easy_caddy/caddy.rb', line 69 def self.trust system("#{BINARY} trust") end |
.trust_with_output ⇒ Array(String, Boolean)
Runs ‘caddy trust` and captures stdout+stderr so callers can inspect failures.
76 77 78 79 |
# File 'lib/easy_caddy/caddy.rb', line 76 def self.trust_with_output out = `#{BINARY} trust 2>&1` [out, $CHILD_STATUS.success?] end |
.validate(caddyfile = Paths.caddyfile) ⇒ Object
22 23 24 |
# File 'lib/easy_caddy/caddy.rb', line 22 def self.validate(caddyfile = Paths.caddyfile) system("#{BINARY} validate --config #{caddyfile} 2>&1") end |
.validate!(caddyfile = Paths.caddyfile) ⇒ Object
26 27 28 29 30 31 32 33 |
# File 'lib/easy_caddy/caddy.rb', line 26 def self.validate!(caddyfile = Paths.caddyfile) return unless caddyfile.exist? out = `#{BINARY} validate --config #{caddyfile} 2>&1` return if $CHILD_STATUS.success? raise Error, translate_validate_error(out) end |
.wait_for_admin_endpoint(timeout: 5) ⇒ Boolean
Polls Caddy’s admin API until it responds or the timeout elapses.
87 88 89 90 91 92 93 94 95 |
# File 'lib/easy_caddy/caddy.rb', line 87 def self.wait_for_admin_endpoint(timeout: 5) deadline = Time.now + timeout until Time.now > deadline return true if admin_endpoint_reachable? sleep 0.25 end false end |