sudo 0.3.0 → 0.4.0.rc1
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 +58 -13
- data/LICENSE +2 -1
- data/README.md +73 -14
- data/lib/sudo/configuration.rb +66 -0
- data/lib/sudo/constants.rb +2 -3
- data/lib/sudo/proxy.rb +13 -7
- data/lib/sudo/support/kernel.rb +7 -3
- data/lib/sudo/support/process.rb +5 -2
- data/lib/sudo/system.rb +29 -8
- data/lib/sudo/wrapper.rb +39 -31
- data/lib/sudo.rb +7 -0
- metadata +13 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 351e03abfefb1fdf2a25a6069fcc6eaebc4577b7f4279c8658b338431175afa0
|
4
|
+
data.tar.gz: 2f00f8bde90fbc42bd50f4fe80912f5a4fd982f2cc6eb5190ffa7c6f853c9e2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6d0257cfac653363fb46fc68aca35a91bed9ca751748fd31f3e768aa4c9f482c64329b89038dfcafbe2c2d9cca9c5fe95d6eca9a7aeb2ee6b2fe9d150a6c0f9
|
7
|
+
data.tar.gz: 7f89398dacdcb0bd13dbbfcaba66741b28befdf0dff03c0eada3a5546975876d6854b61ae41413ac48b725cedf50d0c3bb566d3fd0f1c1e8e51060987b8bd498
|
data/CHANGELOG.md
CHANGED
@@ -1,19 +1,64 @@
|
|
1
1
|
# Sudo
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
<!--
|
4
|
+
Emoji Legend:
|
5
|
+
🎉 Initial Release ✨ Feature 🐛 Bug Fix 🔒 Security
|
6
|
+
🚀 Compatibility 💥 Breaking 🔧 Internal ✅ Testing
|
7
|
+
📚 Documentation 📄 License 🗑️ Removed
|
8
|
+
-->
|
5
9
|
|
6
|
-
##
|
7
|
-
- Modernized
|
8
|
-
- Tests
|
9
|
-
- Works on ruby 2.3 - 2.5
|
10
|
-
- More robust dependency loading
|
10
|
+
## `v0.4.0-rc1` _(July 23, 2025)_
|
11
11
|
|
12
|
-
|
13
|
-
-
|
12
|
+
- 🔒 **Security**: Fix command injection vulnerabilities in system calls
|
13
|
+
- 🔒 **Security**: Use SecureRandom for socket paths instead of predictable object_id
|
14
|
+
- ✨ **Feature**: Add configuration system with global defaults
|
15
|
+
- ✨ **Feature**: Implement sudo -A flag support for graphical password prompts
|
16
|
+
- ✨ **Feature**: Add Sudo.as_root convenience method for better DSL
|
17
|
+
- ✨ **Feature**: Add configurable timeouts
|
18
|
+
- ✨ **Feature**: Add respond_to_missing? for proper method reflection
|
19
|
+
- 💥 **Breaking**: Minimum Ruby version bumped to 2.7+ (EOL compliance)
|
20
|
+
- 🔧 **Internal**: Modernize Ruby code with keyword arguments and array-form system calls
|
21
|
+
- 🔧 **Internal**: Improve test coverage and add configuration tests
|
14
22
|
|
15
|
-
|
16
|
-
|
23
|
+
<details>
|
24
|
+
<summary>📜 Historical Releases</summary>
|
17
25
|
|
18
|
-
##
|
19
|
-
|
26
|
+
## `v0.3.0` _(July 04, 2023)_
|
27
|
+
|
28
|
+
- 🚀 **Compatibility**: Add Ruby 3.2 support
|
29
|
+
- 🐛 **Fix**: Resolve Bundler::StubSpecification marshaling issues
|
30
|
+
|
31
|
+
## `v0.2.0` _(November 05, 2018)_
|
32
|
+
|
33
|
+
- 🔧 **Internal**: Complete code modernization and cleanup
|
34
|
+
- ✅ **Testing**: Add comprehensive RSpec test suite (98%+ coverage)
|
35
|
+
- 🚀 **Compatibility**: Support Ruby 2.3, 2.4, and 2.5
|
36
|
+
- 🐛 **Fix**: Improve gem and dependency loading robustness
|
37
|
+
- 🐛 **Fix**: Ensure sudo process properly stops when run block ends
|
38
|
+
- 🐛 **Fix**: Fix Wrapper.run to properly return values
|
39
|
+
- 🐛 **Fix**: Resolve infinite recursion under Bundler
|
40
|
+
- 🔒 **Security**: Restrict DRb access to localhost only
|
41
|
+
- 📚 **Documentation**: Extensive README and code documentation improvements
|
42
|
+
|
43
|
+
## `v0.1.0` _(October 25, 2010)_
|
44
|
+
|
45
|
+
- 📄 **License**: Switch to MIT license
|
46
|
+
- ✨ **Feature**: Add auto-require and autoload support
|
47
|
+
- 🔧 **Internal**: Modularize codebase architecture
|
48
|
+
- 📚 **Documentation**: Extensive documentation improvements
|
49
|
+
- 🗑️ **Removed**: Remove confusing DSL features (temporarily)
|
50
|
+
|
51
|
+
## `v0.0.2` _(October 22, 2010)_
|
52
|
+
|
53
|
+
- 📚 **Documentation**: Correct RDoc options in gemspec
|
54
|
+
- 🔧 **Internal**: Minor packaging improvements
|
55
|
+
|
56
|
+
## `v0.0.1` _(October 22, 2010)_
|
57
|
+
|
58
|
+
- 🎉 **Initial**: First public release
|
59
|
+
- ✨ **Feature**: Core sudo wrapper functionality with DRb
|
60
|
+
- ✨ **Feature**: Unix domain socket communication
|
61
|
+
- ✨ **Feature**: Process spawning and management
|
62
|
+
- ✨ **Feature**: Basic object proxying through sudo
|
63
|
+
|
64
|
+
</details>
|
data/LICENSE
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
(The MIT License)
|
2
2
|
|
3
|
-
Copyright (c) 2010-
|
3
|
+
Copyright (c) 2010-2018 Guido De Rosa
|
4
|
+
Copyright (c) 2018-2025 Twilight Coders
|
4
5
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining
|
6
7
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
[](https://badge.fury.io/rb/sudo)
|
2
|
-
[](https://badge.fury.io/rb/sudo)
|
2
|
+
[](https://github.com/TwilightCoders/rubysu/actions/workflows/ci.yml)
|
3
|
+
[](https://qlty.sh/TwilightCoders/rubysu)
|
4
|
+
[](https://qlty.sh/TwilightCoders/rubysu)
|
4
5
|
|
5
6
|
# Ruby Sudo
|
6
7
|
|
@@ -8,7 +9,7 @@ Give Ruby objects superuser privileges.
|
|
8
9
|
|
9
10
|
Based on [dRuby](http://ruby-doc.org/stdlib-2.5.3/libdoc/drb/rdoc/DRb.html) and [sudo](http://www.sudo.ws/).
|
10
11
|
|
11
|
-
|
12
|
+
Tested with [MRI](http://en.wikipedia.org/wiki/Ruby_MRI) Ruby 2.7, 3.0, 3.1, 3.2, and 3.3.
|
12
13
|
|
13
14
|
## Usage
|
14
15
|
|
@@ -52,7 +53,7 @@ sudo[MyClass].my_class_method
|
|
52
53
|
sudo.stop!
|
53
54
|
```
|
54
55
|
|
55
|
-
A
|
56
|
+
A convenient utility for working with sudo is to use the `run` method and pass it a block.
|
56
57
|
Run will automatically start and stop the ruby sudo process around the block.
|
57
58
|
|
58
59
|
```ruby
|
@@ -65,16 +66,72 @@ end
|
|
65
66
|
# Sockets and processes are closed automatically when the block exits
|
66
67
|
```
|
67
68
|
|
68
|
-
Both `Sudo::Wrapper.run` and `Sudo::Wrapper.new`
|
69
|
+
Both `Sudo::Wrapper.run` and `Sudo::Wrapper.new` accept configuration options:
|
69
70
|
|
70
|
-
|
71
|
+
- `ruby_opts` (default: `''`) - Options to pass to the sudo-spawned ruby process
|
72
|
+
- Any configuration option can be passed to override global settings (e.g., `timeout`, `load_gems`, `socket_dir`, etc.)
|
71
73
|
|
72
|
-
If you'd like to prevent the loading of `gems` currently loaded from the calling program, pass `false
|
74
|
+
If you'd like to prevent the loading of `gems` currently loaded from the calling program, pass `load_gems: false`. This will give your sudo process an unmodified environment. The only things required via the sudo process are `'drb/drb'`, `'fileutils'`, and of course `'sudo'`.
|
73
75
|
|
74
|
-
|
76
|
+
### New DSL (v0.4.0+)
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
+
For simple operations, you can use the convenience method:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require 'sudo'
|
82
|
+
|
83
|
+
# Accepts the same options as Wrapper.run:
|
84
|
+
Sudo.as_root(load_gems: false) do |sudo|
|
85
|
+
sudo[FileUtils].mkdir_p '/root/only/path'
|
86
|
+
sudo[File].write '/etc/config', content
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
### Configuration (v0.4.0+)
|
91
|
+
|
92
|
+
Configure global defaults:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
Sudo.configure do |config|
|
96
|
+
config.timeout = 30 # Default: 10 seconds
|
97
|
+
config.socket_dir = '/var/run' # Default: '/tmp'
|
98
|
+
config.sudo_askpass = '/usr/bin/ssh-askpass' # For graphical password prompts
|
99
|
+
config.load_gems = false # Default: true - whether to load current gems in sudo process
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### Graphical Password Prompts (v0.4.0+)
|
104
|
+
|
105
|
+
Set `sudo_askpass` to use graphical password prompts via `sudo -A`:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Sudo.configure do |config|
|
109
|
+
config.sudo_askpass = '/usr/bin/ssh-askpass'
|
110
|
+
# Or use the auto-detected constant for convenience:
|
111
|
+
# config.sudo_askpass = Sudo::ASK_PATH_CMD
|
112
|
+
end
|
113
|
+
|
114
|
+
# Or per-wrapper:
|
115
|
+
Sudo::Wrapper.run(sudo_askpass: '/usr/bin/ssh-askpass') do |sudo|
|
116
|
+
sudo[FileUtils].mkdir_p '/secure/path'
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
### Timeouts (v0.4.0+)
|
121
|
+
|
122
|
+
Configure connection timeouts:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
# Global configuration
|
126
|
+
Sudo.configure do |config|
|
127
|
+
config.timeout = 15 # Wait up to 15 seconds for sudo process to start
|
128
|
+
end
|
129
|
+
|
130
|
+
# Or per-wrapper
|
131
|
+
Sudo::Wrapper.run(timeout: 5) do |sudo|
|
132
|
+
sudo[SomeClass].time_sensitive_operation
|
133
|
+
end
|
134
|
+
```
|
78
135
|
|
79
136
|
## Credits
|
80
137
|
|
@@ -92,15 +149,17 @@ Robert M. Koch ([@threadmetal](https://github.com/threadmetal))
|
|
92
149
|
|
93
150
|
Wolfgang Teuber ([@wteuber](https://github.com/wteuber))
|
94
151
|
|
95
|
-
### Other
|
96
|
-
|
152
|
+
### Other acknowledgements
|
153
|
+
|
154
|
+
|
155
|
+
Thanks to Tony Arcieri and Brian Candler for suggestions on
|
97
156
|
[ruby-talk](http://www.ruby-forum.com/topic/262655).
|
98
157
|
|
99
158
|
Initially developed by G. D. while working at [@vemarsas](https://github.com/vemarsas).
|
100
159
|
|
101
160
|
## Contributing
|
102
161
|
|
103
|
-
1. Fork it ( https://github.com/
|
162
|
+
1. Fork it ( https://github.com/TwilightCoders/rubysu/fork )
|
104
163
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
105
164
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
106
165
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
# Sudo module provides superuser privileges to Ruby objects
|
6
|
+
module Sudo
|
7
|
+
# Configuration class for managing global sudo settings
|
8
|
+
class Configuration < Hash
|
9
|
+
private :[], :[]=
|
10
|
+
|
11
|
+
DEFAULTS = {
|
12
|
+
timeout: 10,
|
13
|
+
retries: 3,
|
14
|
+
socket_dir: '/tmp',
|
15
|
+
sudo_askpass: nil,
|
16
|
+
load_gems: true
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def initialize(config = {}, **kwargs)
|
20
|
+
super()
|
21
|
+
merge!(@configuration || DEFAULTS)
|
22
|
+
merge!(config.merge(kwargs).slice(*DEFAULTS.keys))
|
23
|
+
end
|
24
|
+
|
25
|
+
def socket_path(pid, random_id)
|
26
|
+
File.join(self[:socket_dir], "rubysu-#{pid}-#{random_id}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method, *args, &block)
|
30
|
+
method_name = method.to_s
|
31
|
+
|
32
|
+
if method_name.end_with?('=')
|
33
|
+
key = method_name.chomp('=').to_sym
|
34
|
+
if DEFAULTS.key?(key)
|
35
|
+
self[key] = args.first
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
elsif DEFAULTS.key?(method)
|
40
|
+
self[method]
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to_missing?(method, include_private = false)
|
47
|
+
method_name = method.to_s
|
48
|
+
key = method_name.end_with?('=') ? method_name.chomp('=').to_sym : method
|
49
|
+
DEFAULTS.key?(key) || super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def configuration
|
55
|
+
@configuration ||= Configuration.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def configure
|
59
|
+
yield configuration
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_configuration!
|
63
|
+
@configuration = Configuration.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/sudo/constants.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
|
3
3
|
module Sudo
|
4
|
-
|
5
|
-
VERSION = '0.3.0'
|
4
|
+
VERSION = '0.4.0.rc1'
|
6
5
|
|
7
6
|
def self.root
|
8
7
|
@root ||= Pathname.new(File.expand_path('../../', __dir__))
|
@@ -12,7 +11,7 @@ module Sudo
|
|
12
11
|
SERVER_SCRIPT = root.join('libexec/server.rb')
|
13
12
|
SUDO_CMD = `which sudo`.chomp
|
14
13
|
RUBY_CMD = `which ruby`.chomp
|
14
|
+
ASK_PATH_CMD = `which ssh-askpass`.chomp
|
15
15
|
|
16
16
|
RuntimeError = Class.new(RuntimeError)
|
17
|
-
|
18
17
|
end
|
data/lib/sudo/proxy.rb
CHANGED
@@ -1,24 +1,31 @@
|
|
1
|
-
|
2
1
|
module Sudo
|
3
|
-
|
4
2
|
class MethodProxy
|
5
3
|
def initialize(object, proxy)
|
6
4
|
@object = object
|
7
5
|
@proxy = proxy
|
8
6
|
end
|
9
|
-
|
7
|
+
|
8
|
+
def method_missing(method = :itself, *args, &blk)
|
10
9
|
@proxy.proxy @object, method, *args, &blk
|
11
10
|
end
|
11
|
+
|
12
|
+
def respond_to_missing?(method, include_private = false)
|
13
|
+
@object.respond_to?(method, include_private) || super
|
14
|
+
end
|
12
15
|
end
|
13
16
|
|
14
17
|
class Proxy
|
15
|
-
def proxy(object, method
|
18
|
+
def proxy(object, method = :itself, *args, &blk)
|
16
19
|
object.send method, *args, &blk
|
17
20
|
end
|
18
21
|
|
19
22
|
def loaded_specs
|
20
|
-
#
|
21
|
-
|
23
|
+
# Return only the keys (gem names) to avoid marshaling StubSpecification objects
|
24
|
+
# which can fail in newer Bundler versions
|
25
|
+
Gem.loaded_specs.keys
|
26
|
+
rescue => e
|
27
|
+
warn "Warning: Could not get loaded gem specs (#{e.class}: #{e.message}). Returning empty list."
|
28
|
+
[]
|
22
29
|
end
|
23
30
|
|
24
31
|
def load_path
|
@@ -29,5 +36,4 @@ module Sudo
|
|
29
36
|
$LOAD_PATH << path
|
30
37
|
end
|
31
38
|
end
|
32
|
-
|
33
39
|
end
|
data/lib/sudo/support/kernel.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'timeout'
|
2
4
|
|
5
|
+
# Kernel module extensions for sudo functionality
|
3
6
|
module Kernel
|
4
7
|
def wait_for(timeout: nil, step: 0.125)
|
5
|
-
Timeout
|
8
|
+
Timeout.timeout(timeout) do
|
6
9
|
condition = false
|
7
|
-
sleep(step) until (condition = yield)
|
10
|
+
sleep(step) until (condition = yield)
|
11
|
+
condition
|
8
12
|
end
|
9
13
|
rescue Timeout::Error
|
10
|
-
|
14
|
+
false
|
11
15
|
end
|
12
16
|
end
|
data/lib/sudo/support/process.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Process module extensions for sudo functionality
|
1
4
|
module Process
|
2
5
|
class << self
|
3
6
|
# Thanks to:
|
4
7
|
# http://stackoverflow.com/questions/141162/how-can-i-determine-if-a-different-process-id-is-running-using-java-or-jruby-on-l
|
5
8
|
def exists?(pid)
|
6
|
-
Process.getpgid(
|
9
|
+
Process.getpgid(pid)
|
7
10
|
true
|
8
|
-
rescue Errno::ESRCH
|
11
|
+
rescue Errno::ESRCH, TypeError
|
9
12
|
false
|
10
13
|
end
|
11
14
|
end
|
data/lib/sudo/system.rb
CHANGED
@@ -3,32 +3,53 @@ require 'sudo/constants'
|
|
3
3
|
|
4
4
|
module Sudo
|
5
5
|
module System
|
6
|
-
|
7
6
|
ProcessStillExists = Class.new(RuntimeError)
|
8
7
|
FileStillExists = Class.new(RuntimeError)
|
9
8
|
|
10
9
|
class << self
|
11
|
-
|
12
10
|
def kill(pid)
|
13
11
|
if pid and Process.exists? pid
|
14
|
-
system
|
15
|
-
|
16
|
-
|
12
|
+
system("sudo", "kill", pid.to_s) or
|
13
|
+
system("sudo", "kill", "-9", pid.to_s) or
|
14
|
+
raise ProcessStillExists, "Couldn't kill sudo process (PID=#{pid})"
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
18
|
+
def command(ruby_opts, socket, env = {})
|
19
|
+
cmd_args, env = command_base(env)
|
20
|
+
cmd_args << "-I#{LIBDIR}"
|
21
|
+
cmd_args.concat(ruby_opts.split) unless ruby_opts.empty?
|
22
|
+
cmd_args.concat([SERVER_SCRIPT.to_s, socket, Process.uid.to_s])
|
23
|
+
[cmd_args, env]
|
24
|
+
end
|
25
|
+
|
20
26
|
def unlink(file)
|
21
27
|
if file and File.exist? file
|
22
|
-
system("sudo rm -f
|
23
|
-
|
28
|
+
system("sudo", "rm", "-f", file) or
|
29
|
+
raise(FileStillExists, "Couldn't delete #{file}")
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
33
|
# just to check if we can sudo; and we'll receive a sudo token
|
28
34
|
def check
|
29
|
-
|
35
|
+
cmd_args, env = command_base
|
36
|
+
cmd_args.concat(["-e", ""])
|
37
|
+
raise Sudo::Wrapper::SudoFailed unless system(env, *cmd_args)
|
30
38
|
end
|
31
39
|
|
40
|
+
private
|
41
|
+
|
42
|
+
def command_base(env = {})
|
43
|
+
cmd_args = [SUDO_CMD]
|
44
|
+
|
45
|
+
if defined?(Sudo.configuration) && Sudo.configuration.sudo_askpass
|
46
|
+
env["SUDO_ASKPASS"] = Sudo.configuration.sudo_askpass
|
47
|
+
cmd_args << "-A"
|
48
|
+
end
|
49
|
+
|
50
|
+
cmd_args.concat(["-E", RUBY_CMD])
|
51
|
+
[cmd_args, env]
|
52
|
+
end
|
32
53
|
end
|
33
54
|
end
|
34
55
|
end
|
data/lib/sudo/wrapper.rb
CHANGED
@@ -2,13 +2,12 @@ require 'drb/drb'
|
|
2
2
|
require 'sudo/support/kernel'
|
3
3
|
require 'sudo/support/process'
|
4
4
|
require 'sudo/constants'
|
5
|
+
require 'sudo/configuration'
|
5
6
|
require 'sudo/system'
|
6
7
|
require 'sudo/proxy'
|
7
8
|
|
8
9
|
module Sudo
|
9
|
-
|
10
10
|
class Wrapper
|
11
|
-
|
12
11
|
RuntimeError = Class.new(RuntimeError)
|
13
12
|
NotRunning = Class.new(RuntimeError)
|
14
13
|
SudoFailed = Class.new(RuntimeError)
|
@@ -20,18 +19,17 @@ module Sudo
|
|
20
19
|
SudoProcessNotFound = Class.new(NoValidSudoPid)
|
21
20
|
|
22
21
|
class << self
|
23
|
-
|
24
22
|
# Yields a new running Sudo::Wrapper, and do all the necessary
|
25
23
|
# cleanup when the block exits.
|
26
24
|
#
|
27
25
|
# ruby_opts:: is passed to Sudo::Wrapper::new .
|
28
|
-
def run(ruby_opts: '',
|
29
|
-
sudo = new(ruby_opts: ruby_opts,
|
26
|
+
def run(ruby_opts: '', **config) # :yields: sudo
|
27
|
+
sudo = new(ruby_opts: ruby_opts, config: Configuration.new(config)).start!
|
30
28
|
yield sudo
|
31
29
|
rescue Exception => e # Bubble all exceptions...
|
32
30
|
raise e
|
33
31
|
ensure # and ensure sudo stops
|
34
|
-
sudo.stop!
|
32
|
+
sudo.stop! if sudo
|
35
33
|
end
|
36
34
|
|
37
35
|
# Do the actual resources clean-up.
|
@@ -42,18 +40,20 @@ module Sudo
|
|
42
40
|
Sudo::System.kill h[:pid]
|
43
41
|
Sudo::System.unlink h[:socket]
|
44
42
|
end
|
45
|
-
|
46
43
|
end
|
47
44
|
|
48
45
|
# +ruby_opts+ are the command line options to the sudo ruby interpreter;
|
49
46
|
# usually you don't need to specify stuff like "-rmygem/mylib", libraries
|
50
47
|
# will be sorta "inherited".
|
51
|
-
def initialize(ruby_opts: '',
|
48
|
+
def initialize(ruby_opts: '', config: nil)
|
49
|
+
@config = config || Sudo.configuration
|
52
50
|
@proxy = nil
|
53
|
-
@socket =
|
51
|
+
@socket = @config.socket_path(Process.pid, SecureRandom.hex(8))
|
54
52
|
@sudo_pid = nil
|
55
53
|
@ruby_opts = ruby_opts
|
56
|
-
@load_gems = load_gems
|
54
|
+
@load_gems = @config.load_gems
|
55
|
+
@timeout = @config.timeout
|
56
|
+
@retries = @config.retries
|
57
57
|
end
|
58
58
|
|
59
59
|
def server_uri; "drbunix:#{@socket}"; end
|
@@ -62,17 +62,17 @@ module Sudo
|
|
62
62
|
def start!
|
63
63
|
Sudo::System.check
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
)
|
65
|
+
cmd_args, env = Sudo::System.command(@ruby_opts, @socket)
|
66
|
+
|
67
|
+
@sudo_pid = spawn(env, *cmd_args)
|
68
68
|
Process.detach(@sudo_pid) if @sudo_pid # avoid zombies
|
69
69
|
finalizer = Finalizer.new(pid: @sudo_pid, socket: @socket)
|
70
70
|
ObjectSpace.define_finalizer(self, finalizer)
|
71
71
|
|
72
|
-
if wait_for(timeout:
|
72
|
+
if wait_for(timeout: @timeout) { socket? }
|
73
73
|
@proxy = DRbObject.new_with_uri(server_uri)
|
74
74
|
else
|
75
|
-
raise RuntimeError, "Couldn't create DRb socket #{@socket}"
|
75
|
+
raise RuntimeError, "Couldn't create DRb socket #{@socket} within #{@timeout} seconds"
|
76
76
|
end
|
77
77
|
|
78
78
|
load!
|
@@ -80,12 +80,12 @@ module Sudo
|
|
80
80
|
self
|
81
81
|
end
|
82
82
|
|
83
|
+
def socket?
|
84
|
+
File.exist?(@socket)
|
85
|
+
end
|
86
|
+
|
83
87
|
def running?
|
84
|
-
|
85
|
-
@sudo_pid and Process.exists? @sudo_pid and
|
86
|
-
@socket and File.exist? @socket and
|
87
|
-
@proxy
|
88
|
-
)
|
88
|
+
Process.exists?(@sudo_pid) && socket? && @proxy
|
89
89
|
end
|
90
90
|
|
91
91
|
# Free the resources opened by this Wrapper: e.g. the sudo-ed
|
@@ -130,22 +130,31 @@ module Sudo
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def prospective_gems
|
133
|
-
|
133
|
+
proxy_loaded_specs = @proxy.loaded_specs
|
134
|
+
local_loaded_specs = Gem.loaded_specs.keys
|
135
|
+
(local_loaded_specs - proxy_loaded_specs)
|
136
|
+
rescue => e
|
137
|
+
# Fallback if DRb marshaling fails with newer Bundler versions
|
138
|
+
warn "Warning: Could not compare loaded gems (#{e.class}: #{e.message}). Skipping gem loading."
|
139
|
+
[]
|
134
140
|
end
|
135
141
|
|
136
142
|
# Load needed libraries in the DRb server. Usually you don't need
|
137
143
|
def load_gems
|
138
144
|
load_paths
|
139
145
|
prospective_gems.each do |prospect|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
146
|
+
try_gem_variants(prospect)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def try_gem_variants(gem_name)
|
153
|
+
[gem_name, gem_name.gsub('-', '/')].uniq.each do |variant|
|
154
|
+
@proxy.proxy(Kernel, :require, variant)
|
155
|
+
return # Success, stop trying variants
|
156
|
+
rescue LoadError, NameError
|
157
|
+
# Try next variant
|
149
158
|
end
|
150
159
|
end
|
151
160
|
|
@@ -157,6 +166,5 @@ module Sudo
|
|
157
166
|
@proxy.add_load_path(path)
|
158
167
|
end
|
159
168
|
end
|
160
|
-
|
161
169
|
end
|
162
170
|
end
|
data/lib/sudo.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sudo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guido De Rosa
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-07-24 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: pry-byebug
|
@@ -30,42 +29,42 @@ dependencies:
|
|
30
29
|
requirements:
|
31
30
|
- - ">="
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
32
|
+
version: '2.0'
|
34
33
|
type: :development
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
39
|
+
version: '2.0'
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: rake
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
44
|
- - "~>"
|
46
45
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
46
|
+
version: '13.0'
|
48
47
|
type: :development
|
49
48
|
prerelease: false
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
51
|
- - "~>"
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
53
|
+
version: '13.0'
|
55
54
|
- !ruby/object:Gem::Dependency
|
56
55
|
name: rspec
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
58
57
|
requirements:
|
59
|
-
- - "
|
58
|
+
- - "~>"
|
60
59
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
60
|
+
version: '3.10'
|
62
61
|
type: :development
|
63
62
|
prerelease: false
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
65
64
|
requirements:
|
66
|
-
- - "
|
65
|
+
- - "~>"
|
67
66
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
67
|
+
version: '3.10'
|
69
68
|
description: |
|
70
69
|
Give Ruby objects superuser privileges.
|
71
70
|
Based on dRuby and sudo (the Unix program).
|
@@ -79,6 +78,7 @@ files:
|
|
79
78
|
- LICENSE
|
80
79
|
- README.md
|
81
80
|
- lib/sudo.rb
|
81
|
+
- lib/sudo/configuration.rb
|
82
82
|
- lib/sudo/constants.rb
|
83
83
|
- lib/sudo/proxy.rb
|
84
84
|
- lib/sudo/support/kernel.rb
|
@@ -91,7 +91,6 @@ licenses:
|
|
91
91
|
- MIT
|
92
92
|
metadata:
|
93
93
|
allowed_push_host: https://rubygems.org
|
94
|
-
post_install_message:
|
95
94
|
rdoc_options: []
|
96
95
|
require_paths:
|
97
96
|
- lib
|
@@ -99,15 +98,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
98
|
requirements:
|
100
99
|
- - ">="
|
101
100
|
- !ruby/object:Gem::Version
|
102
|
-
version: '2.
|
101
|
+
version: '2.7'
|
103
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
103
|
requirements:
|
105
104
|
- - ">="
|
106
105
|
- !ruby/object:Gem::Version
|
107
106
|
version: '0'
|
108
107
|
requirements: []
|
109
|
-
rubygems_version: 3.
|
110
|
-
signing_key:
|
108
|
+
rubygems_version: 3.6.3
|
111
109
|
specification_version: 4
|
112
110
|
summary: Give Ruby objects superuser privileges
|
113
111
|
test_files: []
|