From 61bcc61e22b29353d61178b20af2a888537fd2e2 Mon Sep 17 00:00:00 2001 From: Camden Narzt Date: Mon, 14 Apr 2025 14:41:05 -0600 Subject: [PATCH 1/8] general readability --- lib/daemon_controller.rb | 63 ++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index 5781094..8b3a127 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -74,7 +74,7 @@ def initialize(identifier:, start_command:, ping_command:, pid_file:, log_file:, lock_file: nil, stop_command: nil, restart_command: nil, before_start: nil, start_timeout: 30, start_abort_timeout: 10, stop_timeout: 30, log_file_activity_timeout: 10, ping_interval: 0.1, stop_graceful_signal: "TERM", dont_stop_if_pid_file_invalid: false, - daemonize_for_me: false, keep_ios: nil, env: nil, logger: nil) + daemonize_for_me: false, keep_ios: nil, env: {}, logger: nil) @identifier = identifier @start_command = start_command @ping_command = ping_command @@ -140,7 +140,7 @@ def connect end if connection.nil? @lock_file.exclusive_lock do - if !daemon_is_running? + unless daemon_is_running? start_without_locking end connect_exception = nil @@ -335,9 +335,7 @@ def spawn_daemon def kill_daemon if @stop_command - if @dont_stop_if_pid_file_invalid && read_pid_file.nil? - return - end + return if @dont_stop_if_pid_file_invalid && read_pid_file.nil? result = run_command(@stop_command) case result @@ -356,8 +354,7 @@ def kill_daemon end def kill_daemon_with_signal(force: false) - pid = read_pid_file - if pid + if (pid = read_pid_file) if force Process.kill("SIGKILL", pid) else @@ -380,14 +377,11 @@ def daemon_is_running? end def read_pid_file - begin - pid = File.read(@pid_file).strip - rescue Errno::ENOENT - return nil - end + pid = File.read(@pid_file).strip if /\A\d+\Z/.match?(pid) pid.to_i end + rescue Errno::ENOENT end def delete_pid_file @@ -438,7 +432,7 @@ def no_activity?(seconds) end def pid_file_available? - File.exist?(@pid_file) && File.stat(@pid_file).size != 0 + File.exist?(@pid_file) && !File.empty?(@pid_file) end # This method does nothing and only serves as a hook for the unit test. @@ -571,10 +565,7 @@ def determine_lock_file(given_lock_file, identifier, pid_file) def run_command(command) if should_capture_output_while_running_command? # Create tempfile for storing the command's output. - tempfile = Tempfile.new("daemon-output") - tempfile.chmod(0o666) - tempfile_path = tempfile.path - tempfile.close + tempfile_path = Tempfile.create("daemon-output").tap(&:close).path spawn_options = { in: "/dev/null", @@ -598,10 +589,10 @@ def run_command(command) end pid = if @daemonize_for_me - Process.spawn(@env || {}, ruby_interpreter, SPAWNER_FILE, + Process.spawn(@env, ruby_interpreter, SPAWNER_FILE, command, spawn_options) else - Process.spawn(@env || {}, command, spawn_options) + Process.spawn(@env, command, spawn_options) end # run_command might be running in a timeout block (like @@ -615,9 +606,9 @@ def run_command(command) # it started successfully; if it didn't we'll know # that later by checking the PID file and by pinging # it. - return InternalCommandOkResult.new(pid, tempfile_path ? File.read(tempfile_path).strip : nil) + return InternalCommandOkResult.new(pid, tempfile_path && File.read(tempfile_path).strip) rescue Timeout::Error - return InternalCommandTimeoutResult.new(pid, tempfile_path ? File.read(tempfile_path).strip : nil) + return InternalCommandTimeoutResult.new(pid, tempfile_path && File.read(tempfile_path).strip) end child_status = $? @@ -629,7 +620,7 @@ def run_command(command) end ensure begin - tempfile.unlink if tempfile + File.unlink(tempfile_path) if tempfile_path && File.exist?(tempfile_path) rescue SystemCallError nil end @@ -706,7 +697,7 @@ def run_ping_command end end - if !can_ping_unix_sockets? + unless can_ping_unix_sockets? require "java" def ping_socket(host_name, port) @@ -714,16 +705,12 @@ def ping_socket(host_name, port) begin address = java.net.InetSocketAddress.new(host_name, port) channel.configure_blocking(false) - if channel.connect(address) - return true - end + return true if channel.connect(address) deadline = Time.now.to_f + 0.1 - while true + loop do begin - if channel.finish_connect - return true - end + return true if channel.finish_connect rescue java.net.ConnectException => e if /Connection refused/i.match?(e.message) return false @@ -734,9 +721,7 @@ def ping_socket(host_name, port) # Not done connecting and no error. sleep 0.01 - if Time.now.to_f >= deadline - return false - end + return false if Time.now.to_f >= deadline end ensure channel.close @@ -778,22 +763,18 @@ def ping_tcp_socket(sockaddr) end def ruby_interpreter - rb_config = if defined?(RbConfig) - RbConfig::CONFIG - else - Config::CONFIG - end + rb_config = defined?(RbConfig)? RbConfig::CONFIG : Config::CONFIG File.join( rb_config["bindir"], - rb_config["RUBY_INSTALL_NAME"] - ) + rb_config["EXEEXT"] + rb_config.values_at("RUBY_INSTALL_NAME","EXEEXT").join + ) end def timeoutable(amount, &block) Thread.handle_interrupt(Timeout::Error => :never) do start_time = monotonic_time result = Timeout.timeout(amount, Timeout::Error, &block) - [result, [monotonic_time - start_time, 0].max] + [result, (monotonic_time - start_time).clamp(0..)] end end From 27db275f47c12ce47f8234b6fee7124ca588334c Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Tue, 15 Apr 2025 11:31:08 +0200 Subject: [PATCH 2/8] Split up CI and release workflows for security reasons --- .github/workflows/ci.yaml | 67 ++-------------------------------- .github/workflows/release.yaml | 58 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 63 deletions(-) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4714277..c630615 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,9 +1,10 @@ name: CI on: - push: {} - pull_request_review: - types: [submitted, edited] + push: + branches: + - "*" + pull_request_target: {} jobs: test: @@ -50,63 +51,3 @@ jobs: - name: Build gem run: gem build daemon_controller.gemspec - - - name: Upload gem artifact - uses: actions/upload-artifact@v4 - with: - name: gem - path: "daemon_controller-*.gem" - - release: - runs-on: ubuntu-24.04 - needs: - - test - - lint-and-build - if: startsWith(github.ref, 'refs/tags/') - environment: release - permissions: - contents: write - id-token: write - attestations: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.4" - bundler-cache: true - - - name: Verify version number - id: get_version - run: | - VERSION_STRING=$(ruby -r ./lib/daemon_controller/version.rb -e "puts DaemonController::VERSION_STRING") - if ! [[ "$GITHUB_REF_NAME" =~ ^release- ]]; then - echo "Tag name must start with a 'release-'." - exit 1 - fi - if [[ "$GITHUB_REF_NAME" != "release-${VERSION_STRING}" ]]; then - echo "Tag version ($GITHUB_REF_NAME) does not match version.rb ($VERSION_STRING)" - exit 1 - fi - - - name: Download gem artifact - uses: actions/download-artifact@v4 - with: - name: gem - - - name: Create attestation - uses: actions/attest-build-provenance@v2 - with: - subject-path: "daemon_controller-*.gem" - - - name: Push gem to RubyGems - run: gem push daemon_controller-*.gem - env: - GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} - - - name: Create GitHub release - run: gh release create "$GITHUB_REF_NAME" *.gem --title "$GITHUB_REF_NAME" --notes-from-tag - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..fbf46b8 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,58 @@ +name: Release + +on: + push: + tags: + - "release-*" + +jobs: + release: + runs-on: ubuntu-24.04 + needs: + - lint-and-build + if: startsWith(github.ref, 'refs/tags/') + environment: release + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + + - name: Verify version number + id: get_version + run: | + VERSION_STRING=$(ruby -r ./lib/daemon_controller/version.rb -e "puts DaemonController::VERSION_STRING") + if ! [[ "$GITHUB_REF_NAME" =~ ^release- ]]; then + echo "Tag name must start with a 'release-'." + exit 1 + fi + if [[ "$GITHUB_REF_NAME" != "release-${VERSION_STRING}" ]]; then + echo "Tag version ($GITHUB_REF_NAME) does not match version.rb ($VERSION_STRING)" + exit 1 + fi + + - name: Build gem + run: gem build daemon_controller.gemspec + + - name: Create attestation + uses: actions/attest-build-provenance@v2 + with: + subject-path: "daemon_controller-*.gem" + + - name: Push gem to RubyGems + run: gem push daemon_controller-*.gem + env: + GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} + + - name: Create GitHub release + run: gh release create "$GITHUB_REF_NAME" *.gem --title "$GITHUB_REF_NAME" --notes-from-tag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 5aa4c49cc15fce84fff2d76dd3d911403f24a599 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 14 Nov 2025 16:27:36 +0100 Subject: [PATCH 3/8] ... --- lib/daemon_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index f304124..3f1d5fd 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -432,7 +432,7 @@ def no_activity?(seconds) end def pid_file_available? - File.exist?(@pid_file) && !File.empty?(@pid_file) + File.exist?(@pid_file) && !File.zero?(@pid_file) end # This method does nothing and only serves as a hook for the unit test. @@ -747,10 +747,10 @@ def ping_tcp_socket(sockaddr) end def ruby_interpreter - rb_config = defined?(RbConfig)? RbConfig::CONFIG : Config::CONFIG + rb_config = defined?(RbConfig) ? RbConfig::CONFIG : Config::CONFIG File.join( rb_config["bindir"], - rb_config.values_at("RUBY_INSTALL_NAME","EXEEXT").join + rb_config.values_at("RUBY_INSTALL_NAME", "EXEEXT").join ) end From 6764a31c1162ac041ca4f2daf0dc494e3447cd89 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 14 Nov 2025 16:30:22 +0100 Subject: [PATCH 4/8] ... --- lib/daemon_controller.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index 3f1d5fd..2bb8d54 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -747,10 +747,9 @@ def ping_tcp_socket(sockaddr) end def ruby_interpreter - rb_config = defined?(RbConfig) ? RbConfig::CONFIG : Config::CONFIG File.join( - rb_config["bindir"], - rb_config.values_at("RUBY_INSTALL_NAME", "EXEEXT").join + RbConfig::CONFIG["bindir"], + RbConfig::CONFIG.values_at("RUBY_INSTALL_NAME", "EXEEXT").join ) end From 14a37b3625475379435aa362c9849da64d626ca0 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 14 Nov 2025 16:31:27 +0100 Subject: [PATCH 5/8] ... --- lib/daemon_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index 2bb8d54..f5be294 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -757,7 +757,7 @@ def timeoutable(amount, &block) Thread.handle_interrupt(Timeout::Error => :never) do start_time = monotonic_time result = Timeout.timeout(amount, Timeout::Error, &block) - [result, (monotonic_time - start_time).clamp(0..)] + [result, [monotonic_time - start_time, 0].max] end end From 33fd67839eb7898c9beb65b0eb37e6cf0de9628c Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 14 Nov 2025 16:33:04 +0100 Subject: [PATCH 6/8] ... --- lib/daemon_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index f5be294..772b4de 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -549,7 +549,10 @@ def determine_lock_file(given_lock_file, identifier, pid_file) def run_command(command) if should_capture_output_while_running_command? # Create tempfile for storing the command's output. - tempfile_path = Tempfile.create("daemon-output").tap(&:close).path + tempfile = Tempfile.new("daemon-output") + tempfile.chmod(0o666) + tempfile_path = tempfile.path + tempfile.close spawn_options = { in: "/dev/null", From 67b2589a320d891b483ed4176750fafc76ff810b Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 14 Nov 2025 16:34:21 +0100 Subject: [PATCH 7/8] ... --- lib/daemon_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index 772b4de..084d7c6 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -607,7 +607,7 @@ def run_command(command) end ensure begin - File.unlink(tempfile_path) if tempfile_path && File.exist?(tempfile_path) + tempfile.unlink if tempfile rescue SystemCallError nil end From c46375e6b45b9020ca201e00c64fa06ab6bfe9d7 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 14 Nov 2025 16:34:48 +0100 Subject: [PATCH 8/8] ... --- lib/daemon_controller.rb | 62 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/daemon_controller.rb b/lib/daemon_controller.rb index 084d7c6..017948c 100644 --- a/lib/daemon_controller.rb +++ b/lib/daemon_controller.rb @@ -684,37 +684,7 @@ def run_ping_command end end - unless can_ping_unix_sockets? - require "java" - - def ping_socket(host_name, port) - channel = java.nio.channels.SocketChannel.open - begin - address = java.net.InetSocketAddress.new(host_name, port) - channel.configure_blocking(false) - return true if channel.connect(address) - - deadline = Time.now.to_f + 0.1 - loop do - begin - return true if channel.finish_connect - rescue java.net.ConnectException => e - if /Connection refused/i.match?(e.message) - return false - else - throw e - end - end - - # Not done connecting and no error. - sleep 0.01 - return false if Time.now.to_f >= deadline - end - ensure - channel.close - end - end - else + if can_ping_unix_sockets? def ping_socket(socket_domain, sockaddr) socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0) begin @@ -747,6 +717,36 @@ def ping_tcp_socket(sockaddr) rescue Errno::EAFNOSUPPORT ping_socket(Socket::Constants::AF_INET6, sockaddr) end + else + require "java" + + def ping_socket(host_name, port) + channel = java.nio.channels.SocketChannel.open + begin + address = java.net.InetSocketAddress.new(host_name, port) + channel.configure_blocking(false) + return true if channel.connect(address) + + deadline = Time.now.to_f + 0.1 + loop do + begin + return true if channel.finish_connect + rescue java.net.ConnectException => e + if /Connection refused/i.match?(e.message) + return false + else + throw e + end + end + + # Not done connecting and no error. + sleep 0.01 + return false if Time.now.to_f >= deadline + end + ensure + channel.close + end + end end def ruby_interpreter