durable_streams-rails 0.5.0 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf65130284ea6d82ba8c5a96a6140d8d90b2a85644150d95e4cccc254e166da4
4
- data.tar.gz: a9351da1b24a5a717ad3717eed8faa19f6079c99ef3e9d8300214a1e31558e19
3
+ metadata.gz: 79f28df832bb82db8fe2c8bf0204cf5c6ffededd6755c153d2a00aba4ec7f611
4
+ data.tar.gz: 698ea2440a5422124be37cb82ee2f988ad2e34dd008c702943f30d60797b0510
5
5
  SHA512:
6
- metadata.gz: 48dce7b23beb7d4a5a8c7e37350d5790c25865f20cfebcd31bd80fad574c7179896fae4471289524556ea61836f739936c2e18fcc404fb7b810a4eee55a9f1c5
7
- data.tar.gz: e1518c09eeaeaf28a6a92279bac1bc06b962aee73d89b8ead82b7651c7a6910f1a5eb9faa7210d3a4e3f97e2bf030c97df4c736a230e46d9dcf554c8ffc18375
6
+ metadata.gz: 407c2bcb96115a2b19207a3a10d99992b09daa273a39c935ce87adde911df3bc38c7be8b5e5ad409d6313c366ad74607ad9abe3a32eee62a6acfb4b95ce5afd3
7
+ data.tar.gz: 4c91ddfb552668d981f16c355876daa08c616ed42988f4eea157f1848af5b386eb9a93ace96def84a25c7abc9582e3e7d04b24e053b5146e2d15498ec5b95d5a
@@ -4,31 +4,43 @@ require "erb"
4
4
  module DurableStreams
5
5
  module Rails
6
6
  class ServerConfig
7
+ LOG_FORMATS = %w[ console json ].freeze
8
+ LOG_LEVELS = %w[ INFO WARN ERROR DEBUG ].freeze
9
+ STORAGE_DIRECTIVES = %w[ data_dir max_file_handles long_poll_timeout sse_reconnect_interval ].freeze
10
+
7
11
  TEMPLATE = ERB.new(<<~'CADDYFILE', trim_mode: "-")
8
12
  {
9
13
  admin off
10
- <%- unless @config["domain"] -%>
14
+ <%- unless auto_https? -%>
11
15
  auto_https off
12
16
  <%- end -%>
13
17
  }
14
18
 
15
19
  <%= address %> {
20
+ <%- if tls_config -%>
21
+ tls <%= tls_config["cert"] %> <%= tls_config["key"] %>
22
+ <%- end -%>
16
23
  log {
17
24
  output stderr
18
25
  format <%= log_format %>
19
26
  level <%= log_level %>
20
27
  }
21
28
 
22
- route <%= route_path %> {
23
- forward_auth <%= auth["url"] %> {
24
- uri <%= auth["path"] %>
25
- <%- copy_headers.each do |header| -%>
26
- copy_headers <%= header %>
29
+ <%= route_block %>
30
+ }
31
+ <%- if internal -%>
32
+
33
+ :<%= internal["port"] %> {
34
+ <%- if internal["bind"] -%>
35
+ bind <%= internal["bind"] %>
27
36
  <%- end -%>
28
- }
29
- durable_streams<%= storage_block %>
37
+ @allowed remote_ip <%= internal_allowed_ips.join(" ") %>
38
+ handle @allowed {
39
+ <%= route_block(indent: 2) %>
30
40
  }
41
+ respond 403
31
42
  }
43
+ <%- end -%>
32
44
  CADDYFILE
33
45
 
34
46
  def self.generate(config_path, environment)
@@ -37,6 +49,7 @@ module DurableStreams
37
49
 
38
50
  def initialize(config_path, environment)
39
51
  @config = YAML.load_file(config_path, aliases: true).fetch(environment)
52
+ validate!
40
53
  end
41
54
 
42
55
  def generate
@@ -44,10 +57,77 @@ module DurableStreams
44
57
  end
45
58
 
46
59
  private
60
+ def validate!
61
+ validate_internal!
62
+ validate_log!
63
+ end
64
+
65
+ def validate_internal!
66
+ if internal && !internal_allowed_ips
67
+ raise ArgumentError, "internal.allowed_ips is required when internal is configured"
68
+ end
69
+ end
70
+
71
+ def validate_log!
72
+ if log_config.key?("format") && !LOG_FORMATS.include?(log_config["format"])
73
+ raise ArgumentError,
74
+ "log.format must be one of #{LOG_FORMATS.join(", ")}, got: #{log_config["format"].inspect}"
75
+ end
76
+
77
+ if log_config.key?("level") && !LOG_LEVELS.include?(log_config["level"])
78
+ raise ArgumentError,
79
+ "log.level must be one of #{LOG_LEVELS.join(", ")}, got: #{log_config["level"].inspect}"
80
+ end
81
+ end
82
+
83
+ # Template methods — ordered by invocation
84
+
85
+ def auto_https?
86
+ @config["domain"] && !tls_config
87
+ end
88
+
47
89
  def address
48
90
  @config["domain"] || ":#{@config.fetch("port", 4437)}"
49
91
  end
50
92
 
93
+ def tls_config
94
+ @config["tls"]
95
+ end
96
+
97
+ def log_format
98
+ log_config.fetch("format", "console")
99
+ end
100
+
101
+ def log_level
102
+ log_config.fetch("level", "INFO")
103
+ end
104
+
105
+ def log_config
106
+ @config.fetch("log", {})
107
+ end
108
+
109
+ def route_block(indent: 1)
110
+ t = "\t" * indent
111
+ lines = []
112
+ lines << "#{t}route #{route_path} {"
113
+ lines << "#{t}\tforward_auth #{auth["url"]} {"
114
+ lines << "#{t}\t\turi #{auth["path"]}"
115
+ copy_headers.each { |h| lines << "#{t}\t\tcopy_headers #{h}" }
116
+ lines << "#{t}\t}"
117
+
118
+ directives = storage_directives
119
+ if directives.any?
120
+ lines << "#{t}\tdurable_streams {"
121
+ directives.each { |d| lines << "#{t}\t\t#{d}" }
122
+ lines << "#{t}\t}"
123
+ else
124
+ lines << "#{t}\tdurable_streams"
125
+ end
126
+
127
+ lines << "#{t}}"
128
+ lines.join("\n")
129
+ end
130
+
51
131
  def route_path
52
132
  @config.fetch("route", "/v1/streams/*")
53
133
  end
@@ -60,18 +140,17 @@ module DurableStreams
60
140
  auth.fetch("copy_headers", [ "Cookie" ])
61
141
  end
62
142
 
63
- def log_format
64
- @config.fetch("log_format", "console")
143
+ def storage_directives
144
+ storage = @config.fetch("storage", {})
145
+ STORAGE_DIRECTIVES.filter_map { |key| "#{key} #{storage[key]}" if storage[key] }
65
146
  end
66
147
 
67
- def log_level
68
- @config.fetch("log_level", "INFO")
148
+ def internal
149
+ @config["internal"]
69
150
  end
70
151
 
71
- def storage_block
72
- if dir = @config.dig("storage", "data_dir")
73
- " {\n\t\t\tdata_dir #{dir}\n\t\t}"
74
- end
152
+ def internal_allowed_ips
153
+ internal&.dig("allowed_ips")
75
154
  end
76
155
  end
77
156
  end
@@ -1,5 +1,5 @@
1
1
  module DurableStreams
2
2
  module Rails
3
- VERSION = "0.5.0"
3
+ VERSION = "0.5.2"
4
4
  end
5
5
  end
@@ -6,7 +6,10 @@
6
6
 
7
7
  default: &default
8
8
  port: 4437
9
- # route: /v1/streams/*
9
+ # route: /v1/streams/* # URL path prefix for stream endpoints. Default: /v1/streams/*
10
+ # log:
11
+ # format: console # "console" (human-readable) or "json" (structured). Default: console
12
+ # level: INFO # INFO, WARN, or ERROR. Default: INFO
10
13
  auth:
11
14
  url: http://localhost:3000
12
15
  path: /durable_streams/auth/verify
@@ -22,6 +25,17 @@ test:
22
25
  production:
23
26
  <<: *default
24
27
  domain: streams.example.com
28
+ # tls: # Explicit TLS certs (disables ACME). Use with Cloudflare origin certs.
29
+ # cert: /etc/caddy/certs/cert.pem
30
+ # key: /etc/caddy/certs/key.pem
31
+ # internal: # Server-to-server listener (no TLS). Restrict to private network.
32
+ # port: 4437
33
+ # bind: 10.0.0.5 # Only listen on the private interface.
34
+ # allowed_ips: # Caddy remote_ip check — 403 for everyone else.
35
+ # - 10.0.0.4
36
+ # log:
37
+ # format: json
38
+ # level: WARN
25
39
  auth:
26
40
  url: https://app.example.com
27
41
  path: /durable_streams/auth/verify
@@ -30,3 +44,6 @@ production:
30
44
  - Authorization
31
45
  storage:
32
46
  data_dir: /var/data/durable-streams
47
+ # max_file_handles: 100 # Connection pool for segment files. Default: 100
48
+ # long_poll_timeout: 30s # How long to wait for new data before returning 204. Default: 30s
49
+ # sse_reconnect_interval: 60s # Server closes SSE after this interval for CDN collapsing. Default: 60s
@@ -1,4 +1,17 @@
1
1
  namespace :durable_streams do
2
+ desc "Generate Caddyfile from config/durable_streams.yml"
3
+ task caddyfile: :environment do
4
+ require "durable_streams/rails/server_config"
5
+
6
+ environment = ENV.fetch("RAILS_ENV", ::Rails.env)
7
+ config_path = ::Rails.root.join("config", "durable_streams.yml")
8
+ output_path = ::Rails.root.join("config", "caddy", "Caddyfile")
9
+
10
+ FileUtils.mkdir_p(output_path.dirname)
11
+ File.write(output_path, DurableStreams::Rails::ServerConfig.generate(config_path.to_s, environment))
12
+ puts "Generated #{output_path} (#{environment})"
13
+ end
14
+
2
15
  desc "Download the Durable Streams server binary for the current platform"
3
16
  task :download do
4
17
  require "net/http"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: durable_streams-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - tokimonki