A modern, atomic package manager for macOS ARM64 with rollback capabilities and hermetic builds.
This project is in the early stages of development. It is not yet recommended for production use. The API, package format, and internal architecture are still subject to change. There is no public package repository yet.
install, uninstall, rollback, history, cleanup, list, vulndb update, check-healthdraft and build (functional but incomplete)update, upgrade, info, search, reposync, audit, self-update/opt/pm/live/>=1.2.0,<2.0.0)/opt/pm directory# Clone the repository
git clone https://github.com/yourusername/sps2.git
cd sps2
# Build the project
cargo build --release
# Run setup script (requires sudo)
sudo ./setup.sh
# Add to PATH in your shell config
echo 'export PATH="/opt/pm/live/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# Verify installation
sps2 --version
Some working .sp packages in the test_build/ dir. Layout is messy, will eventually clean that up.
# Install from repository (when available)
sps2 install jq
# Install specific version
sps2 install "jq==1.7"
# Install with version constraints
sps2 install "curl>=8.0.0,<9.0.0"
# Install from local .sp file
sps2 install ./package-1.0.0-1.arm64.sp
# Build and install
sps2 build my-package.yml
# Build without installing
sps2 build my-package.yml -o ./packages/
Use the draft command to automatically generate recipes:
# From a Git repository
sps2 draft -g "https://github.com/BurntSushi/ripgrep"
# From a source archive URL
sps2 draft -u "https://example.com/package-1.0.tar.gz"
# From a local directory
sps2 draft -p ./my-project
# From a local archive
sps2 draft -a ./my-archive.tar.gz
# Specify output file
sps2 draft -g "https://github.com/helix-editor/helix" -o helix.yml
Example recipe for ripgrep (generated by draft):
metadata:
name: ripgrep
version: "14.1.1"
description: "Line-oriented search tool that recursively searches for regex patterns"
license: "MIT"
homepage: "https://github.com/BurntSushi/ripgrep"
environment:
network: true # Cargo needs network for dependencies
source:
git:
url: "https://github.com/BurntSushi/ripgrep"
ref: "14.1.1"
build:
system: cargo
args: ["--release"]
Build with various options:
# Basic build
sps2 build ripgrep.yml
# Build with custom output directory
sps2 build ripgrep.yml -o ./packages/
# Build with maximum compression
sps2 build ripgrep.yml --max
# Build with custom job count
sps2 build ripgrep.yml -j 8
The pack command allows you to create packages from an already-built staging directory, skipping the build process:
# Package from a built directory with manifest
sps2 pack --directory ./build-output --manifest manifest.toml
# Package from a recipe without rebuilding (if already built)
sps2 pack --recipe myapp.yml --no-post
# Include SBOM in the package
sps2 pack --directory ./build-output --manifest manifest.toml --sbom sbom.json
# Custom output directory
sps2 pack --recipe myapp.yml -o ./packages/
# List installed packages
sps2 list
# Example output:
# ┌─────────┬─────────┬───────────┬─────────────┐
# │ Package ┆ Version ┆ Status ┆ Description │
# ╞═════════╪═════════╪═══════════╪═════════════╡
# │ bat ┆ 0.25.0 ┆ Installed ┆ - │
# ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
# │ helix ┆ 25.1.1 ┆ Installed ┆ - │
# ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
# │ ripgrep ┆ 14.1.1 ┆ Installed ┆ - │
# └─────────┴─────────┴───────────┴─────────────┘
# Show package info
sps2 info jq
# Search for packages
sps2 search rust
# Update packages (respects version constraints)
sps2 update
# Upgrade to latest versions
sps2 upgrade curl
# Uninstall packages
sps2 uninstall jq
# View recent state history (filtered by retention policy)
sps2 history
# Show all states (no availability filtering)
sps2 history --all
# Verify CAS availability for states (caps by config: state.history_verify_limit)
sps2 history --verify
# Override per-run verify limit
sps2 history --verify --limit 50
# Preview cleanup actions without deleting anything
sps2 cleanup --dry-run
# Perform cleanup based on policy (see config)
sps2 cleanup
# Verify current system; levels: quick, standard, full; scopes: live, store, all
sps2 verify --level standard --scope live
# Verify and attempt to heal discrepancies
sps2 verify --heal
# After a successful verify/heal, sync DB refcounts from the active state (one-off)
sps2 verify --sync-refcounts
# Example output:
# ┌────────────────────────┬─────────┬───────────┬──────────────────┬──────────┐
# │ State ID ┆ Current ┆ Operation ┆ Created ┆ Packages │
# ╞════════════════════════╪═════════╪═══════════╪══════════════════╪══════════╡
# │ 48b6f85f-78bb-4dc5-... ┆ * ┆ install ┆ 2025-06-13 16:12 ┆ 4 │
# ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
# │ a7be49ca-a976-4f75-... ┆ ┆ install ┆ 2025-06-13 16:11 ┆ 3 │
# └────────────────────────┴─────────┴───────────┴──────────────────┴──────────┘
# Rollback to previous state
sps2 rollback
# Rollback to specific state
sps2 rollback 48b6f85f-78bb-4dc5-9487-a8a60e97423b
# Clean up orphaned packages and old states
sps2 cleanup
# Check system health
sps2 check-health
# Update vulnerability database
sps2 vulndb update
# Show vulnerability database statistics
sps2 vulndb stats
# Audit installed packages for vulnerabilities
sps2 audit
# Audit specific package
sps2 audit --package curl
# Fail on critical vulnerabilities
sps2 audit --fail-on-critical
# Sync repository index
sps2 reposync
# Update sps2 itself
sps2 self-update
sps2 uses an innovative atomic update system:
/opt/pm/
├── live/ # Current active state (add /opt/pm/live/bin to PATH)
├── store/ # Content-addressed package storage
├── states/ # Historical states for rollback
└── state.sqlite # Package database
sps2 uses YAML format for package recipes with declarative, staged build definitions. See Build Script Documentation
metadata:
name: helix
version: "25.1.1"
description: "A post-modern modal text editor"
license: "MIT"
homepage: "https://github.com/helix-editor/helix"
environment:
network: true # Cargo needs network access
source:
git:
url: "https://github.com/helix-editor/helix"
ref: "25.1.1"
build:
system: cargo
args: ["--release"]
metadata:
name: pkgconf
version: "2.4.3"
description: "A system for managing library compile/link flags"
license: "ISC"
homepage: "https://github.com/pkgconf/pkgconf"
source:
git:
url: "https://github.com/pkgconf/pkgconf"
ref: "pkgconf-2.4.3"
build:
system: meson
args: ["--buildtype=release"]
# sps2 build recipe for curl
#
# This recipe builds curl from source archive.
# It enables support for OpenSSL, zlib, and nghttp2 (for HTTP/2).
metadata:
name: curl
version: "8.14.1"
description: "Command-line tool for transferring data with URLs"
license: "MIT"
homepage: "https://curl.se"
dependencies:
runtime:
- openssl
- zlib
- nghttp2
- brotli
- libssh2
- libidn2
- libpsl
environment:
defaults: true # Optimized flags for macOS ARM64
source:
fetch:
url: "https://github.com/curl/curl/releases/download/curl-8_14_1/curl-8.14.1.tar.bz2"
checksum:
sha256: "2893f7b7614192c2a4d8289f3d0009798a7c5f5d895011b5e4c0cf910c4a8b1e"
build:
system: cmake
args:
- "-DCMAKE_BUILD_TYPE=Release"
- "-GNinja"
- "-DBUILD_SHARED_LIBS=ON"
- "-DBUILD_STATIC_LIBS=OFF"
- "-DCURL_USE_OPENSSL=ON"
- "-DCURL_ZLIB=ON"
- "-DUSE_NGHTTP2=ON"
- "-DENABLE_IPV6=ON"
- "-DCURL_USE_LIBSSH2=ON"
- "-DUSE_LIBIDN2=ON"
- "-DCURL_BROTLI=ON"
- "-DCURL_USE_LIBPSL=ON"
- "-DBUILD_TESTING=OFF"
- "-DENABLE_CURL_MANUAL=OFF"
post:
patch_rpaths:
style: homebrew # curl needs absolute paths
install:
auto: true
The sls utility is a specialized debugging tool for exploring sps2's content-addressed storage system. It provides ls-like functionality for both the object store and package metadata.
# List all hash prefix directories (00-ff)
sls
# List objects with a specific hash prefix
sls ff
# List a specific object by partial hash
sls ffff5aa9
# Show full hash without filename mapping
sls --hash ff
# Long format with permissions and sizes
sls -l ff
# Example output:
# -r--r--r-- 2.11 KiB ffff5aa98bd51ec0 -> include/c++/15/fenv.h (gcc:15.1.0)
# -r--r--r-- 6.46 KiB ffff1d26e56791c5 -> share/man/man3/SSL_config.3ossl (openssl:3.5.0)
# Recursive listing of entire store
sls -R
# Find all files from a specific package
sls -R | grep "bat:0.25.0"
# Find all Python files
sls -R | grep "\.py "
# Find files by name pattern using ripgrep
sls -R | rg "libcurl.*dylib"
# Find all files from multiple packages
sls -R | rg "(gcc|clang|llvm):"
# List all packages with their names and versions
sls -p
# Example output:
# 0543d3aaf01da99f -> bat:0.25.0
# 098b0c7c800fd65f -> autoconf:2.72.0
# 0c0be36a37e74b78 -> cmake:4.0.3
# List specific package by hash prefix
sls -p 0543
# Long format for packages (shows metadata files)
sls -p -l
# Find packages by name pattern
sls -p | grep "python"
# Find all compiler packages using ripgrep
sls -p | rg "(gcc|clang|llvm|rust)"
/opt/pm/store/objects/[first-2-chars]/[next-2-chars]/[full-hash-hex]/opt/pm/store/packages/[package-hash]/ with files:manifest.toml - Package metadata and file listfiles.json - Detailed file informationsbom.spdx.json - Software Bill of MaterialsWe welcome contributions! Please see CONTRIBUTING.md for guidelines.
BSD 3-Clause License. See LICENSE.md for details.
sps2 stands on the shoulders of giants:
Building the future of package management on macOS, one atomic update at a time.