Install
One line, and nothing hidden
Every pet on hatchery.world installs into Codex with one line in a terminal. Before you run it, here is everything it does, and the whole script, so you can check for yourself.
What it does
- Downloads the pet's checksum list, then its two files,
pet.jsonand its sprite sheet, from hatchery.world over HTTPS, into a temporary folder. - Checks every file against its SHA-256 checksum, and stops before installing anything if one differs.
- Moves the pet into Codex's pets folder,
${CODEX_HOME:-$HOME/.codex}/pets/<pet>/, the folder OpenAI's pet format names.
What it never does
- Ask for a password or use sudo. It refuses to run as root.
- Run anything it downloads. A pet is a picture and a short description, nothing more.
- Touch anything outside that one pet's folder, or replace a pet you already have unless you add
--force.
Three ways to install
One line
curl -fsSL https://hatchery.world/install.sh | sh -s -- bookworm
Read it first
curl -fsSLO https://hatchery.world/install.sh
less install.sh
sh install.sh bookworm
By hand
Download a pet's zip from its page and unzip it into ~/.codex/pets/. Each zip's checksum is on its page.
The installer works on macOS and Linux. On Windows, use the zip.
The script
This is the exact file served at https://hatchery.world/install.sh: 5 KB, SHA-256 3a7e7bfb2748f5b686043ffa1e538678dfc8fc3315fbe333b4ac2f442893e144. To check a copy you downloaded: shasum -a 256 install.sh. Each pet's checksums are at https://hatchery.world/<pet>/install.sha256, and all of them are in pets.json.
#!/bin/sh
# The Hatchery installer. https://hatchery.world/install/
#
# Puts one Hatchery pet into Codex. It is short on purpose, so you can read all of it before you run it.
#
# curl -fsSL https://hatchery.world/install.sh | sh -s -- bookworm
# sh install.sh bookworm (after downloading this file and reading it)
# sh install.sh bookworm --force (replace a copy that is already installed)
#
# What it does, and nothing else:
# 1. Downloads the pet's checksum list, then its two files (pet.json and its sprite sheet) from
# hatchery.world over HTTPS, into a temporary folder.
# 2. Checks every file against its SHA-256 checksum, and stops before installing anything if one differs.
# 3. Moves the pet into Codex's pets folder: ${CODEX_HOME:-$HOME/.codex}/pets/<pet>/
#
# It never asks for a password, never uses sudo, never runs anything it downloads, and never touches
# anything outside that one pet's folder. A pet that is already installed is left alone unless you add --force.
set -eu
SITE="${HATCHERY_URL:-https://hatchery.world}" # change only to test against a local copy of the site
USAGE="usage: sh install.sh <pet> [--force] for example: sh install.sh bookworm"
say() { printf '%s\n' "$*"; }
fail() { printf 'hatchery: %s\n' "$*" >&2; exit 1; }
PET=""
FORCE=0
for arg in "$@"; do
case "$arg" in
--force) FORCE=1 ;;
-h | --help) say "$USAGE"; exit 0 ;;
-*) fail "unknown option $arg. $USAGE" ;;
*) [ -z "$PET" ] || fail "one pet at a time. $USAGE"; PET="$arg" ;;
esac
done
# A pet's name is lower-case letters, digits and hyphens, as in its address on hatchery.world.
case "$PET" in
"" | -* | *[!a-z0-9-]*) fail "give the pet's name as it appears in its hatchery.world address. $USAGE" ;;
esac
[ "$(id -u)" -ne 0 ] || fail "run this as yourself, not with sudo or as root: Codex's pets live in your own folder"
command -v curl >/dev/null 2>&1 || fail "curl is needed to download the pet, and it isn't installed"
if command -v sha256sum >/dev/null 2>&1; then
digest() { sha256sum "$1" | cut -d ' ' -f 1; }
elif command -v shasum >/dev/null 2>&1; then
digest() { shasum -a 256 "$1" | cut -d ' ' -f 1; }
elif command -v openssl >/dev/null 2>&1; then
digest() { openssl dgst -sha256 -r "$1" | cut -d ' ' -f 1; }
else
fail "no SHA-256 tool found (sha256sum, shasum or openssl), so the download can't be checked"
fi
case "$SITE" in
https://*) SECURE="--proto =https --tlsv1.2" ;; # HTTPS only, including redirects
*) SECURE="" ;;
esac
fetch() { # fetch <path on the site> <file>
# shellcheck disable=SC2086 # $SECURE is two options, or none
curl -fsSL --retry 2 $SECURE -o "$2" "$SITE/$1"
}
PETS="${CODEX_HOME:-$HOME/.codex}/pets"
DEST="$PETS/$PET"
if [ -e "$DEST" ] && [ "$FORCE" -ne 1 ]; then
say "$PET is already installed, in $DEST. Left as it is."
say "To replace it with the copy on hatchery.world, run the same command with --force at the end."
exit 0
fi
TMP=$(mktemp -d "${TMPDIR:-/tmp}/hatchery.XXXXXX") || fail "could not make a temporary folder"
STAGE=""
trap 'rm -rf "$TMP" ${STAGE:+"$STAGE"}' EXIT
trap 'exit 1' INT TERM HUP
say "Installing $PET from $SITE"
fetch "$PET/install.sha256" "$TMP/install.sha256" || fail "there is no pet called $PET on $SITE (or the site can't be reached)"
mkdir "$TMP/$PET"
COUNT=0
while read -r WANT NAME; do
case "$NAME" in "" | .* | *[!A-Za-z0-9._-]*) fail "the checksum list names an unexpected file; nothing was installed" ;; esac
case "$WANT" in *[!0-9a-f]*) fail "the checksum list is not well formed; nothing was installed" ;; esac
[ "${#WANT}" -eq 64 ] || fail "the checksum list is not well formed; nothing was installed"
fetch "$PET/$NAME" "$TMP/$PET/$NAME" || fail "could not download $NAME; nothing was installed"
[ "$(digest "$TMP/$PET/$NAME")" = "$WANT" ] || fail "$NAME does not match its checksum; nothing was installed"
say " $NAME sha256 $WANT checked"
COUNT=$((COUNT + 1))
done < "$TMP/install.sha256"
[ "$COUNT" -ge 2 ] && [ -f "$TMP/$PET/pet.json" ] || fail "the download is incomplete; nothing was installed"
grep -q "\"id\"[[:space:]]*:[[:space:]]*\"$PET\"" "$TMP/$PET/pet.json" || fail "pet.json is for a different pet; nothing was installed"
mkdir -p "$PETS"
STAGE="$PETS/.hatchery-$PET-$$" # assembled beside its final place, then renamed in one step
cp -R "$TMP/$PET" "$STAGE"
if [ -e "$DEST" ]; then rm -rf "$DEST"; fi # reached only with --force
mv "$STAGE" "$DEST"
STAGE=""
say "Installed $PET in $DEST"
say "In Codex: Settings > Appearance > Pets. If $PET isn't listed yet, quit Codex and open it again."