itsi 0.2.27.rc1 → 0.2.27

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: 6ea9fdbfbdd35894f2c5977d2624a4b90cb9467b3133d055d5ae87d1b267d5ff
4
- data.tar.gz: ad82474c56c170163d2fcc7e75c6e2351fbc61348e3fd8e60697acda4ffbe65a
3
+ metadata.gz: 96078f9c8cc05b5683900c02d3a662a7d0b2253ed2292f8aafcda8915bb823ae
4
+ data.tar.gz: 860a06e7fbb91b0d5f38a4cf8d18165e0ae4194fa159bfa551a203ab40cc8a76
5
5
  SHA512:
6
- metadata.gz: 47615c9e033b2119fe5a1c5780ed9acb4731500667fdc42f75df7a9768f0f4feda2d6e80f04ff85a1a938e8832934704b20d81cea48b7d96a87406bae22511e4
7
- data.tar.gz: 33ccb5b47c880ab500dc0551fbb853a8e19a5afd95cd3c8a5e4fc64b3895a602a68e8aaaeaa3b1133ad4bf56e968a4821efc71798b2c46b8d62705a790de78cd
6
+ metadata.gz: 72598619d4fea4bcb177dcb4cb16ec04a31388d066e33cf61fefcb1cdeccd0efde3ef97bd23392065d59ff30912f551c015fb70da67560c6a5ed073c549ca6d8
7
+ data.tar.gz: 42b120ce03f36f81d89814243ccfe6cd634a1202df6563d9da2a87529548a191e03156144267ddd9e0aba25f17918db936f5a7dd9d8e03e60a602c78d8aea103
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.2.27] - 2026-06-18
2
+ - Add `HTTP-01` ACME validation support alongside `TLS-ALPN-01`, including fallback-friendly deployments behind proxies and CDNs when an HTTP listener is reachable.
3
+ - Add runtime TLS domain registration APIs so Ruby code can enroll and remove ACME-managed domains without restarting Itsi.
4
+ - Expand ACME coverage with Pebble-backed end-to-end tests for direct issuance, HTTP fallback, and dynamic runtime issuance flows.
5
+ - Tighten scheduler compatibility around real Ruby fiber-scheduler hooks including DNS resolution, sleeps/timeouts, nested scheduling, and process waiting.
6
+ - Refresh release documentation for native gem packaging, ACME behavior, and local ACME testing.
7
+
1
8
  ## [0.2.26] - 2026-05-13
2
9
  - Restore synchronized release source after the unsynchronized `0.2.24` / `0.2.25` pushes.
3
10
  - Restore the multi-platform native gem packaging workflow so the next release can ship prebuilt binaries again.
data/README.md CHANGED
@@ -29,12 +29,15 @@ Make sure you have Ruby installed! If not, look here:
29
29
  ### 2. Itsi
30
30
 
31
31
  > On Linux?
32
- You'll need at least `build-essential` and `libclang-dev` installed to build Itsi on Linux.
32
+ You'll need at least `build-essential` and `libclang-dev` installed to build Itsi on Linux if RubyGems falls back to a source build.
33
33
  E.g.
34
34
  ```bash
35
35
  apt-get install build-essential libclang-dev
36
36
  ```
37
37
 
38
+ > Precompiled native gems are published for common macOS and Linux targets on x86_64 and ARM64.
39
+ > On those platforms most installs can skip the local Rust toolchain entirely, while source gems remain available as a fallback.
40
+
38
41
  Then, install Itsi using `gem`:
39
42
  ```bash
40
43
  gem install itsi
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.27-rc1"
3
+ version = "0.2.27"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.2.27-rc1"
3
+ version = "0.2.27"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -11,7 +11,6 @@ use http::{request::Parts, Response, StatusCode};
11
11
  use http_body_util::BodyExt;
12
12
  use itsi_error::CLIENT_CONNECTION_CLOSED;
13
13
  use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
14
- use itsi_tracing::debug;
15
14
  use magnus::{
16
15
  block::Proc,
17
16
  error::{ErrorType, Result as MagnusResult},
@@ -72,6 +72,17 @@ pub enum ResponseFrame {
72
72
  }
73
73
 
74
74
  impl ItsiHttpResponse {
75
+ fn is_expected_bridge_shutdown(err: &io::Error) -> bool {
76
+ matches!(
77
+ err.kind(),
78
+ io::ErrorKind::BrokenPipe
79
+ | io::ErrorKind::ConnectionAborted
80
+ | io::ErrorKind::ConnectionReset
81
+ | io::ErrorKind::NotConnected
82
+ | io::ErrorKind::UnexpectedEof
83
+ )
84
+ }
85
+
75
86
  pub fn new(
76
87
  parts: Arc<Parts>,
77
88
  response_sender: OneshotSender<ResponseFrame>,
@@ -98,13 +109,17 @@ impl ItsiHttpResponse {
98
109
  let (mut cr, mut cw) = tokio::io::split(client_io);
99
110
 
100
111
  let to_ruby = tokio::spawn(async move {
101
- if let Err(e) = tokio::io::copy(&mut cr, &mut lw).await {
102
- eprintln!("Error copying upgraded->local: {:?}", e);
112
+ if let Err(err) = tokio::io::copy(&mut cr, &mut lw).await {
113
+ if !Self::is_expected_bridge_shutdown(&err) {
114
+ eprintln!("Error copying upgraded->local: {:?}", err);
115
+ }
103
116
  }
104
117
  });
105
118
  let from_ruby = tokio::spawn(async move {
106
- if let Err(e) = tokio::io::copy(&mut lr, &mut cw).await {
107
- eprintln!("Error copying upgraded->local: {:?}", e);
119
+ if let Err(err) = tokio::io::copy(&mut lr, &mut cw).await {
120
+ if !Self::is_expected_bridge_shutdown(&err) {
121
+ eprintln!("Error copying local->upgraded: {:?}", err);
122
+ }
108
123
  }
109
124
  });
110
125
 
@@ -63,7 +63,7 @@ pub fn send_lifecycle_event(event: LifecycleEvent) {
63
63
  }
64
64
  }
65
65
 
66
- fn receive_signal(signum: i32, _: sighandler_t) {
66
+ extern "C" fn receive_signal(signum: i32) {
67
67
  debug!("Received signal: {}", signum);
68
68
  SIGINT_COUNT.fetch_add(-1, Ordering::SeqCst);
69
69
  let event = match signum {
@@ -96,20 +96,25 @@ fn receive_signal(signum: i32, _: sighandler_t) {
96
96
  }
97
97
  }
98
98
 
99
+ fn signal_handler() -> sighandler_t {
100
+ receive_signal as *const () as sighandler_t
101
+ }
102
+
99
103
  pub fn reset_signal_handlers() -> bool {
100
104
  debug!("Resetting signal handlers");
101
105
  SIGINT_COUNT.store(0, Ordering::SeqCst);
102
106
  SHUTDOWN_REQUESTED.store(false, Ordering::SeqCst);
103
107
 
104
108
  unsafe {
105
- libc::signal(libc::SIGTERM, receive_signal as usize);
106
- libc::signal(libc::SIGINT, receive_signal as usize);
107
- libc::signal(libc::SIGUSR2, receive_signal as usize);
108
- libc::signal(libc::SIGUSR1, receive_signal as usize);
109
- libc::signal(libc::SIGHUP, receive_signal as usize);
110
- libc::signal(libc::SIGTTIN, receive_signal as usize);
111
- libc::signal(libc::SIGTTOU, receive_signal as usize);
112
- libc::signal(libc::SIGCHLD, receive_signal as usize);
109
+ let handler = signal_handler();
110
+ libc::signal(libc::SIGTERM, handler);
111
+ libc::signal(libc::SIGINT, handler);
112
+ libc::signal(libc::SIGUSR2, handler);
113
+ libc::signal(libc::SIGUSR1, handler);
114
+ libc::signal(libc::SIGHUP, handler);
115
+ libc::signal(libc::SIGTTIN, handler);
116
+ libc::signal(libc::SIGTTOU, handler);
117
+ libc::signal(libc::SIGCHLD, handler);
113
118
  }
114
119
  true
115
120
  }
@@ -145,6 +145,8 @@ Itsi also comes bundled with a passfile generator, to help you manage your passw
145
145
  * Automated provisioning of Let's Encrypt certificates.
146
146
  * File system caching of certificate data to avoid excessive API calls.
147
147
  * Supports usage of subject alternative names (SANs) for certificates that span multiple domains/sub-domains.
148
+ * Supports direct `TLS-ALPN-01` validation and `HTTP-01` validation when a reachable HTTP listener is available.
149
+ * Supports runtime domain registration for live certificate issuance without restarting the server.
148
150
  * See <a target="_blank" href="/options/certificates#production-certificates-lets-encrypt">certificates</a>.
149
151
  {{% /details %}}
150
152
 
@@ -225,6 +227,7 @@ Note: This is not the same as <a target="_blank" href="https://grpc.io/blog/grpc
225
227
  {{% details title="Non-blocking(Fiber Scheduler) Mode" closed="true" %}}
226
228
  * Support for Ruby’s fiber scheduler for non-blocking concurrency, boosting performance during I/O operations.
227
229
  * Use Itsi's own high-performance built-in <a target="_blank" href="/itsi_scheduler">Fiber Scheduler</a> or if your prefer you can bring your own!
230
+ * Current native gems are built and tested across common Linux and macOS x86_64 and ARM64 targets.
228
231
  * See <a target="_blank" href="/options/fiber_scheduler">fiber_scheduler</a>.
229
232
  {{% /details %}}
230
233
 
@@ -20,7 +20,8 @@ Install Ruby
20
20
  **Prerequisites**
21
21
 
22
22
 
23
- You'll need at least a C/C++ build environment and `clang` and `curl` (for running `rustup`) installed.
23
+ Most Linux installs on common x86_64 and ARM64 targets can use the precompiled native gems directly.
24
+ If RubyGems falls back to the source gems, you'll need a C/C++ build environment plus `clang` and `curl` (for running `rustup`).
24
25
 
25
26
  #### For Ubuntu / Debian:
26
27
  ```bash
@@ -63,6 +64,9 @@ Then use `gem` to install Itsi, or its components based on your Ruby version.
63
64
  {{< /tab >}}
64
65
  {{< tab >}}
65
66
  **Mac**:
67
+ Most macOS installs on Apple Silicon and Intel Macs can use the precompiled native gems directly.
68
+ If RubyGems falls back to the source gems, install Xcode Command Line Tools first.
69
+
66
70
  **For Ruby >= 3.0**:
67
71
  ```bash
68
72
  gem install itsi
@@ -88,7 +92,7 @@ Then use `gem` to install Itsi, or its components based on your Ruby version.
88
92
  {{< tab >}}
89
93
  **FreeBSD**
90
94
 
91
- On FreeBSD you'll need to install a few build tools manually:
95
+ FreeBSD currently relies on source builds, so you'll need to install a few build tools manually:
92
96
  ```bash
93
97
  pkg install gmake cmake curl llvm
94
98
  ```
@@ -75,6 +75,19 @@ Itsi will print informative error message if config validation fails. E.g.
75
75
  | ^^^
76
76
  ```
77
77
 
78
+ ## Local ACME Testing
79
+ Itsi includes end-to-end ACME tests that exercise both `TLS-ALPN-01` and `HTTP-01` flows against a local [Pebble](https://github.com/letsencrypt/pebble) certificate authority.
80
+
81
+ The test helper installs Pebble on demand using the Go toolchain, so the only prerequisite is a working `go` binary on your `PATH`.
82
+
83
+ Once Go is available, the local ACME tests can be run as part of the normal server suite:
84
+
85
+ ```bash
86
+ rake server:test
87
+ ```
88
+
89
+ If Go is not installed, those specific ACME integration tests will be skipped rather than failing the whole suite.
90
+
78
91
  ## Shell Completions
79
92
  Itsi can also help you install shell completions, which are useful if you find yourself using the `itsi` executable a lot and forgetting the commands.
80
93
  Add the following line to the bottom of your ~/.bashrc or ~/.zshrc file:
@@ -16,6 +16,9 @@ When combined with Itsi Server, you can write endpoints that look just like regu
16
16
  If you're purely after a lightweight, yet efficient Ruby scheduler,
17
17
  you can use Itsi Scheduler as a standalone scheduler for any Ruby application.
18
18
 
19
+ The current release line is packaged as precompiled native gems for common macOS and Linux x86_64 and ARM64 targets, with source builds still available as a fallback.
20
+ Recent scheduler work has also tightened compatibility around the Ruby scheduler hooks that matter in practice: network I/O, DNS resolution, sleep/timeouts, nested scheduling, and process waiting.
21
+
19
22
  Just use `Fiber.set_scheduler` to set an instance `Itsi::Scheduler` as a scheduler to opt in to this IO weaving behavior
20
23
  *automatically* for all blocking IO.
21
24
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- itsi-scheduler (0.2.27.rc1)
4
+ itsi-scheduler (0.2.27)
5
5
  rb_sys (~> 0.9.91)
6
6
 
7
7
  GEM
@@ -86,7 +86,7 @@ CHECKSUMS
86
86
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
87
87
  drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
88
88
  i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
89
- itsi-scheduler (0.2.27.rc1)
89
+ itsi-scheduler (0.2.27)
90
90
  json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
91
91
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
92
92
  minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Scheduler
5
- VERSION = "0.2.27.rc1"
5
+ VERSION = "0.2.27"
6
6
  end
7
7
  end
data/gems/server/Gemfile CHANGED
@@ -19,6 +19,7 @@ group :server_test do
19
19
  end
20
20
 
21
21
  group :server_test_support do
22
+ gem "async-websocket"
22
23
  gem "jwt"
23
24
  gem "net_http_unix"
24
25
  gem "redis"
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: ../scheduler
3
3
  specs:
4
- itsi-scheduler (0.2.27.rc1)
4
+ itsi-scheduler (0.2.27)
5
5
  rb_sys (~> 0.9.91)
6
6
 
7
7
  PATH
8
8
  remote: .
9
9
  specs:
10
- itsi-server (0.2.27.rc1)
10
+ itsi-server (0.2.27)
11
11
  json (~> 2)
12
12
  prism (~> 1.4)
13
13
  rack (>= 1.6)
@@ -17,10 +17,42 @@ GEM
17
17
  remote: https://rubygems.org/
18
18
  specs:
19
19
  ansi (1.6.0)
20
+ async (2.39.0)
21
+ console (~> 1.29)
22
+ fiber-annotation
23
+ io-event (~> 1.11)
24
+ metrics (~> 0.12)
25
+ traces (~> 0.18)
26
+ async-http (0.95.1)
27
+ async (>= 2.10.2)
28
+ async-pool (~> 0.11)
29
+ io-endpoint (~> 0.14)
30
+ io-stream (~> 0.6)
31
+ metrics (~> 0.12)
32
+ protocol-http (~> 0.62)
33
+ protocol-http1 (~> 0.39)
34
+ protocol-http2 (~> 0.26)
35
+ protocol-url (~> 0.2)
36
+ traces (~> 0.10)
37
+ async-pool (0.11.2)
38
+ async (>= 2.0)
39
+ async-websocket (0.30.1)
40
+ async-http (~> 0.76)
41
+ protocol-http (~> 0.34)
42
+ protocol-rack (~> 0.7)
43
+ protocol-websocket (~> 0.17)
20
44
  base64 (0.3.0)
21
45
  bigdecimal (4.1.2)
22
46
  builder (3.3.0)
23
47
  connection_pool (3.0.2)
48
+ console (1.36.0)
49
+ fiber-annotation
50
+ fiber-local (~> 1.1)
51
+ json
52
+ fiber-annotation (0.2.0)
53
+ fiber-local (1.1.0)
54
+ fiber-storage
55
+ fiber-storage (1.0.1)
24
56
  google-protobuf (4.30.2)
25
57
  bigdecimal
26
58
  rake (>= 13)
@@ -29,11 +61,15 @@ GEM
29
61
  grpc (1.78.0)
30
62
  google-protobuf (>= 3.25, < 5.0)
31
63
  googleapis-common-protos-types (~> 1.0)
64
+ io-endpoint (0.17.2)
65
+ io-event (1.16.2)
66
+ io-stream (0.13.1)
32
67
  json (2.19.4)
33
68
  jwt (3.1.2)
34
69
  base64
35
70
  language_server-protocol (3.17.0.5)
36
71
  logger (1.7.0)
72
+ metrics (0.15.0)
37
73
  minitest (5.27.0)
38
74
  minitest-reporters (1.8.0)
39
75
  ansi
@@ -42,6 +78,20 @@ GEM
42
78
  ruby-progressbar
43
79
  net_http_unix (0.2.2)
44
80
  prism (1.9.0)
81
+ protocol-hpack (1.5.1)
82
+ protocol-http (0.62.2)
83
+ protocol-http1 (0.39.0)
84
+ protocol-http (~> 0.62)
85
+ protocol-http2 (0.26.0)
86
+ protocol-hpack (~> 1.4)
87
+ protocol-http (~> 0.62)
88
+ protocol-rack (0.22.1)
89
+ io-stream (>= 0.10)
90
+ protocol-http (~> 0.58)
91
+ rack (>= 1.0)
92
+ protocol-url (0.4.0)
93
+ protocol-websocket (0.21.1)
94
+ protocol-http (~> 0.2)
45
95
  rack (3.2.6)
46
96
  rackup (2.3.1)
47
97
  rack (>= 3)
@@ -64,6 +114,7 @@ GEM
64
114
  prism (>= 1.2, < 2.0)
65
115
  rbs (>= 3, < 5)
66
116
  ruby-progressbar (1.13.0)
117
+ traces (0.18.2)
67
118
  tsort (0.2.0)
68
119
 
69
120
  PLATFORMS
@@ -71,6 +122,7 @@ PLATFORMS
71
122
  ruby
72
123
 
73
124
  DEPENDENCIES
125
+ async-websocket
74
126
  bundler
75
127
  grpc
76
128
  itsi-scheduler!
@@ -89,23 +141,42 @@ DEPENDENCIES
89
141
 
90
142
  CHECKSUMS
91
143
  ansi (1.6.0) sha256=ac9ea0c0ea8d32fb4e271348e609963ac78882f34b73836c2a02b3622e666658
144
+ async (2.39.0) sha256=df18730073f2bbb45788077dfa20cb365ecc1b9453969f44de6796b5191a00aa
145
+ async-http (0.95.1) sha256=0c3dd458c204c06d5c4b20b01bbec4794a1203db627fb2ce536e1799ec14786c
146
+ async-pool (0.11.2) sha256=0a43a17b02b04d9c451b7d12fafa9a50e55dc6dd00d4369aca00433f16a7e3ed
147
+ async-websocket (0.30.1) sha256=54bb8a8f184e4aa64434c7a78ecc55850a67a3d1dcd02e3ae787376e2c673936
92
148
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
93
149
  bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
94
150
  builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
95
151
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
152
+ console (1.36.0) sha256=45599ea906cf80a73d8941f03abf873fe66a6a954e0bac5bc1c01e2cdc406f07
153
+ fiber-annotation (0.2.0) sha256=7abfadf1d119f508867d4103bf231c0354d019cc39a5738945dec2edadaf6c03
154
+ fiber-local (1.1.0) sha256=c885f94f210fb9b05737de65d511136ea602e00c5105953748aa0f8793489f06
155
+ fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1
96
156
  google-protobuf (4.30.2) sha256=0f35168dbeeccf13d928acf6c128cfec17b9a826ae4505246a02c115f4ae16ed
97
157
  googleapis-common-protos-types (1.19.0) sha256=aecb76ca5326f8bcc47ab083259bbc4971d07e87f56808af7e210669d9765694
98
158
  grpc (1.78.0) sha256=7aaf47b6d7783fb0e7d40fc853d34e802d47aef7b1312862b6e719141b3527c9
99
- itsi-scheduler (0.2.27.rc1)
100
- itsi-server (0.2.27.rc1)
159
+ io-endpoint (0.17.2) sha256=3feaf766c116b35839c11fac68b6aaadc47887bb488902a57bf8e1d288fb3338
160
+ io-event (1.16.2) sha256=9f9cb0a96ea5c3850a672606c65f27bc96d7621399ef6196acbfe2be0cd1279c
161
+ io-stream (0.13.1) sha256=570d7c4dfb0fbd767480b4a222048a2be6d9b78febc1ec68258d0e0a4cde20de
162
+ itsi-scheduler (0.2.27)
163
+ itsi-server (0.2.27)
101
164
  json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
102
165
  jwt (3.1.2) sha256=af6991f19a6bb4060d618d9add7a66f0eeb005ac0bc017cd01f63b42e122d535
103
166
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
104
167
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
168
+ metrics (0.15.0) sha256=61ded5bac95118e995b1bc9ed4a5f19bc9814928a312a85b200abbdac9039072
105
169
  minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
106
170
  minitest-reporters (1.8.0) sha256=8ce5280fb73ad3178ae525454df169b6f28c1b38b1d088ea91815d3a370ba384
107
171
  net_http_unix (0.2.2) sha256=98aa0e1e7787f8383e8dd8ff60fc18062c2ddb2aadbc76e92cfb4f95d2c9d71d
108
172
  prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
173
+ protocol-hpack (1.5.1) sha256=6feca238b8078da1cd295677d6f306c6001af92d75fe0643d33e6956cbc3ad91
174
+ protocol-http (0.62.2) sha256=e1c1f2f56029c1af8c4e2b8a67d0d096c76620f3afd8d99d1dcd2f6b8ffa773b
175
+ protocol-http1 (0.39.0) sha256=e49b3f4cda6f5d94c76a323d2b7f6977cba3ebd082d2da437039594da77ad8eb
176
+ protocol-http2 (0.26.0) sha256=bac89cd78082b241ccd0cf7246f5160e4bb0c9c975fb4bf7deef5f88cc317486
177
+ protocol-rack (0.22.1) sha256=1185d245927ef9849a603700d6991ca353bc89724fbf98efa4a4333ed62a9fc3
178
+ protocol-url (0.4.0) sha256=64d4c03b6b51ad815ac6fdaf77a1d91e5baf9220d26becb846c5459dacdea9e1
179
+ protocol-websocket (0.21.1) sha256=34325e4325697f0956877e67784bcc838cfd51ebbf4f8e9e5201be292041ee61
109
180
  rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
110
181
  rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
111
182
  rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
@@ -117,7 +188,8 @@ CHECKSUMS
117
188
  redis-client (0.24.0) sha256=ee65ee39cb2c38608b734566167fd912384f3c1241f59075e22858f23a085dbb
118
189
  ruby-lsp (0.26.7) sha256=60a1199fc7e329348d63a2479854f94435725d833eeabf3d539b790185cbf21f
119
190
  ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
191
+ traces (0.18.2) sha256=80f1649cb4daace1d7174b81f3b3b7427af0b93047759ba349960cb8f315e214
120
192
  tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
121
193
 
122
194
  BUNDLED WITH
123
- 2.6.9
195
+ 2.6.9
@@ -54,12 +54,40 @@ Let's Encrypt enforces strict rate limits on production certificate generation.
54
54
  {{< /callout >}}
55
55
 
56
56
 
57
- {{< callout type="warn" >}}
58
- Currently only the TLS-ALPN-01 challenge type is supported for automated certificates.
59
- The HTTP-01 challenge is not *yet* supported. This means that, for e.g. if your server is sitting behind a CDN or reverse proxy that performs HTTPS termination, you will not be able to rely on the *automated* certificate generation for fully automated, verified e2e encryption.
60
-
61
- Instead you may wish to use:
62
- * [Self-signed](#development--self-signed) certificates
63
- * [Manually](#existing-certificates) install certificates
64
- * Use HTTP between the CDN and the server
65
- {{< /callout >}}
57
+ Itsi supports both ACME challenge types that matter for common deployments:
58
+
59
+ * `TLS-ALPN-01` is used when the certificate authority can reach Itsi directly on the HTTPS listener.
60
+ * `HTTP-01` can be used when you also expose a reachable HTTP listener for the same hostname. In real Let's Encrypt deployments this typically means port `80` must reach Itsi for `/.well-known/acme-challenge/*`.
61
+
62
+ This means setups behind a CDN, WAF, or TLS-terminating proxy can still use automated certificates, provided plain HTTP validation traffic is forwarded to Itsi.
63
+
64
+ E.g. a production configuration that allows HTTP-01 fallback might look like this:
65
+
66
+ ```ruby {filename=Itsi.rb}
67
+ bind "http://0.0.0.0:80"
68
+ bind "https://0.0.0.0:443?cert=acme&domains=example.com&acme_email=you@example.com"
69
+ ```
70
+
71
+ ## Dynamic Domain Registration
72
+ You can add or remove ACME-managed domains while Itsi is already running.
73
+
74
+ This is useful when hostnames are discovered dynamically by your Ruby application, or when you want to defer certificate issuance until a tenant, customer, or site is activated.
75
+
76
+ Runtime APIs:
77
+
78
+ * `Itsi::Server.tls_bindings`
79
+ * `Itsi::Server.tls_domains(listener_id = nil)`
80
+ * `Itsi::Server.tls_domain_statuses(listener_id = nil)`
81
+ * `Itsi::Server.register_tls_domain(domain, listener_id = nil)`
82
+ * `Itsi::Server.unregister_tls_domain(domain, listener_id = nil)`
83
+
84
+ Example:
85
+
86
+ ```ruby
87
+ Itsi::Server.register_tls_domain("customer-a.example.com")
88
+
89
+ status = Itsi::Server.tls_domain_statuses.find { |entry| entry["domain"] == "customer-a.example.com" }
90
+ puts status
91
+ ```
92
+
93
+ When using dynamic issuance with HTTP-01, the same requirement still applies: the domain being issued must be able to reach an Itsi-managed HTTP listener for the ACME challenge path.
@@ -7,6 +7,8 @@ This allows Itsi to process a very large number of IO heavy requests concurrentl
7
7
 
8
8
  Enabling Fiber Scheduler mode can drastically improve application performance if you perform large amounts of blocking IO operations.
9
9
 
10
+ Itsi's bundled scheduler is intended to be practical for real Ruby applications, not just toy socket examples. It integrates with the scheduler hooks used by modern Rubies for socket I/O, DNS lookups, sleeps and timeouts, and process waiting.
11
+
10
12
 
11
13
  ## Configuration File
12
14
  ```ruby {filename="Itsi.rb"}
@@ -37,6 +37,10 @@ fiber_scheduler nil
37
37
  # bind "https://itsi.fyi?cert=acme&acme_email=admin@itsi.fyi"
38
38
  # You can generate certificates for multiple domains at once, by passing a comma-separated list of domains
39
39
  # bind "https://0.0.0.0?domains=foo.itsi.fyi,bar.itsi.fyi&cert=acme&acme_email=admin@itsi.fyi"
40
+ # If HTTPS on 443 is not directly reachable, you can also expose an HTTP listener and
41
+ # Let's Encrypt will be able to validate using HTTP-01 instead.
42
+ # bind "http://0.0.0.0:80"
43
+ # bind "https://0.0.0.0:443?domains=foo.itsi.fyi&cert=acme&acme_email=admin@itsi.fyi"
40
44
  #
41
45
  # If you already have a certificate you can specify it using the cert and key parameters
42
46
  # bind "https://itsi.fyi?cert=/path/to/cert.pem&key=/path/to/key.pem"
@@ -103,7 +103,8 @@ module Itsi
103
103
  elsif body_streamer
104
104
  # If we're partially hijacked or returned a streaming body,
105
105
  # stream this response.
106
- body_streamer.call(response)
106
+ stream = status == 101 ? request.partial_hijack : response
107
+ body_streamer.call(stream)
107
108
 
108
109
  elsif body.is_a?(Array)
109
110
  if body.length == 1
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.2.27.rc1"
5
+ VERSION = "0.2.27"
6
6
  end
7
7
  end
@@ -1,4 +1,7 @@
1
1
  require_relative "../helpers/test_helper"
2
+ require "async/websocket/adapters/rack"
3
+ require "protocol/websocket/connection"
4
+ require "base64"
2
5
 
3
6
  class TestRackServer < Minitest::Test
4
7
  def test_that_it_has_a_version_number
@@ -145,6 +148,36 @@ class TestRackServer < Minitest::Test
145
148
  assert_operator read_arity, :!=, 0
146
149
  end
147
150
 
151
+ def test_async_websocket_rack_adapter_can_upgrade_and_echo_messages
152
+ callback_error = Queue.new
153
+
154
+ server(app_with_lint: lambda do |env|
155
+ Async::WebSocket::Adapters::Rack.open(env) do |connection|
156
+ begin
157
+ message = connection.read
158
+ connection.write(message)
159
+ connection.flush
160
+ rescue => e
161
+ callback_error << e
162
+ end
163
+ end || [200, { "content-type" => "text/plain" }, ["ok"]]
164
+ end) do |uri|
165
+ socket = websocket_upgrade_socket(uri)
166
+ connection = websocket_client_connection(socket)
167
+
168
+ connection.write("hello")
169
+ connection.flush
170
+
171
+ message = connection.read
172
+ assert_equal "hello", message.to_str
173
+ ensure
174
+ connection&.close rescue nil
175
+ socket&.close
176
+ end
177
+
178
+ raise callback_error.pop unless callback_error.empty?
179
+ end
180
+
148
181
  def test_enumerable_body
149
182
  server(app_with_lint: lambda do |env|
150
183
  [200, { "content-type" => "application/json" },
@@ -503,4 +536,36 @@ class TestRackServer < Minitest::Test
503
536
  assert_equal %w[a=b c=d], resp.get_fields("set-cookie")
504
537
  end
505
538
  end
539
+
540
+ private
541
+
542
+ def websocket_upgrade_socket(uri)
543
+ socket = TCPSocket.new(uri.host, uri.port)
544
+ socket.write(
545
+ "GET / HTTP/1.1\r\n" \
546
+ "Host: #{uri.host}:#{uri.port}\r\n" \
547
+ "Connection: Upgrade\r\n" \
548
+ "Upgrade: websocket\r\n" \
549
+ "Sec-WebSocket-Version: 13\r\n" \
550
+ "Sec-WebSocket-Key: #{Base64.strict_encode64(SecureRandom.random_bytes(16))}\r\n" \
551
+ "\r\n"
552
+ )
553
+ socket.flush
554
+
555
+ response = +""
556
+ until response.include?("\r\n\r\n")
557
+ chunk = socket.read(1)
558
+ raise EOFError, "Unexpected EOF during websocket handshake" unless chunk
559
+
560
+ response << chunk
561
+ end
562
+
563
+ assert_includes response, "101"
564
+ socket
565
+ end
566
+
567
+ def websocket_client_connection(socket)
568
+ framer = Protocol::WebSocket::Framer.new(socket)
569
+ Protocol::WebSocket::Connection.new(framer, mask: "\x01\x02\x03\x04")
570
+ end
506
571
  end
data/lib/itsi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Itsi
2
- VERSION = "0.2.27.rc1"
2
+ VERSION = "0.2.27"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itsi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.27.rc1
4
+ version: 0.2.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.2.27.rc1
18
+ version: 0.2.27
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.2.27.rc1
25
+ version: 0.2.27
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: itsi-server
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.2.27.rc1
32
+ version: 0.2.27
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.2.27.rc1
39
+ version: 0.2.27
40
40
  description: Wrapper Gem for both the Itsi server and the Itsi Fiber scheduler
41
41
  email:
42
42
  - wc@pico.net.nz