#!/bin/sh
# MoleculeDesk installer for macOS and Linux.
#
# curl -fsSL https://moleculedesk.com/install.sh | sh
#
# Needs nothing installed beforehand: no Node.js, no npm, no admin rights. If a
# suitable Node.js (>= 20) is not found, a private copy is downloaded (checksum
# verified) and used only by MoleculeDesk. Everything lives under
# ~/.moldesk (or $MOLDESK_HOME): nothing outside it is touched except one line
# added to your shell profile so the `moldesk` command is found.
#
# Options (environment variables):
# MOLDESK_HOME=
install location and data directory (default ~/.moldesk)
# MOLDESK_CLI_SPEC= npm package to install (default @moldesk/cli)
# MOLDESK_NO_MODIFY_PATH=1 do not edit your shell profile
#
# Re-running this script updates MoleculeDesk.
set -eu
NODE_VERSION="22.23.2"
NODE_MIN_MAJOR=20
CLI_SPEC="${MOLDESK_CLI_SPEC:-@moldesk/cli}"
HOME_DIR="${MOLDESK_HOME:-$HOME/.moldesk}"
say() { printf '%s\n' "$*"; }
fail() {
printf '\nMoleculeDesk could not be installed.\n%s\n' "$*" >&2
printf 'Need help? https://moleculedesk.com/docs/troubleshooting\n' >&2
exit 1
}
TMP_DIR=""
cleanup() { [ -z "$TMP_DIR" ] || rm -rf "$TMP_DIR"; }
trap cleanup EXIT INT TERM
# --- 1. Check the machine ---------------------------------------------------
case "$(uname -s)" in
Linux) OS=linux ;;
Darwin) OS=darwin ;;
*) fail "This installer supports macOS and Linux. On Windows, install WSL2 (https://learn.microsoft.com/windows/wsl/install) and run the command from the Ubuntu terminal." ;;
esac
case "$(uname -m)" in
x86_64|amd64) ARCH=x64 ;;
arm64|aarch64) ARCH=arm64 ;;
*) fail "Unsupported CPU type: $(uname -m). MoleculeDesk supports 64-bit Intel/AMD and ARM processors." ;;
esac
if [ "$OS" = linux ] && (ldd --version 2>&1 || true) | grep -qi musl; then
fail "This Linux uses musl (for example Alpine), which MoleculeDesk does not support yet. Use Ubuntu, Debian, Fedora or similar."
fi
if command -v curl >/dev/null 2>&1; then
download() { curl --fail --silent --show-error --location --retry 3 "$1" -o "$2"; }
elif command -v wget >/dev/null 2>&1; then
download() { wget -q -O "$2" "$1"; }
else
fail "Either curl or wget is needed to download files. On Ubuntu/Debian, run: sudo apt-get install -y curl"
fi
command -v tar >/dev/null 2>&1 || fail "tar is needed to unpack downloads. On Ubuntu/Debian, run: sudo apt-get install -y tar"
mkdir -p "$HOME_DIR" || fail "Cannot create $HOME_DIR. Check that you can write to that location."
HOME_DIR=$(cd "$HOME_DIR" && pwd)
TMP_DIR=$(mktemp -d "$HOME_DIR/.install.XXXXXX") || fail "Cannot create a temporary folder in $HOME_DIR."
say "Installing MoleculeDesk into $HOME_DIR"
say ""
# --- 2. Find or download Node.js ---------------------------------------------
node_ok() {
[ -x "$1/node" ] && [ -x "$1/npm" ] &&
"$1/node" -e "process.exit(Number(process.versions.node.split('.')[0]) >= $NODE_MIN_MAJOR ? 0 : 1)" >/dev/null 2>&1
}
NODE_BIN=""
if command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then
SYSTEM_NODE_BIN=$(dirname "$(command -v node)")
if node_ok "$SYSTEM_NODE_BIN"; then NODE_BIN="$SYSTEM_NODE_BIN"; say "Using Node.js found on this computer ($SYSTEM_NODE_BIN)."; fi
fi
if [ -z "$NODE_BIN" ]; then
NODE_NAME="node-v$NODE_VERSION-$OS-$ARCH"
NODE_DIR="$HOME_DIR/node/$NODE_NAME"
if ! node_ok "$NODE_DIR/bin"; then
say "Node.js was not found, so a private copy is being downloaded (about 40 MB)..."
ARCHIVE="$NODE_NAME.tar.gz"
BASE="https://nodejs.org/dist/v$NODE_VERSION"
download "$BASE/$ARCHIVE" "$TMP_DIR/$ARCHIVE" || fail "Could not download Node.js from nodejs.org. Check your internet connection and try again."
download "$BASE/SHASUMS256.txt" "$TMP_DIR/SHASUMS256.txt" || fail "Could not download the Node.js checksum list from nodejs.org."
EXPECTED=$(awk -v f="$ARCHIVE" '$2 == f { print $1 }' "$TMP_DIR/SHASUMS256.txt")
[ -n "$EXPECTED" ] || fail "Node.js checksum for $ARCHIVE was not found."
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$TMP_DIR/$ARCHIVE" | awk '{ print $1 }')
elif command -v shasum >/dev/null 2>&1; then
ACTUAL=$(shasum -a 256 "$TMP_DIR/$ARCHIVE" | awk '{ print $1 }')
else
fail "sha256sum or shasum is needed to verify downloads."
fi
[ "$EXPECTED" = "$ACTUAL" ] || fail "The downloaded Node.js file failed its safety check (checksum mismatch). Nothing was installed. Try again; if it repeats, report it."
mkdir -p "$HOME_DIR/node"
rm -rf "$NODE_DIR"
# --no-same-owner: some shared/network volumes forbid changing file owners.
tar --no-same-owner -xzf "$TMP_DIR/$ARCHIVE" -C "$HOME_DIR/node" || fail "Could not unpack Node.js."
node_ok "$NODE_DIR/bin" || fail "The downloaded Node.js does not run on this computer."
fi
NODE_BIN="$NODE_DIR/bin"
fi
NODE="$NODE_BIN/node"
NPM="$NODE_BIN/npm"
# --- 3. Install the MoleculeDesk command-line tool ----------------------------
say "Installing the MoleculeDesk command-line tool..."
CLI_DIR="$HOME_DIR/cli"
mkdir -p "$CLI_DIR"
# A local --prefix install needs no admin rights and cannot clash with other npm packages.
# PATH carries Node so npm's own scripts find it; stdin is closed so `curl | sh` is not read.
PATH="$NODE_BIN:$PATH" NPM_CONFIG_UPDATE_NOTIFIER=false "$NPM" install --prefix "$CLI_DIR" --no-audit --no-fund --loglevel=error "$CLI_SPEC" "$LAUNCHER.tmp" </dev/null && return 0
{ [ ! -s "$1" ] || [ -z "$(tail -c1 "$1")" ] || echo; printf '%s\n' "$PATH_LINE"; } >> "$1" 2>/dev/null || return 0
PROFILE_UPDATED="$PROFILE_UPDATED $1"
}
if [ "${MOLDESK_NO_MODIFY_PATH:-}" != 1 ]; then
case "${SHELL:-}" in
*/zsh) add_to_profile "$HOME/.zshrc" ;;
*/bash) add_to_profile "$HOME/.bashrc"; add_to_profile "$HOME/.bash_profile" existing ;;
*) add_to_profile "$HOME/.profile" ;;
esac
fi
# --- 5. Done ----------------------------------------------------------------
say ""
say "MoleculeDesk $VERSION is installed."
say ""
if [ "${MOLDESK_NO_MODIFY_PATH:-}" = 1 ]; then
say "To use it, add this folder to your PATH: $BIN_DIR"
elif [ -n "$PROFILE_UPDATED" ]; then
say "Open a NEW terminal window (or run: export PATH=\"$BIN_DIR:\$PATH\"), then:"
else
say "If the moldesk command is not found, open a NEW terminal window, then:"
fi
say ""
say " moldesk doctor check your computer (GPU, Python, disk)"
say " moldesk list see which models can run here"
say ""
say "Docs: https://moleculedesk.com/docs"