Installation Strategies Redesign #16

Open
opened 2026-06-26 08:37:27 +05:30 by sortedcord · 0 comments
Owner

What Bootstrap was meant to do

Just as a brief introduction, the linux ecosystem is notoriously fragmented when it comes to installing packages. This creates a inconsistent experience for users setting up their environments.

  • Each distribution uses its own package managers and packages tend to have different names depending on the OS
  • Official repos are often hopelessly out of date. In order to get the latest features, users are forced to bypass the package manager and manually download binaries
  • Many tools aren't even in official repos at all and ask users to pipe a random script into their shell. These scripts arbitrarily modify .bashrc and clutter the home directory and also leave orphaned files upon installation apart from also posing a security risk by obscuring what they actually do.

So ultimately, what we are left with is a tangled mess of native packages, stray binaries and dirty shell config files making it impossible to systematically and cleanly uninstall software or migrate setups to new machines.

Bootstrap exists to unify this broken experience at least for the tools you use by providing a single consistent interface that abstracts messy installation details.

And to achieve this abstraction, I had designed bootstrap to act as a "shadow package manager". Basically, when an installer (like b ware yazi) needs a system dependency (like fzf), bootstrap installs it via the OS package manager and tracks it using a text file.

So, bootstrap/packages/fzf would look something like:

yazi
zoxide
nvim

When a tool is uninstalled, its name is removed from the file. If the file empties, bootstrap uninstalls fzf from the system.

Then we have the ghost state problem #9 which is a direct result of the shadow registry system.

From what I can see is that installers mainly fall into three categories. And the right way to go about this is to acknowledge this categorization instead of forcing everything into one model:

Category Examples Current Approach
Single static binaries lazygit, bat (on Debian), zoxide, starship, yazi Download from GitHub, drop into ~/.local/bin
Managed runtimes/toolchains node (via nvm), rust (via rustup), uv, pnpm Delegate to an upstream installer/manager
System services & packages docker, yay Must use the OS package manager

Installation Strategies

The best approach I could think of is to formalize these three strategies and unify them behind a single source of truth.

We replace the scattered per-package text files with just 1 registry.json that tracks what's installed, how it was installed, and where it lives. Each tool declares its own installation strategy and the registry records the outcome.

{
        "tools": {
          "lazygit": {
            "strategy": "binary",
            "version": "0.62.2",
            "bin": "~/.local/bin/lazygit",
            "source": "github:jesseduffield/lazygit",
            "installed_at": "2026-06-25T22:00:00Z"
          },
          "bat": {
            "strategy": "binary",
            "version": "0.26.1",
            "bin": "~/.local/bin/bat",
            "source": "github:sharkdp/bat",
            "installed_at": "2026-06-25T22:00:00Z"
          },
          "docker": {
            "strategy": "system",
            "packages": ["docker.io"],
            "installed_at": "2026-06-25T22:00:00Z"
          },
          "node": {
            "strategy": "managed",
            "manager": "nvm",
            "installed_at": "2026-06-25T22:00:00Z"
          }
        }
      }

The Three Strategies

1. binary — For standalone CLI tools (lazygit, bat, starship, zoxide, yazi, asciinema)

• Install: Download the latest release from GitHub → extract → place in $BOOTSTRAP_DIR/bin/
• Uninstall: rm the binary, remove the registry entry
• Update: Re-download latest, overwrite the binary
• No reference counting needed: Each binary is wholly owned by bootstrap

Important

Use $BOOTSTRAP_DIR/bin/ instead of ~/.local/bin/ to avoid conflicting with the user's manually-installed tools. Add this directory to PATH via the existing env.d/ mechanism.

2. managed — For runtimes with their own version managers (node/nvm, rust/rustup, uv, pnpm)

• Install: Delegate to the upstream installer (nvm, rustup, etc.)
• Uninstall: Delegate to the upstream uninstaller
• Update: Delegate to the upstream update mechanism
• No reference counting needed: The upstream manager owns everything

3. system — For things that must be OS packages (docker, build-essential, git)

• Install: Use pkg_install as today
• Uninstall: Use pkg_remove as today
• Reference counting: Still needed here, but only here — and there are far fewer of these tools,
making the surface area for ghost-state bugs much smaller

## What Bootstrap was meant to do Just as a brief introduction, the linux ecosystem is notoriously fragmented when it comes to installing packages. This creates a inconsistent experience for users setting up their environments. - Each distribution uses its own package managers and packages tend to have different names depending on the OS - Official repos are often hopelessly out of date. In order to get the latest features, users are forced to bypass the package manager and manually download binaries - Many tools aren't even in official repos at all and ask users to pipe a random script into their shell. These scripts arbitrarily modify `.bashrc` and clutter the home directory and also leave orphaned files upon installation apart from also posing a security risk by obscuring what they actually do. So ultimately, what we are left with is a tangled mess of native packages, stray binaries and dirty shell config files making it impossible to systematically and cleanly uninstall software or migrate setups to new machines. **Bootstrap exists to unify this broken experience at least for the tools you use by providing a single consistent interface that abstracts messy installation details.** And to achieve this abstraction, I had designed bootstrap to act as a "shadow package manager". Basically, when an installer (like `b ware yazi`) needs a system dependency (like fzf), bootstrap installs it via the OS package manager and tracks it using a text file. So, `bootstrap/packages/fzf` would look something like: ``` yazi zoxide nvim ``` When a tool is uninstalled, its name is removed from the file. If the file empties, bootstrap uninstalls `fzf` from the system. Then we have the ghost state problem #9 which is a direct result of the shadow registry system. From what I can see is that installers mainly fall into three categories. And the right way to go about this is to acknowledge this categorization instead of forcing everything into one model: | Category | Examples | Current Approach | | --------------------------- | ------------------------------------------------ | -------------------------------------------- | | Single static binaries | lazygit, bat (on Debian), zoxide, starship, yazi | Download from GitHub, drop into ~/.local/bin | | Managed runtimes/toolchains | node (via nvm), rust (via rustup), uv, pnpm | Delegate to an upstream installer/manager | | System services & packages | docker, yay | Must use the OS package manager | # Installation Strategies The best approach I could think of is to formalize these three strategies and unify them behind a single source of truth. We replace the scattered per-package text files with just 1 `registry.json` that tracks what's installed, how it was installed, and where it lives. Each tool declares its own installation strategy and the registry records the outcome. ```json { "tools": { "lazygit": { "strategy": "binary", "version": "0.62.2", "bin": "~/.local/bin/lazygit", "source": "github:jesseduffield/lazygit", "installed_at": "2026-06-25T22:00:00Z" }, "bat": { "strategy": "binary", "version": "0.26.1", "bin": "~/.local/bin/bat", "source": "github:sharkdp/bat", "installed_at": "2026-06-25T22:00:00Z" }, "docker": { "strategy": "system", "packages": ["docker.io"], "installed_at": "2026-06-25T22:00:00Z" }, "node": { "strategy": "managed", "manager": "nvm", "installed_at": "2026-06-25T22:00:00Z" } } } ``` ### The Three Strategies #### 1. binary — For standalone CLI tools (lazygit, bat, starship, zoxide, yazi, asciinema) • Install: Download the latest release from GitHub → extract → place in $BOOTSTRAP_DIR/bin/ • Uninstall: rm the binary, remove the registry entry • Update: Re-download latest, overwrite the binary • No reference counting needed: Each binary is wholly owned by bootstrap > [!IMPORTANT] > Use `$BOOTSTRAP_DIR/bin/` instead of `~/.local/bin/` to avoid conflicting with the user's manually-installed tools. Add this directory to PATH via the existing env.d/ mechanism. #### 2. managed — For runtimes with their own version managers (node/nvm, rust/rustup, uv, pnpm) • Install: Delegate to the upstream installer (nvm, rustup, etc.) • Uninstall: Delegate to the upstream uninstaller • Update: Delegate to the upstream update mechanism • No reference counting needed: The upstream manager owns everything #### 3. system — For things that must be OS packages (docker, build-essential, git) • Install: Use pkg_install as today • Uninstall: Use pkg_remove as today • Reference counting: Still needed here, but only here — and there are far fewer of these tools, making the surface area for ghost-state bugs much smaller
sortedcord added this to the Release v3 milestone 2026-06-26 08:37:27 +05:30
sortedcord added a new dependency 2026-06-26 10:09:45 +05:30
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Reference: sortedcord/bootstrap#16