Skip to content

feat: portable, root-free installer refactor (dependency-free)#240

Closed
FNGarvin wants to merge 1 commit intorunpod:mainfrom
FNGarvin:feat-install-noroot
Closed

feat: portable, root-free installer refactor (dependency-free)#240
FNGarvin wants to merge 1 commit intorunpod:mainfrom
FNGarvin:feat-install-noroot

Conversation

@FNGarvin
Copy link

PR: Feature: Root-Free & Portable Installer

Description

This PR refactors the install.sh script to be entirely dependency-free and support non-privileged installation paths by default.

Key Changes

  • Dual-Mode Logic: Explicitly supports both installation types:
    • Root (sudo): Installs globally to /usr/local/bin for all users.
    • Non-Root: Installs to a user-space directory within $HOME.
  • Tiered Path Discovery: Automatically detects the most suitable user-space directory already in user $PATH (prioritizing ~/.local/bin, ~/bin, and ~/.bin).
  • Standardized Fallback: Defaults to creating ~/.local/bin only if no preferred paths are found, with automated $PATH guidance.
  • High-Visibility Guidance: Includes an impossible-to-miss alert for non-root users, providing clear instructions on how to use sudo for system-wide installation if desired.
  • Dependency-Free Logic: Removed the requirements for jq. Version extraction and URL construction now use portable, POSIX-compliant grep and sed logic. I'd humbly claim that by the time you already require curl, tar, and bash, it's not a stretch to require sed and grep.
  • multi-URL fallback With pending PR fix: correct download URL pattern in install script #235 in the balance, we've implemented a multi-URL fallback strategy. If eliminating support for the "old" naming convention is a priority, the fallback logic can be easily removed.
  • Verified Matrix: Installation has been verified in both Root and Non-Root modes using clean Alpine containers via the new Integration Matrix.

Integration Notes

  • This refactor ensures that the installer is as "light touch" as possible, making it safe for container-side automation and restricted user environments while retaining first-class support for MacOS via Homebrew.
image image

There's a review of the PR by Sourcery with diagrams and stuff here that you can peruse if you like.

Also, it looks like someone else is hacking on the brew stuff atm, so I didn't want to tamper with it. But there might be an issue w/ cask vs formula in the install line. Just FYI, I'm not sure.

Cheers!

@FNGarvin
Copy link
Author

FNGarvin commented Mar 1, 2026

Reviewer's Guide

Refactors the installer to be dependency-free, support non-root/user-space installs with dynamic install directory detection and URL fallbacks, and adds an automated Alpine-based integration test matrix in CI to validate both root and non-root installation and core CLI workflows.

Sequence diagram for root-free installer execution flow

sequenceDiagram
    actor User
    participant Shell as install_sh
    participant Brew as try_brew_install
    participant Env as detect_install_dir
    participant Sys as check_system_requirements
    participant Ver as fetch_latest_version
    participant Url as download_url_constructor
    participant Dl as download_and_install_cli

    User->>Shell: execute install.sh
    Shell->>Brew: try_brew_install
    alt macOS_with_Homebrew_success
        Brew-->>Shell: runpodctl installed via Homebrew
        Shell-->>User: exit 0
    else not_macOS_or_Homebrew_failed
        Brew-->>Shell: fallback to binary install
        Shell->>Env: detect_install_dir
        Env-->>Shell: INSTALL_DIR (root_or_user_space)
        Shell->>Sys: check_system_requirements
        Sys-->>Shell: verify wget tar grep sed
        Shell->>Ver: fetch_latest_version
        Ver-->>Shell: VERSION
        Shell->>Url: download_url_constructor
        Url-->>Shell: DOWNLOAD_URLS
        loop try_each_download_url
            Shell->>Dl: download_and_install_cli
            alt url_download_and_extract_success
                Dl-->>Shell: cli_installed_to_INSTALL_DIR
            else url_failed
                Dl-->>Shell: try_next_url
            end
        end
        Shell-->>User: report_success_or_failure
    end
Loading

File-Level Changes

Change Details Files
Refactor installer to support root and non-root installs with dynamic install directory selection and clearer messaging.
  • Add detect_install_dir to choose /usr/local/bin for root or a writable user PATH directory (preferring ~/.local/bin, ~/bin, ~/.bin) with fallback creation of ~/.local/bin.
  • Print a high-visibility notice when performing user-space installs, including sudo-based system-wide install guidance and PATH update hints.
  • Relax check_root to allow non-root execution while still emitting informational messaging about non-root mode.
install.sh
Make installer dependency-free by removing jq and package-manager logic and replacing it with portable shell tooling and multi-URL download fallbacks.
  • Remove REQUIRED_PKGS, generic package installation logic, and jq requirement from the installer.
  • Implement check_system_requirements to assert presence of wget, tar, grep, and sed, failing fast with a consolidated error if any are missing.
  • Replace jq-based GitHub API parsing with grep/sed to extract the latest tag_name from releases/latest.
  • Update download_url_constructor to normalize OS/arch (including macOS universal binary and linux arm64) and construct two possible asset URLs to support both old and new naming conventions.
  • Change download_and_install_cli to iterate over DOWNLOAD_URLS, retry downloads, validate extraction, and install runpodctl into the dynamically chosen INSTALL_DIR.
install.sh
Improve macOS support by preferring Homebrew installation when available and falling back to binary install otherwise.
  • Introduce try_brew_install to attempt runpodctl installation with Homebrew on macOS, respecting non-root Homebrew usage patterns and original user context.
  • Reorder main flow to attempt Homebrew install first, exiting early on success, before running the binary installer path.
  • Ensure Homebrew failures degrade gracefully to the standard download-and-extract installer path.
install.sh
Add an integration test suite that validates installation modes and core CLI functionality against the RunPod API.
  • Create tests/integration_suite.sh to run end-to-end checks for root and non-root installation, binary execution, pod lifecycle operations, file transfer via send/receive, and serverless endpoint lifecycle and job submission.
  • Implement reusable helpers (report_status, run_step) for colored status output, retry logic, and expected-fail handling (e.g., missing API key or privileges).
  • Use jq and curl within the test suite (not the installer) to interact with runpodctl JSON output and the RunPod API for verification, including cleanup of created pods and endpoints.
tests/integration_suite.sh
Add a GitHub Actions workflow that runs the integration matrix on Alpine in both root and non-root modes.
  • Create .github/workflows/integration-tests.yml to define an Integration Test Matrix workflow triggered on main pushes, selected feature branches, PRs, and manual dispatch.
  • Configure an Alpine container job that installs test dependencies, runs integration_suite.sh as root and as a non-root user, and passes through RUNPOD_API_KEY from secrets for API-backed tests.
  • Add a post-run cleanup step that, when runpodctl and RUNPOD_API_KEY are available, enumerates and deletes any leftover pods or serverless endpoints created during CI.
.github/workflows/integration-tests.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@TimPietruskyRunPod
Copy link
Member

hey, thanks for putting this together — the non-root install support and removing the jq dependency are things we'd genuinely like to have. really appreciate the effort here!

a few things that are blocking us from merging this as-is:

  1. merge conflicts with main — we recently merged fix: correct download URL pattern in install script #235 which rewrote the download URL logic in install.sh. this PR was branched before that, so there are conflicts that need resolving (a rebase onto current main would be needed).

  2. the multi-url fallback can be simplified — since fix: correct download URL pattern in install script #235 already fixed the URL pattern on main, the fallback URL isn't needed anymore. the second URL pattern also uses hyphens but historical releases used underscores, so it wouldn't match any real release anyway.

  3. this PR and feat: unified integration matrix with gated CI signaling #241 appear to be identical — same diff across the same 3 files. was this intentional or accidental? we'd suggest consolidating into one PR to keep things clean.

  4. the emergency cleanup in the CI workflow is a concern — it runs pod list | delete and serverless list | delete which would remove all resources on the account, not just ones created by the test. this would be dangerous if run against a production API key. scoping cleanup to only ci-created resources (e.g. by name prefix) would be much safer.

  5. hardcoded template ids and branch names — the template ids (bwf8egptou, wvrr20un0l) and the feature branch names in the workflow trigger should be parameterized.

we've created two separate issues to track the features you're proposing:

if you'd like to continue working on this, we'd suggest splitting the installer changes and the CI test suite into separate PRs based on current main. that would make review much easier. but no pressure at all — we're happy to pick these up ourselves too.

thanks again for the contribution! 🙏

@FNGarvin
Copy link
Author

FNGarvin commented Mar 3, 2026

Merged into #249

@FNGarvin FNGarvin closed this Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants