What Does the aqua install Command Do? A Technical Deep Dive into Aqua’s Core Installation Pipeline

The aqua install command reads your declarative aqua.yaml configuration, resolves tool versions from remote registries, cryptographically verifies downloaded binaries, and creates symbolic links in your local bin directory.

The aqua install command (commonly aliased as aqua i) is the central operation of the aquaproj/aqua declarative CLI version manager. When executed, it transforms your configuration into concrete, runnable binaries on your system. This article examines the complete execution flow based on the v2 source code, detailing how the aqua install command handles CLI arguments, initializes controllers, resolves configurations, and securely installs packages.

How the aqua install Command Works: Step-by-Step Execution Flow

The implementation in aquaproj/aqua follows a precise nine-step pipeline from CLI parsing to binary installation.

1. CLI Argument Parsing

In pkg/cli/install/command.go, the install sub-command extracts flags such as --only-link, --all, -t/--tags, and --exclude-tags. It constructs a config.Param struct that controls the installation behavior, including parallelism limits and tag filters.

2. Controller Initialization

The controller.InitializeInstallCommandController function in pkg/controller/install/controller.go uses Google Wire to inject dependencies. This includes the registry installer, package installer, filesystem abstraction (afero.Fs), runtime detector, and policy reader, creating a fully wired InstallController.

3. Binary Directory and Proxy Setup

In pkg/controller/install/install.go, the mkBinDir function creates the <root>/bin directory (and removes <root>/bat on Windows). Then InstallProxy fetches the tiny aqua-proxy binary that Aqua uses for self-updates and proxying tool execution.

4. Policy File Loading

Global and per-project policy YAML files are read and merged by the policy reader. These policies can enforce checksum validation, restrict allowed registries, or enable specific verification methods.

5. Configuration File Resolution

The configFinder.Finds method walks the working directory hierarchy to discover all aqua.yaml files (or custom paths specified via flags) that need processing.

6. Registry and Package Installation Loop

For each discovered aqua.yaml, the controller reads and validates the configuration, opens or creates the checksum store for integrity tracking, installs remote registries (downloads registry definitions if needed), and triggers package installation for each tool defined.

7. Global Configuration Processing

When the --all flag is set, Aqua also processes global configuration files listed in param.GlobalConfigFilePaths, installing tools defined at the user or system level.

8. Individual Package Installation

The installpackage.Installer.InstallPackages method in pkg/installpackage/installer.go handles the core logic for each tool. It creates symbolic or hard links in the bin directory (unless --only-link is false), downloads the asset from the source, verifies signatures using cosign, SLSA, minisign, or GitHub Artifact Attestations, validates checksums, extracts archives if necessary, and updates the vacuum timestamp for cleanup tracking.

9. Completion and Error Handling

If any package fails installation, the controller propagates errInstallFailure. Otherwise, the command finishes successfully, leaving all tools linked and ready for execution.

Architectural Highlights of the aqua install Command

The aqua install implementation demonstrates several sophisticated design patterns that ensure reliability and security.

Dependency Injection via Google Wire

The controller layer uses Wire to assemble complex dependencies, making the codebase testable and modular. The InstallController receives interfaces for filesystem operations (afero.Fs), HTTP clients, and policy readers rather than concrete implementations.

Filesystem Abstraction

All file operations route through afero.Fs, enabling in-memory testing and platform-independent path handling across Linux, macOS, and Windows.

Parallel Installation with Error Groups

Package downloads execute concurrently using errgroup.Group with configurable parallelism (param.MaxParallelism), significantly speeding up bulk installations while maintaining error aggregation.

Cryptographic Verification Pipeline

Aqua supports multiple verification backends:

  • cosign signatures (pkg/cosign/)
  • SLSA provenance (pkg/slsa/)
  • minisign signatures (pkg/minisign/)
  • GitHub Artifact Attestations (pkg/ghattestation/)

Policy-Driven Security

Global and project-level policy files can enforce mandatory checksum verification, restrict allowed registries, or filter packages by tags, ensuring compliance in enterprise environments.

Practical Usage Examples for aqua install

Command-Line Usage

Install all packages defined in the nearest aqua.yaml:

aqua install

Install only packages tagged with ci:

aqua i -t ci

Create symbolic links without downloading (useful when binaries already exist in the cache):

aqua i -l

Install packages from both local and global configurations:

aqua i -a

Programmatic Integration

Embed Aqua installation logic in your Go application:

package main

import (
	"context"
	"net/http"
	"log/slog"
	"os"

	"github.com/aquaproj/aqua/v2/pkg/cli/util"
	"github.com/aquaproj/aqua/v2/pkg/config"
	"github.com/aquaproj/aqua/v2/pkg/controller"
	"github.com/aquaproj/aqua/v2/pkg/runtime"
)

func main() {
	ctx := context.Background()
	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

	// Build parameters (equivalent to CLI flag parsing)
	param := &config.Param{}
	if err := util.SetParam(&cliargs.GlobalArgs{}, logger, param, "v2.6.0"); err != nil {
		panic(err)
	}
	param.OnlyLink = false
	param.All = true

	// Initialize the install controller with dependency injection
	ctrl, err := controller.InitializeInstallCommandController(
		ctx, logger, param, http.DefaultClient, runtime.New())
	if err != nil {
		panic(err)
	}

	// Execute installation
	if err := ctrl.Install(ctx, logger, param); err != nil {
		panic(err)
	}
}

Conditional Download Logic

When --only-link is specified, the installer skips downloading as shown in pkg/installpackage/installer.go:

if is.onlyLink {
    logger.Debug("skip downloading the package", "only_link", true)
    return nil // only linking was requested
}

Key Source Files in the aqua install Pipeline

Understanding the repository structure helps when debugging or extending Aqua:

  • pkg/cli/install/command.go — Defines the install sub-command, parses flags, builds config.Param, and invokes the controller.
  • pkg/controller/install/controller.go — Wire-in for the install controller; holds references to registry and package installers.
  • pkg/controller/install/install.go — Implements the high-level workflow: bin directory creation, proxy installation, policy loading, configuration discovery, and orchestrating registry and package installation.
  • pkg/installpackage/installer.go — Core installer that creates links, downloads assets, verifies signatures and checksums, extracts archives, and updates vacuum timestamps.
  • pkg/policy/reader.go — Reads and merges policy YAML files that influence verification behavior and security constraints.

Summary

The aqua install command serves as the central mechanism for transforming declarative configuration into executable tooling. Key takeaways include:

  • Nine-step pipeline: From CLI parsing in pkg/cli/install/command.go through cryptographic verification to final link creation.
  • Dependency injection: Google Wire assembles the InstallController with modular components for testing and flexibility.
  • Security-first design: Built-in support for cosign, SLSA, minisign, and GitHub Attestations verification, plus policy-driven enforcement.
  • Flexible execution: Supports --only-link for cache-only operations, tag filtering, and parallel downloads with configurable concurrency.

Frequently Asked Questions

What is the difference between aqua install and aqua cp?

The aqua install command installs packages into the central Aqua root directory (typically ~/.local/share/aquaproj-aqua) and creates symbolic links in the bin directory. In contrast, aqua cp copies the actual binary files to a destination directory you specify, useful for Docker images or isolated environments where symlinks are not desired.

How does the aqua install command verify package security?

During step 8 of the installation pipeline, installpackage.Installer.InstallPackages invokes multiple verification backends based on your aqua.yaml configuration. It supports cosign signatures, SLSA provenance attestations, minisign signatures, and GitHub Artifact Attestations. Additionally, policy files can enforce mandatory checksum validation and restrict which registries are trusted.

When you run aqua install --only-link (or aqua i -l), the command skips the download and verification phases entirely. It only creates symbolic or hard links in the bin directory pointing to binaries that already exist in the Aqua cache. This is useful for restoring links after clearing your bin directory or when working offline with pre-populated caches.

Where does aqua install store downloaded binaries?

Aqua stores downloaded binaries in a versioned cache directory within the Aqua root path (by default ~/.local/share/aquaproj-aqua). The exact path follows the pattern <root>/pkgs/<registry>/<package>/<version>/. The aqua install command then creates symbolic links from <root>/bin/<tool> to the actual binary in the cache, keeping your system PATH clean while maintaining multiple versions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →