consolle 0.2.6 → 0.2.7
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/.version +1 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +1 -1
- data/bin/cone +2 -2
- data/bin/consolle +2 -2
- data/consolle.gemspec +20 -20
- data/lib/consolle/adapters/rails_console.rb +82 -77
- data/lib/consolle/cli.rb +380 -255
- data/lib/consolle/server/console_socket_server.rb +79 -72
- data/lib/consolle/server/console_supervisor.rb +194 -174
- data/lib/consolle/server/request_broker.rb +96 -99
- data/lib/consolle/version.rb +2 -2
- data/lib/consolle.rb +6 -6
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b0fccfa081563dfbdbb310a8822ccca827150906aac1208e3b72fa754eb8ab6
|
|
4
|
+
data.tar.gz: 05ab3780cce3a62638540641146dae880f7cdbe75d5ede45ce71ba32433af2ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45c5bff6c8b43e6029fb1e4d0f12cc83ce2f5925f0882c8b628394868b52f119c92e4fcc6dccaae7190e72ebdf30cc71cc1b7aeb021f6c9fbaff127fd67b9524
|
|
7
|
+
data.tar.gz: e96701a1acea38c2c90c51e4c5df56ff9e038e48551b5d34c3d17d74b4c6ad0044a8123553e1ebf2000415654da192a5f60890924b08ab904d9ad13ee8728512
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.7
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/bin/cone
CHANGED
data/bin/consolle
CHANGED
data/consolle.gemspec
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'lib/consolle/version'
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = 'consolle'
|
|
7
7
|
spec.version = Consolle::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
8
|
+
spec.authors = ['nacyot']
|
|
9
|
+
spec.email = ['propellerheaven@gmail.com']
|
|
10
10
|
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.required_ruby_version =
|
|
15
|
-
spec.license
|
|
11
|
+
spec.summary = 'PTY-based Rails console management library'
|
|
12
|
+
spec.description = 'Consolle is a library that manages Rails console through PTY (Pseudo-Terminal). Moving away from the traditional eval-based execution method, it manages the actual Rails console process as a subprocess to provide a more stable and secure execution environment.'
|
|
13
|
+
spec.homepage = 'https://github.com/nacyot/consolle'
|
|
14
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
16
|
|
|
17
17
|
spec.metadata = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
'homepage_uri' => spec.homepage,
|
|
19
|
+
'source_code_uri' => 'https://github.com/nacyot/consolle',
|
|
20
|
+
'changelog_uri' => 'https://github.com/nacyot/consolle/blob/main/CHANGELOG.md',
|
|
21
|
+
'bug_tracker_uri' => 'https://github.com/nacyot/consolle/issues',
|
|
22
|
+
'rubygems_mfa_required' => 'true'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
# Specify which files should be added to the gem when it is released.
|
|
@@ -27,14 +27,14 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
28
28
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
29
29
|
end
|
|
30
|
-
spec.bindir =
|
|
30
|
+
spec.bindir = 'bin'
|
|
31
31
|
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
|
|
32
|
-
spec.require_paths = [
|
|
32
|
+
spec.require_paths = ['lib']
|
|
33
33
|
|
|
34
34
|
# Runtime dependencies
|
|
35
|
-
spec.add_dependency
|
|
36
|
-
spec.add_dependency
|
|
35
|
+
spec.add_dependency 'logger', '~> 1.0'
|
|
36
|
+
spec.add_dependency 'thor', '~> 1.0'
|
|
37
37
|
|
|
38
38
|
# Development dependencies
|
|
39
|
-
spec.add_development_dependency
|
|
40
|
-
end
|
|
39
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
40
|
+
end
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'timeout'
|
|
6
|
+
require 'securerandom'
|
|
7
|
+
require 'fileutils'
|
|
8
8
|
|
|
9
9
|
module Consolle
|
|
10
10
|
module Adapters
|
|
11
11
|
class RailsConsole
|
|
12
12
|
attr_reader :socket_path, :process_pid, :pid_path, :log_path
|
|
13
13
|
|
|
14
|
-
def initialize(socket_path: nil, pid_path: nil, log_path: nil, rails_root: nil, rails_env: nil, verbose: false,
|
|
14
|
+
def initialize(socket_path: nil, pid_path: nil, log_path: nil, rails_root: nil, rails_env: nil, verbose: false,
|
|
15
|
+
command: nil)
|
|
15
16
|
@socket_path = socket_path || default_socket_path
|
|
16
17
|
@pid_path = pid_path || default_pid_path
|
|
17
18
|
@log_path = log_path || default_log_path
|
|
18
19
|
@rails_root = rails_root || Dir.pwd
|
|
19
|
-
@rails_env = rails_env ||
|
|
20
|
+
@rails_env = rails_env || 'development'
|
|
20
21
|
@verbose = verbose
|
|
21
|
-
@command = command ||
|
|
22
|
+
@command = command || 'bin/rails console'
|
|
22
23
|
@server_pid = nil
|
|
23
24
|
end
|
|
24
25
|
|
|
@@ -27,14 +28,14 @@ module Consolle
|
|
|
27
28
|
|
|
28
29
|
# Start socket server daemon
|
|
29
30
|
start_server_daemon
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
# Wait for server to be ready
|
|
32
33
|
wait_for_server(timeout: 10)
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
# Get server status
|
|
35
36
|
status = get_status
|
|
36
|
-
@server_pid = status[
|
|
37
|
-
|
|
37
|
+
@server_pid = status['pid'] if status && status['success']
|
|
38
|
+
|
|
38
39
|
true
|
|
39
40
|
rescue StandardError => e
|
|
40
41
|
stop_server_daemon
|
|
@@ -57,10 +58,10 @@ module Consolle
|
|
|
57
58
|
|
|
58
59
|
def running?
|
|
59
60
|
return false unless File.exist?(@socket_path)
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
# Check if socket is responsive
|
|
62
63
|
status = get_status
|
|
63
|
-
status && status[
|
|
64
|
+
status && status['success'] && status['running']
|
|
64
65
|
rescue StandardError
|
|
65
66
|
false
|
|
66
67
|
end
|
|
@@ -69,50 +70,48 @@ module Consolle
|
|
|
69
70
|
# Return the server daemon PID from pid file
|
|
70
71
|
pid_file = @pid_path
|
|
71
72
|
return nil unless File.exist?(pid_file)
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
File.read(pid_file).strip.to_i
|
|
74
75
|
rescue StandardError
|
|
75
76
|
nil
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
def send_code(code, timeout: 15)
|
|
79
|
-
unless running?
|
|
80
|
-
raise RuntimeError, "Console is not running"
|
|
81
|
-
end
|
|
80
|
+
raise 'Console is not running' unless running?
|
|
82
81
|
|
|
83
82
|
request = {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
'action' => 'eval',
|
|
84
|
+
'code' => code,
|
|
85
|
+
'timeout' => timeout,
|
|
86
|
+
'request_id' => SecureRandom.uuid
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
response = send_request(request, timeout: timeout + 5)
|
|
91
|
-
|
|
90
|
+
|
|
92
91
|
# Format response for compatibility
|
|
93
|
-
if response[
|
|
92
|
+
if response['success']
|
|
94
93
|
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
'success' => true,
|
|
95
|
+
'result' => response['result'],
|
|
96
|
+
'execution_time' => response['execution_time'],
|
|
97
|
+
'request_id' => response['request_id']
|
|
99
98
|
}
|
|
100
99
|
else
|
|
101
100
|
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
'success' => false,
|
|
102
|
+
'error' => response['error'] || 'Unknown',
|
|
103
|
+
'message' => response['message'] || 'Unknown error',
|
|
104
|
+
'request_id' => response['request_id']
|
|
106
105
|
}
|
|
107
106
|
end
|
|
108
107
|
end
|
|
109
108
|
|
|
110
109
|
def get_status
|
|
111
110
|
request = {
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
'action' => 'status',
|
|
112
|
+
'request_id' => SecureRandom.uuid
|
|
114
113
|
}
|
|
115
|
-
|
|
114
|
+
|
|
116
115
|
send_request(request, timeout: 5)
|
|
117
116
|
rescue StandardError
|
|
118
117
|
nil
|
|
@@ -121,30 +120,30 @@ module Consolle
|
|
|
121
120
|
private
|
|
122
121
|
|
|
123
122
|
def default_socket_path
|
|
124
|
-
File.join(Dir.pwd,
|
|
123
|
+
File.join(Dir.pwd, 'tmp', 'cone', 'cone.socket')
|
|
125
124
|
end
|
|
126
125
|
|
|
127
126
|
def default_pid_path
|
|
128
|
-
File.join(Dir.pwd,
|
|
127
|
+
File.join(Dir.pwd, 'tmp', 'cone', 'cone.pid')
|
|
129
128
|
end
|
|
130
129
|
|
|
131
130
|
def default_log_path
|
|
132
|
-
File.join(Dir.pwd,
|
|
131
|
+
File.join(Dir.pwd, 'tmp', 'cone', 'cone.log')
|
|
133
132
|
end
|
|
134
133
|
|
|
135
134
|
def server_command
|
|
136
|
-
consolle_lib_path = File.expand_path(
|
|
137
|
-
|
|
135
|
+
consolle_lib_path = File.expand_path('../..', __dir__)
|
|
136
|
+
|
|
138
137
|
# Build server command
|
|
139
138
|
[
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
'ruby',
|
|
140
|
+
'-I', consolle_lib_path,
|
|
141
|
+
'-e', server_script,
|
|
142
|
+
'--',
|
|
144
143
|
@socket_path,
|
|
145
144
|
@rails_root,
|
|
146
145
|
@rails_env,
|
|
147
|
-
@verbose ?
|
|
146
|
+
@verbose ? 'debug' : 'info',
|
|
148
147
|
@pid_path,
|
|
149
148
|
@log_path,
|
|
150
149
|
@command
|
|
@@ -156,31 +155,31 @@ module Consolle
|
|
|
156
155
|
begin
|
|
157
156
|
require 'consolle/server/console_socket_server'
|
|
158
157
|
require 'logger'
|
|
159
|
-
|
|
158
|
+
#{' '}
|
|
160
159
|
socket_path, rails_root, rails_env, log_level, pid_path, log_path, command = ARGV
|
|
161
|
-
|
|
160
|
+
#{' '}
|
|
162
161
|
# Write initial log
|
|
163
162
|
log_file = log_path || socket_path.sub(/\\.socket$/, '.log')
|
|
164
163
|
File.open(log_file, 'a') { |f| f.puts "[Server] Starting... PID: \#{Process.pid}" }
|
|
165
|
-
|
|
164
|
+
#{' '}
|
|
166
165
|
# Daemonize
|
|
167
166
|
Process.daemon(true, false)
|
|
168
|
-
|
|
167
|
+
#{' '}
|
|
169
168
|
# Redirect output
|
|
170
169
|
$stdout.reopen(log_file, 'a')
|
|
171
170
|
$stderr.reopen(log_file, 'a')
|
|
172
171
|
$stdout.sync = $stderr.sync = true
|
|
173
|
-
|
|
172
|
+
#{' '}
|
|
174
173
|
# Write PID file
|
|
175
174
|
pid_file = pid_path || socket_path.sub(/\\.socket$/, '.pid')
|
|
176
175
|
File.write(pid_file, Process.pid.to_s)
|
|
177
|
-
|
|
176
|
+
#{' '}
|
|
178
177
|
puts "[Server] Daemon started, PID: \#{Process.pid}"
|
|
179
|
-
|
|
178
|
+
#{' '}
|
|
180
179
|
# Create logger with appropriate level
|
|
181
180
|
logger = Logger.new(log_file)
|
|
182
181
|
logger.level = (log_level == 'debug') ? Logger::DEBUG : Logger::INFO
|
|
183
|
-
|
|
182
|
+
#{' '}
|
|
184
183
|
# Start server
|
|
185
184
|
server = Consolle::Server::ConsoleSocketServer.new(
|
|
186
185
|
socket_path: socket_path,
|
|
@@ -189,12 +188,12 @@ module Consolle
|
|
|
189
188
|
logger: logger,
|
|
190
189
|
command: command
|
|
191
190
|
)
|
|
192
|
-
|
|
191
|
+
#{' '}
|
|
193
192
|
puts "[Server] Starting server with log level: \#{log_level}..."
|
|
194
193
|
server.start
|
|
195
|
-
|
|
194
|
+
#{' '}
|
|
196
195
|
puts "[Server] Server started, entering sleep..."
|
|
197
|
-
|
|
196
|
+
#{' '}
|
|
198
197
|
# Keep running
|
|
199
198
|
sleep
|
|
200
199
|
rescue => e
|
|
@@ -209,7 +208,7 @@ module Consolle
|
|
|
209
208
|
# Ensure directory exists
|
|
210
209
|
socket_dir = File.dirname(@socket_path)
|
|
211
210
|
FileUtils.mkdir_p(socket_dir) unless Dir.exist?(socket_dir)
|
|
212
|
-
|
|
211
|
+
|
|
213
212
|
# Start server process
|
|
214
213
|
log_file = @log_path
|
|
215
214
|
pid = Process.spawn(
|
|
@@ -217,7 +216,7 @@ module Consolle
|
|
|
217
216
|
out: log_file,
|
|
218
217
|
err: log_file
|
|
219
218
|
)
|
|
220
|
-
|
|
219
|
+
|
|
221
220
|
Process.detach(pid)
|
|
222
221
|
end
|
|
223
222
|
|
|
@@ -225,27 +224,32 @@ module Consolle
|
|
|
225
224
|
# Read PID file
|
|
226
225
|
pid_file = @pid_path
|
|
227
226
|
return unless File.exist?(pid_file)
|
|
228
|
-
|
|
227
|
+
|
|
229
228
|
pid = File.read(pid_file).to_i
|
|
230
|
-
|
|
229
|
+
|
|
231
230
|
# Kill process
|
|
232
231
|
begin
|
|
233
|
-
Process.kill(
|
|
234
|
-
|
|
232
|
+
Process.kill('TERM', pid)
|
|
233
|
+
|
|
235
234
|
# Wait for socket to disappear
|
|
236
235
|
10.times do
|
|
237
236
|
break unless File.exist?(@socket_path)
|
|
237
|
+
|
|
238
238
|
sleep 0.1
|
|
239
239
|
end
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
# Force kill if needed
|
|
242
242
|
if File.exist?(@socket_path)
|
|
243
|
-
|
|
243
|
+
begin
|
|
244
|
+
Process.kill('KILL', pid)
|
|
245
|
+
rescue StandardError
|
|
246
|
+
nil
|
|
247
|
+
end
|
|
244
248
|
end
|
|
245
249
|
rescue Errno::ESRCH
|
|
246
250
|
# Process already dead
|
|
247
251
|
end
|
|
248
|
-
|
|
252
|
+
|
|
249
253
|
# Clean up files
|
|
250
254
|
File.unlink(@socket_path) if File.exist?(@socket_path)
|
|
251
255
|
File.unlink(pid_file) if File.exist?(pid_file)
|
|
@@ -253,43 +257,44 @@ module Consolle
|
|
|
253
257
|
|
|
254
258
|
def wait_for_server(timeout: 10)
|
|
255
259
|
deadline = Time.now + timeout
|
|
256
|
-
|
|
260
|
+
|
|
257
261
|
while Time.now < deadline
|
|
258
262
|
return true if File.exist?(@socket_path) && get_status
|
|
263
|
+
|
|
259
264
|
sleep 0.1
|
|
260
265
|
end
|
|
261
|
-
|
|
266
|
+
|
|
262
267
|
raise "Server failed to start within #{timeout} seconds"
|
|
263
268
|
end
|
|
264
269
|
|
|
265
270
|
def send_request(request, timeout: 30)
|
|
266
271
|
Timeout.timeout(timeout) do
|
|
267
272
|
socket = UNIXSocket.new(@socket_path)
|
|
268
|
-
|
|
273
|
+
|
|
269
274
|
# Send request
|
|
270
275
|
socket.write(JSON.generate(request))
|
|
271
276
|
socket.write("\n")
|
|
272
277
|
socket.flush
|
|
273
|
-
|
|
278
|
+
|
|
274
279
|
# Read response
|
|
275
280
|
response_data = socket.gets
|
|
276
281
|
socket.close
|
|
277
|
-
|
|
282
|
+
|
|
278
283
|
JSON.parse(response_data)
|
|
279
284
|
end
|
|
280
285
|
rescue Timeout::Error
|
|
281
286
|
{
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
287
|
+
'success' => false,
|
|
288
|
+
'error' => 'Timeout',
|
|
289
|
+
'message' => "Request timed out after #{timeout} seconds"
|
|
285
290
|
}
|
|
286
291
|
rescue StandardError => e
|
|
287
292
|
{
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
293
|
+
'success' => false,
|
|
294
|
+
'error' => e.class.name,
|
|
295
|
+
'message' => e.message
|
|
291
296
|
}
|
|
292
297
|
end
|
|
293
298
|
end
|
|
294
299
|
end
|
|
295
|
-
end
|
|
300
|
+
end
|