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.
- ✅ Working:
install
,uninstall
,rollback
,history
,list
,vulndb update
,check-health
- 🚧 In Progress:
draft
andbuild
(functional but incomplete) ⚠️ Untested:update
,upgrade
,info
,search
,reposync
,cleanup
,audit
,self-update
- 🔄 Atomic Updates - All package operations are atomic with instant rollback
- 📦 Content-Addressed Storage - Deduplication via BLAKE3 hashing
- 🏗️ Hermetic Builds - Reproducible builds in isolated environments
- 🔐 Security First - Minisign signatures, SBOM generation, CVE scanning
- 🚀 Fast & Parallel - Concurrent downloads and installations
- 🎯 Single Prefix - Clean design with everything in
/opt/pm/live/
- 🐍 Python-Style Versions - Familiar version constraints (
>=1.2.0,<2.0.0
) - 📝 YAML Recipes - Declarative, staged build definitions
- macOS with Apple Silicon
- Rust 1.87.0 or later
- SQLite 3.x
- sudo access for
/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
# 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 state history
sps2 history
# 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:
- Content-Addressed Store - All package files are stored by their BLAKE3 hash
- State Directories - Each system state is a complete filesystem tree
- Atomic Swaps - Updates use APFS clones and atomic renames
- Instant Rollback - Previous states are preserved and can be restored instantly
/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)"
- Object Storage: Files stored by hash in
/opt/pm/store/objects/[first-2-chars]/[full-64-char-hash]
- Package Storage: Package Metadata stored in
/opt/pm/store/packages/[package-hash]/
with files:manifest.toml
- Package metadata and file listfiles.json
- Detailed file informationsbom.spdx.json
- Software Bill of Materials
- Deduplication: Multiple packages can reference the same file hash (content deduplication)
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
- Architecture Overview - Detailed system design
- Build Script Documentation - YAML recipe format reference
- Contributing Guide - How to contribute
- Code of Conduct - Community guidelines
- Packages are signed with Minisign // TODO
- All downloads are verified against BLAKE3 hashes BLAKE3
- SBOM (Software Bill of Materials) generated for every package
- Offline CVE scanning via integrated vulnerability database
BSD 3-Clause License. See LICENSE.md for details.
sps2 stands on the shoulders of giants:
- moss & boulder AerynOS - atomic updates, content-addressed storage, stateful package management
- Homebrew - Inspiration for macOS-native packaging
- Ansible - Inspiration for YAML format and shell/command distinction
- Starlark - Previously used for build recipes
Building the future of package management on macOS, one atomic update at a time.