Skip to main content

A language and command line utility for bootstrapping the setup of personal OS installs.

Project description

Booty is a language and command line utility for bootstrapping the setup of personal OS installs. Its goal is to execute all of the things that you do after your first boot, like install packages through your package manager, configure your shell, setup your terminal tools/IDE, and install SDKs for various programming languages, without you having to download everything manually and run various scripts in the right order.

You model your setup process as Targets and declare which ones depend on which ones, then execute booty to set everything up, if everything isn't already setup. Your install.booty file will specify various setup and is_setup methods that booty uses to set your system up, and to test if everything was actually setup.

Install

Installing from pip

pip install booty-cli

Or download the appropriate binary from the latest release. This script just downloads it for you and marks it as executable, but you should move it somewhere on your path. The script will drop it in your pwd.

Linux

curl https://raw.githubusercontent.com/naddeoa/booty/master/scripts/booty-download-linux.sh | bash

Mac x86

curl https://raw.githubusercontent.com/naddeoa/booty/master/scripts/booty-download-mac-x86.sh | bash

Mac Arm

curl https://raw.githubusercontent.com/naddeoa/booty/master/scripts/booty-download-mac-universal.sh | bash

Usage

You can run booty --help to see all of the options. You'll generally cd to a directory with an install.booty file and run booty. You'll see a table that shows the status of all of your targets and you'll be prompted to install all of the missing ones, which will display in real-time as a second table with a row for each target getting setup.

Usage: booty [OPTIONS]

Options:
  -c, --config TEXT   Path to the booty file. Defaults to ./install.booty
  -s, --status        Check the status of all known targets
  -i, --install       Install all uninstalled targets
  -d, --debug         See the AST of the config file
  -l, --log-dir TEXT  Where to store logs. Defaults to ./logs
  --no-sudo           Don't allow booty to prompt with sudo -v. Instead, you
                      can manually run sudo -v before using booty to cache
                      credentials for any targets that use sudo. By default,
                      booty runs sudo -v upfront if you use sudo in any
                      targets.
  -y, --yes           Don't prompt for confirmation
  --help              Show this message and exit.

Testing/Dry Runs

Setting your system up is inherently side effect prone. Every command from apt to pip is probably doing something to some part of your system that you didn't realize. If you want to execute your install.booty file without worrying about what might go wrong before you use it for real then the best way to do that is via Docker (which is how this repo does "integration" testing).

There is a public docker image (naddeoa/booty:ubuntu22.04) that you can base your own Dockerfile on that mimics what a fresh install looks like for Ubuntu 22.04 users. You can create an image that does roughly what you would do to your system to get the booty command working, and then run it and make sure everything works as expected. The image's user is named myuser and its password is password. You can see how its created in Dockerfile.base.

This Dockerfile is what I use to test my own installs.

from naddeoa/booty:ubuntu22.04

RUN sudo apt-get install -y curl # Just like IRL, install curl first
RUN curl https://raw.githubusercontent.com/naddeoa/booty/master/scripts/booty-download-linux.sh | bash # MANUAL install booty without pip

# Copy my ssh keys in so I can clone my private repos
COPY ./ssh ./.ssh
RUN sudo chown -R myuser:myuser ./.ssh
RUN chmod 600 .ssh/id_rsa

COPY ./examples/install.booty ./

# This would be on my path IRL
ENV PATH="/home/myuser/.local/bin:${PATH}"

CMD ["bash"]

Then you build and run the image, and execute booty.

# Build the image
docker build . -t my-booty-test

# Run it
docker run --rm -it --entrypoint bash my-booty-test

## Inside the image now
./booty_linux_x86_64 -y

You can't test everything here but you can get pretty far. One thing I can't test here, for example, is chsh because that requires logging out, but I gets me confident enough to use it on a fresh install and know I won't mess things up. In practice, you do this sort of a test infrequently, when you initially create your install.booty file or when you make big changes to it.

Syntax Support

See naddeoa/vim-booty for the syntax plugin.

Booty Language

For full examples, check out the examples folder in github.

The Booty language is a small language inspired primarily by make, with some additions to better address the problem space. There are a few different types of entities.

Recipes

Recipes are reusable pieces of code that Targets and other Recipes can invoke. You can think of these as classes that implement a Recipe by defining the setup and is_setup methods. Recipes are invoked with parameters being separated by commas, like recipe_name(foo, bar). Parameters can optionally container whitespace as well, meaning recipe_name(a b c) is a single parameter a b c. The body of the recipe methods consists of one or more executable statements which can either be a line to invoke in shell or another recipe invocation.

This is a recipe named apt who's setup executes the shell command sudo apt-get install -y $((packages)), where $((packages)) will be substituted by the value of the paraemter packages when it's run. Its is_setup executes a multiline shell statement. The shell is invoked via bash -c ....

recipe apt(packages):
    setup: sudo apt-get install -y $((packages))
    is_setup:
      for pkg in $(echo $((packages)) | tr " " "\n"); do
        if ! dpkg -l "$pkg" &> /dev/null; then
          echo "$pkg is not installed."
          exit 1
        fi
      done

Targets

Targets are the main piece of a booty file. They invoke a recipe to accomplish their goal. This is a target named essentials that invokes the aptrecipe, passing it the parameter wget git vim autokey-gtk silversearcher-ag gawk xclip.

essentials: apt(wget git vim autokey-gtk silversearcher-ag gawk xclip)

Custom Targets

Custom Targets are similar to recipes, but they're defined inline. You would use this for something that didn't require any code shared between other recipes. The body of a Custom Target follows the same rules as a Recipe.

pyenv:
    setup: curl https://pyenv.run | bash
    is_setup: test -e ~/.pyenv/bin/pyenv

# Can also be multiline and invoke different commands/recipes.
pyenv:
    setup:
        apt(python3)
        curl https://pyenv.run | bash
    is_setup: test -e ~/.pyenv/bin/pyenv

Dependencies

Dependencies determine the execution order at setup time. The way that you declare this is flexible. You can use either the depends on syntax (->) or the depended upon syntax (<-). This is allowed because it's sometimes easier to manage a bunch of dependencies in a single line when they're logically related. Its personal preference for how you maintain your install.booty file.

# the target `essentials` is depended upon by foo and bar.
essentials <- foo bar

# the target `baz` depends on bar.
baz -> bar

Stdlib

Certain recipes are included in booty by default. These include the following.

recipe apt(packages):
    setup: sudo apt-get install -y $((packages))
    is_setup:
      for pkg in $(echo $((packages)) | tr " " "\n"); do
        if ! dpkg -l "$pkg" &> /dev/null; then
          echo "$pkg is not installed."
          exit 1
        fi
      done

recipe ppa(name):
    setup:
        sudo add-apt-repository $((name))
        sudo apt update
    is_setup: grep $((name)) /etc/apt/sources.list

recipe pipx(packages):
    setup: pipx install $((packages))
    is_setup: pipx list | grep $((packages))

recipe git(repo dist):
    setup: git clone $((repo)) $((dist))
    is_setup: test -d $((dist))

recipe git_shallow(repo dist):
    setup: git clone --depth 1 $((repo)) $((dist))
    is_setup: test -d $((dist))

recipe ln(src dst):
    setup:
        mkdir -p $(dirname $((dst)))
        ln -fs $((src)) $((dst))
    is_setup: test -L $((dst)) && test -e $((dst))

recipe cp(src dst):
    setup:
        mkdir -p $(dirname $((dst)))
        cp -r $((src)) $((dst))
    is_setup: test -e $((dst))

Any of these can be referenced from any booty.install file, and you can redifine them locally if you want different recipe logic that uses the same name.

FAQ

Why wasn't make good enough?

You can check the experiments folder to see what it looks like to implement the example booty file in make. It's a fair bit longer and clunkier for a few reasons:

  • Every target in make is designed to be an actual file. This great for building things but it means that you end up marking a lot of targets as .PHONY if they don't result in differences on the file system.
  • Implementing the --status flag from booty is very, very ugly. You'll just manually define two modes of each target and hard code a phony target that runs them all, with no particular attention paid to the output.
  • Same goes for running the setup code -- manually enumerating all targets as a phony target's dependency gets you the --install flag in booty.
  • Output in general isn't very digestible.
  • Each line of a target is executed in a new shell. Good for sandboxing commands but it gets very ugly when you want to do something like an if statement.

There are a lot of good things about make though. My favorite relevant parts are:

  • Easy, independent dependency specification
  • Plain old shell for each target definition.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

booty_cli-1.0.14.tar.gz (21.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

booty_cli-1.0.14-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

Details for the file booty_cli-1.0.14.tar.gz.

File metadata

  • Download URL: booty_cli-1.0.14.tar.gz
  • Upload date:
  • Size: 21.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.7.1 CPython/3.11.6 Linux/6.2.0-1018-azure

File hashes

Hashes for booty_cli-1.0.14.tar.gz
Algorithm Hash digest
SHA256 ae09f415627d59286f6bd78738b9d9c452738d10b5487933577f917f33886a02
MD5 9d89fe3a73d86786438d9ab048828951
BLAKE2b-256 a48f072395ff0b4d264eccea4b0f9fba27d343b02dae3dfde2a6f4633e8d018e

See more details on using hashes here.

File details

Details for the file booty_cli-1.0.14-py3-none-any.whl.

File metadata

  • Download URL: booty_cli-1.0.14-py3-none-any.whl
  • Upload date:
  • Size: 21.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.7.1 CPython/3.11.6 Linux/6.2.0-1018-azure

File hashes

Hashes for booty_cli-1.0.14-py3-none-any.whl
Algorithm Hash digest
SHA256 4cea78dc2d437fca072124a3287a2b8a20c92553e213151bc19d6989a1d334df
MD5 fbfa37994d45a486a432a918a1f8397f
BLAKE2b-256 bf666683fc06685372d0b5942813c23fe05b8b3d72002e8d259de6481fe1682d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page