sudo 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8533dcc019e5bf40c31a3a2f0d00fb1ed7799710fbd623e1f35bdc7b3854f8ad
4
- data.tar.gz: d21c8cc10c30840bdcd0d6a9c86816ea8123897305c5e4b41b6503301c39e8f6
3
+ metadata.gz: 672afd6f5901990cba97a2cd66f55730fd9357751c3218e5fcea74bafaa70ae8
4
+ data.tar.gz: c86aa8ccb29d8d7078351ac878b7797d088c97ebf5980693a5b6c5015b7e816b
5
5
  SHA512:
6
- metadata.gz: 53a797c78535d39c6fd4c123726345cc6ad4a73617f8e835437ab49c86168dbfd83696776ea7c2a1fdb26419e88f9cb6799bf33365447ff42c2a023015c0fc48
7
- data.tar.gz: e81deabf36e8a0a4a4bad76e8d94560c0518e25deb8f8dac7184dcbb9befee866f704a2f17716dafc773ad0427329120c7ca88562fb82af21e061234681c5114
6
+ metadata.gz: bab63f33550bc1fbc4915cf183e50f5b265fbc7f234cf062531ba593e5c2fe34a6c91bcef446416fe0e8cc8a338ea54fcd161907d5191c4aed9eda72e7c82dd1
7
+ data.tar.gz: 24270556f2aa92f1ad1a26dab42aa47b79e138c9e1f38085df49c76f648c365eab5ede0b7d5a0170ee42da7e3863f0bb8e9b08b85a97d5c87c6892014561acba
data/CHANGELOG.md CHANGED
@@ -1,19 +1,64 @@
1
1
  # Sudo
2
2
 
3
- ## 0.3.0 _(July 04, 2023)_
4
- - Works on ruby 3.2
3
+ <!--
4
+ Emoji Legend:
5
+ 🎉 Initial Release ✨ Feature 🐛 Bug Fix 🔒 Security
6
+ 🚀 Compatibility 💥 Breaking 🔧 Internal ✅ Testing
7
+ 📚 Documentation 📄 License 🗑️ Removed
8
+ -->
5
9
 
6
- ## 0.2.0 _(November 05, 2018)_
7
- - Modernized
8
- - Tests
9
- - Works on ruby 2.3 - 2.5
10
- - More robust dependency loading
10
+ ## `v0.4.0` _(July 23, 2025)_
11
11
 
12
- ## 0.0.3 _(October 25, 2010)_
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
- ## 0.0.2 _(October 22, 2010)_
16
- -
23
+ <details>
24
+ <summary>📜 Historical Releases</summary>
17
25
 
18
- ## 0.0.1 _(October 22, 2010)_
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-2023 Guido De Rosa
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
- [![Gem Version](https://badge.fury.io/rb/sudo.svg)](https://badge.fury.io/rb/sudo)[![Build Status](https://travis-ci.com/gderosa/rubysu.svg?branch=master)](https://travis-ci.com/gderosa/rubysu)
2
- [![Maintainability](https://api.codeclimate.com/v1/badges/3fdebfb836bebb531fb3/maintainability)](https://codeclimate.com/github/gderosa/rubysu/maintainability)
3
- [![Test Coverage](https://api.codeclimate.com/v1/badges/3fdebfb836bebb531fb3/test_coverage)](https://codeclimate.com/github/gderosa/rubysu/test_coverage)
1
+ [![Gem Version](https://badge.fury.io/rb/sudo.svg)](https://badge.fury.io/rb/sudo)
2
+ [![CI](https://github.com/TwilightCoders/rubysu/actions/workflows/ci.yml/badge.svg)](https://github.com/TwilightCoders/rubysu/actions/workflows/ci.yml)
3
+ [![Maintainability](https://qlty.sh/badges/e63e40be-4d72-4519-ad77-d4f94803a7b9/maintainability.svg)](https://qlty.sh/TwilightCoders/rubysu)
4
+ [![Test Coverage](https://qlty.sh/badges/e63e40be-4d72-4519-ad77-d4f94803a7b9/test_coverage.svg)](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
- Only tested with [MRI](http://en.wikipedia.org/wiki/Ruby_MRI).
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 convienient utility for working with sudo is to use the `run` method and pass it a block.
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` take the same named arguments: `ruby_opts` (default: `''` ) and `load_gems` (default: `true`).
69
+ Both `Sudo::Wrapper.run` and `Sudo::Wrapper.new` accept configuration options:
69
70
 
70
- If you'd like to pass options to the sudo-spawned ruby process, pass them as a string to `ruby_opts`.
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` to `load_gems`. This will give your sudo process a unmodifed environment. The only things required via the sudo process are `'drb/drb'`, `'fileutils'`, and of course `'sudo'`.
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
- ## Todo
76
+ ### New DSL (v0.4.0+)
75
77
 
76
- `sudo` has a `-A` option to accept password via an external program (maybe
77
- graphical): support this feature.
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 aknowledgements
96
- Thanks to Tony Arcieri and Brian Candler for suggestions on
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/gderosa/rubysu/fork )
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
@@ -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'
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
- def method_missing(method=:itself, *args, &blk)
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=:itself, *args, &blk)
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
- # Something's weird with this method when called outside
21
- Gem.loaded_specs.to_a.to_h
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
@@ -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::timeout(timeout) do
8
+ Timeout.timeout(timeout) do
6
9
  condition = false
7
- sleep(step) until (condition = yield) and return condition
10
+ sleep(step) until (condition = yield)
11
+ condition
8
12
  end
9
13
  rescue Timeout::Error
10
- return false
14
+ false
11
15
  end
12
16
  end
@@ -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( pid )
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 "sudo kill #{pid}" or
15
- system "sudo kill -9 #{pid}" or
16
- raise ProcessStillExists, "Couldn't kill sudo process (PID=#{pid})"
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 #{file}") or
23
- raise(FileStillExists, "Couldn't delete #{file}")
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
- raise SudoFailed unless system "#{SUDO_CMD} -E #{RUBY_CMD} -e ''"
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: '', load_gems: true) # :yields: sudo
29
- sudo = new(ruby_opts: ruby_opts, load_gems: load_gems).start!
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: '', load_gems: true)
48
+ def initialize(ruby_opts: '', config: nil)
49
+ @config = config || Sudo.configuration
52
50
  @proxy = nil
53
- @socket = "/tmp/rubysu-#{Process.pid}-#{object_id}"
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 == true
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
- @sudo_pid = spawn(
66
- "#{SUDO_CMD} -E #{RUBY_CMD} -I#{LIBDIR} #{@ruby_opts} #{SERVER_SCRIPT} #{@socket} #{Process.uid}"
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: 1){File.exist? @socket}
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
- true if (
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
- (Gem.loaded_specs.keys - @proxy.loaded_specs.keys)
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
- gem_name = prospect.dup
141
- begin
142
- loaded = @proxy.proxy(Kernel, :require, gem_name)
143
- # puts "Loading Gem: #{gem_name} => #{loaded}"
144
- rescue LoadError, NameError => e
145
- old_gem_name = gem_name.dup
146
- gem_name.gsub!('-', '/')
147
- retry if old_gem_name != gem_name
148
- end
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
@@ -1,2 +1,9 @@
1
+ require 'sudo/configuration'
1
2
  require 'sudo/wrapper'
2
3
 
4
+ module Sudo
5
+ # Convenience method for simple root operations
6
+ def self.as_root(**options, &block)
7
+ Wrapper.run(**options, &block)
8
+ end
9
+ end
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guido De Rosa
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-07-10 00:00:00.000000000 Z
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: '12.0'
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: '12.0'
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: '0'
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: '0'
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.3'
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.1.6
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: []