Migrated from ~/Projects/spin
  • Python 80.1%
  • Shell 19.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-04-11 10:09:12 +01:00
installer v1 - Ruby 2026-04-11 10:09:12 +01:00
manager v1 - Ruby 2026-04-11 10:09:12 +01:00
.gitignore v1 - Ruby 2026-04-11 10:09:12 +01:00
README.md v1 - Ruby 2026-04-11 10:09:12 +01:00

Spin

Spin is a small Python package manager prototype for installing versioned .spin archives from local files or HTTPS URLs.

The CLI lives in manager/spin.py, and the system installer lives in installer/install.sh.

Status

This project is an early implementation. The current code supports:

  • Installing a package from a local .spin archive or a configured HTTPS URL
  • Removing and reinstalling packages
  • Installing a specific version when the repo URL can be versioned
  • Basic dependency resolution from package manifests
  • SHA-256 verification when hashes are present in repo metadata
  • Atomic install with rollback on failure
  • A local package database, cache, and generated lock file
  • Scaffolding a simple HTTPS package repository directory

Requirements

  • Python 3.10+ for the installer script
  • toml
  • openssl if you want to use the generated local repo activate script
  • Root privileges for most spin commands

The Python package metadata in pyproject.toml currently declares requires-python = ">=3.14", but the installer itself checks for Python 3.10 or newer.

Repository Layout

spin/
├── installer/
│   └── install.sh
├── manager/
│   └── spin.py
├── info/
│   └── README.md
├── pyproject.toml
└── uv.lock

Installation

Install system-wide with:

sudo ./installer/install.sh

Useful installer options:

./installer/install.sh --help
./installer/install.sh --dry-run
sudo ./installer/install.sh --prefix /usr/local
sudo ./installer/install.sh --uninstall

By default, the installer writes:

  • /usr/local/bin/spin
  • /usr/local/lib/spin/spin.py

Runtime Storage

Spin separates package payloads from its internal state.

State storage:

  • Linux: /var/lib/spin
  • macOS: /usr/local/var/spin
  • Windows: %PROGRAMDATA%\Spin

Installed package contents:

  • Linux: /usr/local/spin
  • macOS: /Applications/Spin
  • Windows: %ProgramFiles%\Spin

State storage contains:

  • cache/ for downloaded archives
  • db.toml for installed package metadata
  • repos.toml for configured package sources
  • spin.lock for installed version pins
  • .spin.mutex for a file-based lock

Usage

Spin uses subcommands such as spin help. It does not currently expose standard --help or --version flags.

Only spin help works without root. All other commands currently exit unless run as root.

spin help
sudo spin up <package|file.spin>
sudo spin down <name>
sudo spin redo <name>

sudo spin lift <name>
sudo spin drop <name> <version>

sudo spin ls
sudo spin info <name>
sudo spin status
sudo spin clean

sudo spin search <query>
sudo spin repo add <name> <https-url>
sudo spin repo make <directory>
sudo spin repo pull

Examples:

sudo spin up ./example.spin
sudo spin repo add hello https://packages.example.com/hello.spin
sudo spin up hello
sudo spin drop hello 1.2.3
sudo spin ls

Create a local repo scaffold:

sudo spin repo make /srv/spin-repo
cd /srv/spin-repo
./activate 8000
sudo spin repo add local https://localhost:8000

Package Format

A .spin file is a ZIP archive. Spin expects the archive to contain:

  • manifest.toml
  • files/

At minimum, the manifest must define:

name = "hello"
version = "1.0.0"

Dependencies are optional and are expressed as a table:

name = "hello"
version = "1.0.0"

[dependencies]
libfoo = ">=1.2.0"
libbar = "==2.0.0"

Supported dependency operators are:

  • >=
  • <=
  • ==

If no operator is used, Spin treats the dependency string as an exact value to compare.

Repository Metadata

Configured package sources are stored in repos.toml under a repos table.

For package installation, the current implementation uses a simple name-to-URL map:

Example:

[repos]
hello = "https://packages.example.com/hello.spin"
"hello.sha256" = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

For versioned installs with spin drop, Spin supports either:

  • A URL template containing {version}
  • A fallback convention where hello.spin becomes hello-<version>.spin

Example template:

[repos]
hello = "https://packages.example.com/hello-{version}.spin"
"hello-1.2.3.sha256" = "..."

Repository Scaffolding

spin repo make <dir> creates a minimal repository directory with:

  • packages/ for .spin archives
  • index.toml as a repository index template
  • activate as a small HTTPS file server launcher

The generated activate script:

  • Generates a self-signed TLS certificate on first run using openssl
  • Serves the repo over HTTPS with Python’s built-in HTTP server
  • Listens on port 8000 by default, or a custom port passed as the first argument

The generated index.toml template looks like this:

# Spin repository index
#
# Register packages like:
#
#   [packages.mypkg]
#   version = "1.0.0"
#   file    = "packages/mypkg-1.0.0.spin"
#   sha256  = "<hex digest>"
#
# Versioned URL templates are also supported:
#   url = "https://example.com/pkgs/mypkg-{version}.spin"

At the moment, this scaffold is only partially integrated with the rest of the CLI:

  • repo make creates the repo layout successfully
  • repo pull downloads <repo>/index.toml into the cache
  • up, drop, and dependency resolution do not currently read package entries out of those downloaded index files
  • In practice, installs still depend on direct name-to-URL entries in repos.toml

Notes And Current Limitations

  • Package and repo URLs must use HTTPS.
  • All commands except help currently require root because spin.py exits if os.geteuid() != 0.
  • spin help works, but spin --help and spin --version do not.
  • repo add stores a single name-to-string entry in repos.toml. That string is treated as a package URL by up, but as a repo base URL by repo pull.
  • repo pull downloads index.toml files into the cache, but does not merge them back into repos.toml or use them for install resolution.
  • Version comparison is string-based, not semantic-version aware.
  • search matches raw keys in repos.toml, so hash keys like foo.sha256 can also appear in results.
  • status counts raw repos.toml entries, so package URLs and hash entries both contribute to the displayed repo count.
  • The info/README.md file exists but is currently empty.

Development

Run the CLI directly during development:

python3 manager/spin.py help
sudo python3 manager/spin.py status
sudo python3 manager/spin.py repo make /tmp/my-repo

If toml is missing:

python3 -m pip install toml