capistrano-cable 0.1.3 → 0.2.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +55 -5
- data/lib/capistrano/cable/bind.rb +27 -23
- data/lib/capistrano/cable/systemd.rb +30 -20
- data/lib/capistrano/cable/version.rb +1 -1
- data/lib/capistrano/tasks/systemd.rake +8 -0
- data/lib/capistrano/templates/cable.service.erb +1 -1
- data/lib/capistrano/templates/cable.socket.erb +7 -5
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b954ccd5824a22b8706c42c8e3935ad46d6bc57c2adf3a5ddb28bd75b1908b5a
|
|
4
|
+
data.tar.gz: c405063b580674e99877061c12476db5ee5300a23814adae59ba624797c45727
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c33e8732a80681cbbc2d2fbe6a0ddf3c509321a45be8ce6668bdd9ecacdd3c8eca8c7330c74500f6e7a684d478566a998a86d24eddd4c0b97072c9527bda024
|
|
7
|
+
data.tar.gz: 6712410d5ae24f5744e966bf800294784a72e9c998170105ce885e9a4381252fef2e511d3b146e7cb2630e6eec2e94d56d0382b722e59d6316ca736856aa31a0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-09-15
|
|
4
|
+
|
|
5
|
+
- Replace `cable_port`, `cable_ssl_certificate` and `cable_ssl_certificate_key` with a single `cable_bind` option, defaulting to a unix socket in the shared directory
|
|
6
|
+
- Enable systemd socket activation by default, so the listening socket survives a restart of the server
|
|
7
|
+
- Install the systemd units at every deploy, before restarting the server
|
|
8
|
+
- Stop the deploy on `deploy:starting` when a removed option is still set, instead of moving the server somewhere else in silence
|
|
9
|
+
|
|
3
10
|
## [0.1.0] - 2024-07-05
|
|
4
11
|
|
|
5
12
|
- Initial release
|
data/README.md
CHANGED
|
@@ -44,10 +44,8 @@ Many options are available to customize the cable server configuration. Here are
|
|
|
44
44
|
```ruby
|
|
45
45
|
# config/deploy.rb or config/deploy/<stage>.rb
|
|
46
46
|
set :cable_role, :web
|
|
47
|
-
set :
|
|
47
|
+
set :cable_bind, -> { "unix://#{shared_path.join("tmp", "sockets", "cable.sock")}" }
|
|
48
48
|
# set :cable_limit_nofile, 65536 # optional, to customize if `Errno::EMFILE: Too many open files` happens
|
|
49
|
-
# set :cable_ssl_certificate
|
|
50
|
-
# set :cable_ssl_certificate_key
|
|
51
49
|
set :cable_rackup_file, 'cable/config.ru'
|
|
52
50
|
set :cable_dir, -> { File.join(release_path, "cable") }
|
|
53
51
|
set :cable_pidfile, -> { File.join(shared_path, "tmp", "pids", "cable.pid") }
|
|
@@ -55,14 +53,66 @@ set :cable_env, -> { fetch(:rack_env, fetch(:rails_env, fetch(:stage))) }
|
|
|
55
53
|
set :cable_access_log, -> { File.join(shared_path, "log", "cable.access.log") }
|
|
56
54
|
set :cable_error_log, -> { File.join(shared_path, "log", "cable.error.log") }
|
|
57
55
|
set :cable_phased_restart, -> { true }
|
|
56
|
+
set :cable_enable_socket_service, true
|
|
58
57
|
set :cable_service_unit_env_files, -> { fetch(:service_unit_env_files, []) }
|
|
59
58
|
set :cable_service_unit_env_vars, -> { fetch(:service_unit_env_vars, []) }
|
|
60
59
|
set :cable_service_templates_path, fetch(:service_templates_path, "config/deploy/templates")
|
|
61
60
|
```
|
|
62
61
|
See Capistrao::Cable::Systemd#set_defaults for more details.
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
### Where the server listens
|
|
64
|
+
|
|
65
|
+
`cable_bind` is the only option about listening. It takes a Puma bind string, or
|
|
66
|
+
an array of them:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
set :cable_bind, "unix:///home/myapp/public_html/shared/tmp/sockets/cable.sock"
|
|
70
|
+
set :cable_bind, "tcp://0.0.0.0:28090"
|
|
71
|
+
set :cable_bind, "ssl://0.0.0.0:28090?cert=/path/cert.pem&key=/path/key.pem"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
By default the server listens on a unix socket in the shared directory, which
|
|
75
|
+
keeps it unreachable from the outside and saves picking a free port on every
|
|
76
|
+
host. The socket directory is created by `cable:install`; the web server in
|
|
77
|
+
front then proxies to that socket path instead of a host and port, the way it
|
|
78
|
+
proxies to any other unix socket.
|
|
79
|
+
|
|
80
|
+
A unix socket path has to be absolute: write `unix:///path/to/cable.sock`, with
|
|
81
|
+
three slashes.
|
|
82
|
+
|
|
83
|
+
### Socket activation
|
|
84
|
+
|
|
85
|
+
With `cable_enable_socket_service` (enabled by default), systemd owns the
|
|
86
|
+
listening socket and hands it over to Puma. The socket stays open while the
|
|
87
|
+
service restarts, so connections are queued in the backlog instead of being
|
|
88
|
+
refused, and the server is started on the first request if it is not running
|
|
89
|
+
yet.
|
|
90
|
+
|
|
91
|
+
The `ListenStream` of the socket unit is the address of each `cable_bind`: Puma
|
|
92
|
+
matches the socket it receives against its own binds, so both are always
|
|
93
|
+
written from the same option.
|
|
94
|
+
|
|
95
|
+
## Migrating to 0.2.0
|
|
96
|
+
|
|
97
|
+
`cable_port`, `cable_ssl_certificate` and `cable_ssl_certificate_key` are gone,
|
|
98
|
+
replaced by `cable_bind`. A deploy that still sets one of them stops on
|
|
99
|
+
`deploy:starting`, before anything is uploaded, with a message telling what to
|
|
100
|
+
write instead. `cable:install` refuses to run too, for setups that install the
|
|
101
|
+
plugin without its hooks.
|
|
102
|
+
|
|
103
|
+
Nothing else to do on the Capistrano side: the systemd units are now installed
|
|
104
|
+
at every deploy, before the server is restarted, so upgrading the gem and
|
|
105
|
+
deploying is enough to move an app to its socket.
|
|
106
|
+
|
|
107
|
+
The one manual step is the web server, which has to proxy to the socket instead
|
|
108
|
+
of the port. If both can't be changed in the same window, keep the port for
|
|
109
|
+
now:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
set :cable_bind, "tcp://0.0.0.0:28090"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
and move to the default socket in a later deploy.
|
|
66
116
|
|
|
67
117
|
## Development
|
|
68
118
|
|
|
@@ -1,34 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Capistrano
|
|
2
4
|
module Cable
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
# A Puma bind, as given to `set :cable_bind`:
|
|
6
|
+
#
|
|
7
|
+
# unix:///home/app/shared/tmp/sockets/cable.sock
|
|
8
|
+
# tcp://0.0.0.0:28090
|
|
9
|
+
# ssl://0.0.0.0:28090?cert=/path/cert.pem&key=/path/key.pem
|
|
10
|
+
#
|
|
11
|
+
# `address` is the part systemd needs for its `ListenStream`: Puma matches
|
|
12
|
+
# the socket handed over by systemd against its own binds by address, so
|
|
13
|
+
# both have to be written exactly the same way.
|
|
14
|
+
class Bind
|
|
15
|
+
SCHEMES = %w[tcp ssl unix].freeze
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
kind == :ssl
|
|
10
|
-
end
|
|
17
|
+
attr_reader :address
|
|
11
18
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
def initialize(bind)
|
|
20
|
+
@bind = bind.to_s
|
|
21
|
+
scheme, rest = @bind.split(":", 2)
|
|
22
|
+
@scheme = scheme
|
|
23
|
+
@address = rest.to_s.delete_prefix("//").split("?").first.to_s
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
else
|
|
20
|
-
self.class.new(
|
|
21
|
-
localize_address(full_address),
|
|
22
|
-
kind,
|
|
23
|
-
localize_address(address)
|
|
24
|
-
)
|
|
25
|
-
end
|
|
25
|
+
raise ArgumentError, "Unsupported cable_bind #{@bind.inspect}, expected #{SCHEMES.join("://, ")}://" unless SCHEMES.include?(@scheme)
|
|
26
|
+
raise ArgumentError, "Empty address in cable_bind #{@bind.inspect}" if @address.empty?
|
|
27
|
+
raise ArgumentError, "cable_bind #{@bind.inspect} needs an absolute socket path" if unix? && !@address.start_with?("/")
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
def unix?
|
|
31
|
+
@scheme == "unix"
|
|
32
|
+
end
|
|
29
33
|
|
|
30
|
-
def
|
|
31
|
-
|
|
34
|
+
def to_s
|
|
35
|
+
@bind
|
|
32
36
|
end
|
|
33
37
|
end
|
|
34
38
|
end
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
require "capistrano/plugin"
|
|
2
|
+
require "erb"
|
|
3
|
+
require "stringio"
|
|
2
4
|
require_relative "bind"
|
|
3
5
|
|
|
4
6
|
module Capistrano
|
|
5
7
|
module Cable
|
|
6
8
|
class Systemd < Capistrano::Plugin
|
|
9
|
+
# Options dropped in favour of :cable_bind. They are still looked up so
|
|
10
|
+
# that a stale setting stops the deploy before it does anything, instead
|
|
11
|
+
# of silently moving the server to another address.
|
|
12
|
+
REMOVED_OPTIONS = {
|
|
13
|
+
cable_port: 'set :cable_bind, "tcp://0.0.0.0:<port>"',
|
|
14
|
+
cable_ssl_certificate: 'set :cable_bind, "ssl://0.0.0.0:<port>?cert=<cert>&key=<key>"',
|
|
15
|
+
cable_ssl_certificate_key: 'set :cable_bind, "ssl://0.0.0.0:<port>?cert=<cert>&key=<key>"'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
7
18
|
def register_hooks
|
|
19
|
+
before "deploy:starting", "cable:check"
|
|
20
|
+
after "deploy:finished", "cable:install"
|
|
8
21
|
after "deploy:finished", "cable:smart_restart"
|
|
9
22
|
end
|
|
10
23
|
|
|
@@ -14,8 +27,8 @@ module Capistrano
|
|
|
14
27
|
|
|
15
28
|
def set_defaults
|
|
16
29
|
set_if_empty :cable_role, :web
|
|
17
|
-
set_if_empty :
|
|
18
|
-
set_if_empty :cable_rackup_file,
|
|
30
|
+
set_if_empty :cable_bind, -> { "unix://#{shared_path.join("tmp", "sockets", "cable.sock")}" }
|
|
31
|
+
set_if_empty :cable_rackup_file, "cable/config.ru"
|
|
19
32
|
set_if_empty :cable_dir, -> { File.join(release_path, "cable") }
|
|
20
33
|
set_if_empty :cable_pidfile, -> { File.join(shared_path, "tmp", "pids", "cable.pid") }
|
|
21
34
|
set_if_empty :cable_env, -> { fetch(:rack_env, fetch(:rails_env, fetch(:stage))) }
|
|
@@ -25,9 +38,8 @@ module Capistrano
|
|
|
25
38
|
|
|
26
39
|
set_if_empty :cable_systemctl_bin, -> { fetch(:systemctl_bin, "/bin/systemctl") }
|
|
27
40
|
set_if_empty :cable_service_unit_name, -> { "#{fetch(:application)}_cable_#{fetch(:stage)}" }
|
|
28
|
-
set_if_empty :cable_enable_socket_service,
|
|
41
|
+
set_if_empty :cable_enable_socket_service, true
|
|
29
42
|
set_if_empty :cable_socket_unit_name, -> { "#{fetch(:application)}_cable_#{fetch(:stage)}.socket" }
|
|
30
|
-
# set_if_empty :cable_bind, -> { "unix:/tmp/#{fetch(:app_domain)}.sock" }
|
|
31
43
|
|
|
32
44
|
set_if_empty :cable_service_unit_env_files, -> { fetch(:service_unit_env_files, []) }
|
|
33
45
|
set_if_empty :cable_service_unit_env_vars, -> { fetch(:service_unit_env_vars, []) }
|
|
@@ -47,6 +59,13 @@ module Capistrano
|
|
|
47
59
|
append :bundle_bins, "puma", "pumactl"
|
|
48
60
|
end
|
|
49
61
|
|
|
62
|
+
def check_removed_options!
|
|
63
|
+
messages = REMOVED_OPTIONS.select { |option, _| fetch(option) }.map do |option, replacement|
|
|
64
|
+
":#{option} is not supported anymore, use :cable_bind instead (#{replacement})"
|
|
65
|
+
end
|
|
66
|
+
raise ArgumentError, messages.join("\n") if messages.any?
|
|
67
|
+
end
|
|
68
|
+
|
|
50
69
|
def expanded_bundle_command
|
|
51
70
|
backend.capture(:echo, SSHKit.config.command_map[:bundle]).strip
|
|
52
71
|
end
|
|
@@ -72,7 +91,7 @@ module Capistrano
|
|
|
72
91
|
|
|
73
92
|
def sudo_if_needed(*command)
|
|
74
93
|
if fetch(:cable_systemctl_user) == :system
|
|
75
|
-
backend.sudo command.
|
|
94
|
+
backend.sudo command.join(" ")
|
|
76
95
|
else
|
|
77
96
|
backend.execute(*command)
|
|
78
97
|
end
|
|
@@ -103,12 +122,6 @@ module Capistrano
|
|
|
103
122
|
role.user
|
|
104
123
|
end
|
|
105
124
|
|
|
106
|
-
def cable_bind
|
|
107
|
-
Array(fetch(:cable_bind)).collect do |bind|
|
|
108
|
-
"bind '#{bind}'"
|
|
109
|
-
end.join("\n")
|
|
110
|
-
end
|
|
111
|
-
|
|
112
125
|
def service_unit_type
|
|
113
126
|
## Jruby don't support notify
|
|
114
127
|
return "simple" if RUBY_ENGINE == "jruby"
|
|
@@ -120,11 +133,7 @@ module Capistrano
|
|
|
120
133
|
def puma_options
|
|
121
134
|
options = []
|
|
122
135
|
options << "--no-config"
|
|
123
|
-
|
|
124
|
-
options << "--bind 'ssl://0.0.0.0:#{fetch(:cable_port)}?key=#{fetch(:cable_ssl_certificate_key)}&cert=#{fetch(:cable_ssl_certificate)}'"
|
|
125
|
-
else
|
|
126
|
-
options << "--port #{fetch(:cable_port)}"
|
|
127
|
-
end
|
|
136
|
+
cable_binds.each { |bind| options << "--bind '#{bind}'" }
|
|
128
137
|
options << "--environment #{fetch(:cable_env)}"
|
|
129
138
|
options << "--pidfile #{fetch(:cable_pidfile)}" if fetch(:cable_pidfile)
|
|
130
139
|
options << "--threads #{fetch(:cable_threads)}" if fetch(:cable_threads)
|
|
@@ -156,10 +165,11 @@ module Capistrano
|
|
|
156
165
|
end
|
|
157
166
|
|
|
158
167
|
def cable_binds
|
|
159
|
-
Array(fetch(:cable_bind)).map
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
168
|
+
Array(fetch(:cable_bind)).map { |bind| Bind.new(bind) }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def cable_socket_dirs
|
|
172
|
+
cable_binds.select(&:unix?).map { |bind| File.dirname(bind.address) }.uniq
|
|
163
173
|
end
|
|
164
174
|
end
|
|
165
175
|
end
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
git_plugin = self
|
|
4
4
|
|
|
5
5
|
namespace :cable do
|
|
6
|
+
desc "Check Cable configuration"
|
|
7
|
+
task :check do
|
|
8
|
+
git_plugin.check_removed_options!
|
|
9
|
+
end
|
|
10
|
+
|
|
6
11
|
desc "Install Cable systemd service"
|
|
7
12
|
task :install do
|
|
13
|
+
git_plugin.check_removed_options!
|
|
8
14
|
on roles(fetch(:cable_role)) do |role|
|
|
9
15
|
upload_compiled_template = lambda do |template_name, unit_filename|
|
|
10
16
|
git_plugin.upload_template_cable template_name, "#{fetch(:tmp_dir)}/#{unit_filename}", role
|
|
@@ -17,6 +23,8 @@ namespace :cable do
|
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
|
|
26
|
+
git_plugin.cable_socket_dirs.each { |dir| execute :mkdir, "-p", dir }
|
|
27
|
+
|
|
20
28
|
upload_compiled_template.call("cable.service", "#{fetch(:cable_service_unit_name)}.service")
|
|
21
29
|
|
|
22
30
|
if fetch(:cable_enable_socket_service)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[Unit]
|
|
13
13
|
Description=Cable HTTP Server for <%= "#{fetch(:application)} (#{fetch(:stage)})" %>
|
|
14
14
|
<%= "Requires=#{fetch(:cable_socket_unit_name)}" if fetch(:cable_enable_socket_service) %>
|
|
15
|
-
After=syslog.target network.target
|
|
15
|
+
After=syslog.target network.target<%= " #{fetch(:cable_socket_unit_name)}" if fetch(:cable_enable_socket_service) %>
|
|
16
16
|
|
|
17
17
|
[Service]
|
|
18
18
|
Type=<%= service_unit_type %>
|
|
@@ -3,17 +3,19 @@ Description=Cable Puma HTTP Server Accept Sockets for <%= "#{fetch(:application)
|
|
|
3
3
|
|
|
4
4
|
[Socket]
|
|
5
5
|
<% cable_binds.each do |bind| -%>
|
|
6
|
-
|
|
6
|
+
ListenStream=<%= bind.address %>
|
|
7
7
|
<% end -%>
|
|
8
8
|
|
|
9
9
|
# Don't let systemd accept the request, wait for Cable to do that.
|
|
10
10
|
# Systemd will start the cable service upon first request if it wasn't started.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# Systemd keeps the socket open across restarts of the service: connections are
|
|
13
|
+
# queued in the backlog instead of being refused while Cable boots.
|
|
14
|
+
#
|
|
15
|
+
# You might also want to give the web server in front a connection timeout large
|
|
16
|
+
# enough to accommodate your app's startup time.
|
|
14
17
|
Accept=no
|
|
15
|
-
<%= "NoDelay=true"
|
|
16
|
-
ReusePort=true
|
|
18
|
+
<%= "NoDelay=true" unless cable_binds.all?(&:unix?) %>
|
|
17
19
|
Backlog=1024
|
|
18
20
|
|
|
19
21
|
SyslogIdentifier=<%= fetch(:cable_socket_unit_name) %>
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capistrano-cable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brice TEXIER
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: capistrano
|
|
@@ -51,7 +50,6 @@ metadata:
|
|
|
51
50
|
homepage_uri: https://github.com/codeur/capistrano-cable#readme
|
|
52
51
|
source_code_uri: https://github.com/codeur/capistrano-cable
|
|
53
52
|
changelog_uri: https://github.com/codeur/capistrano-cable/releases
|
|
54
|
-
post_install_message:
|
|
55
53
|
rdoc_options: []
|
|
56
54
|
require_paths:
|
|
57
55
|
- lib
|
|
@@ -66,8 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
66
64
|
- !ruby/object:Gem::Version
|
|
67
65
|
version: '0'
|
|
68
66
|
requirements: []
|
|
69
|
-
rubygems_version:
|
|
70
|
-
signing_key:
|
|
67
|
+
rubygems_version: 4.0.16
|
|
71
68
|
specification_version: 4
|
|
72
69
|
summary: ActionCable integration for Capistrano
|
|
73
70
|
test_files: []
|