You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

664 lines
17 KiB

#!/usr/bin/env bash
cd "$(dirname "$0")" || exit
check_exists()
{
command -v "$1" > /dev/null
}
# This may be overriden by --python/-p option.
python="python3"
# This is needed for systems where GNU is not the default make, like FreeBSD.
if check_exists gmake; then
make="gmake"
else
make="make"
fi
num_cores()
{
${python} -c 'import multiprocessing as mp; print(mp.cpu_count())'
}
sha256_verify ()
{
if [[ "$(uname)" == "Darwin" ]]; then
shasum -a 256 -c <<<"$1 $2"
return "$?"
elif [[ "$(uname)" == "FreeBSD" ]]; then
sha256 -c "$1" "$2"
return "$?"
else
sha256sum -c <<<"$1 $2"
return "$?"
fi
}
# http_get url filename
http_get ()
{
if check_exists curl; then
curl --retry 5 -L "$1" -o "$2"
elif check_exists wget; then
wget "$1" -O "$2"
else
echo "Neither curl nor wget present; please install one of them using your OS package manager."
kill $$
fi
}
gpg_verify ()
{
if [[ $no_gpg_validation != 1 ]]; then
if ! check_exists gpg; then
echo "GPG not installed, cannot verify release signatures; install gpg or use --no-gpg-validation."
kill $$
fi
gpg --import "$1"
gpg --verify "$2" || kill $$
fi
}
deps_install ()
{
debian_deps=( \
'curl' \
'build-essential' \
'automake' \
'pkg-config' \
'libtool' \
'python3-dev' \
'python3-pip' \
'python3-setuptools' \
'python3-venv' \
'libltdl-dev' )
if [ "$with_sudo" == 1 ]; then debian_deps+=("sudo"); fi
darwin_deps=( \
'automake' \
'libtool' )
if ! is_python3; then
echo "Python 2 is no longer supported. Please use a compatible Python 3 version."
return 1
fi
if [[ ${use_os_deps_check} != '1' ]]; then
echo "Checking OS package manager's dependencies disabled. Trying to build."
return 0
elif [[ ${install_os} == 'debian' ]]; then
deb_deps_install "${debian_deps[@]}"
return "$?"
elif [[ ${install_os} == 'darwin' ]]; then
dar_deps_install "${darwin_deps[@]}"
return "$?"
else
echo "OS can not be determined. Trying to build."
return 0
fi
}
tor_deps_install ()
{
debian_deps=( \
'libevent-dev' \
'libssl-dev' \
'zlib1g-dev' )
darwin_deps=( \
'libevent' \
'zlib' )
if [[ ${use_os_deps_check} != '1' ]]; then
return 0
elif [[ ${install_os} == 'debian' ]]; then
deb_deps_install "${debian_deps[@]}"
return "$?"
elif [[ ${install_os} == 'darwin' ]]; then
dar_deps_install "${darwin_deps[@]}"
return "$?"
else
return 0
fi
}
deb_deps_check ()
{
apt-cache policy "${deb_deps[@]}" | grep "Installed.*none"
}
deb_deps_install ()
{
deb_deps=( "${@}" )
if deb_deps_check; then
clear
sudo_command=''
if [ "$with_sudo" == 1 ]; then
echo "
sudo password required to run :
\`apt-get install ${deb_deps[*]}\`
"
sudo_command="sudo"
fi
if ! $sudo_command apt-get install -y --no-install-recommends "${deb_deps[@]}"; then
return 1
fi
fi
}
dar_deps_install ()
{
dar_deps=( "${@}" )
if ! brew install "${dar_deps[@]}"; then
return 1
fi
}
check_skip_build ()
{
if [[ ${reinstall} == false ]] && [[ -d "$1" ]]; then
read -r -n 1 -p "Directory ${1} exists. Remove and recreate? (y/N) " q
echo ""
if [[ "${q}" =~ Y|y ]]; then
rm -rf "./${1}"
mkdir -p "./${1}"
return 1
else
echo "skipping ${1}..."
return 0
fi
fi
return 1
}
venv_setup ()
{
if check_skip_build 'jmvenv'; then
return 0
else
reinstall='true'
fi
"${python}" -m venv "${jm_source}/jmvenv" || return 1
# shellcheck source=/dev/null
source "${jm_source}/jmvenv/bin/activate" || return 1
pip install --upgrade pip
pip install --upgrade setuptools
deactivate
}
dep_get ()
{
pkg_name="$1" pkg_hash="$2" pkg_url="$3"
pkg_pubkeys="$4" pkg_sig="$5" pkg_hash_file="$6" pkg_hash_file_sig="$7"
pushd cache || return 1
if [ ! -f "${pkg_name}" ] || ! sha256_verify "${pkg_hash}" "${pkg_name}"; then
http_get "${pkg_url}/${pkg_name}" "${pkg_name}"
fi
if ! sha256_verify "${pkg_hash}" "${pkg_name}"; then
return 1
fi
if [[ -n "${pkg_hash_file}" ]]; then
http_get "${pkg_url}/${pkg_hash_file}" "${pkg_hash_file}"
if [[ -n "${pkg_hash_file_sig}" ]]; then
http_get "${pkg_url}/${pkg_hash_file_sig}" "${pkg_hash_file_sig}"
gpg_verify "../../pubkeys/third-party/${pkg_pubkeys}" "${pkg_hash_file_sig}"
fi
if ! grep -qs "${pkg_hash}" "${pkg_hash_file}"; then
echo "Hash mismatch, ${pkg_hash} not in ${pkg_url}/${pkg_hash_file}!"
return 1
fi
fi
if [[ -n "${pkg_sig}" ]]; then
http_get "${pkg_url}/${pkg_sig}" "${pkg_sig}"
gpg_verify "../../pubkeys/third-party/${pkg_pubkeys}" "${pkg_sig}"
fi
tar -xzf "${pkg_name}" -C ../
popd || return 1
}
# add '--disable-docs' to libffi ./configure so makeinfo isn't needed
# https://github.com/libffi/libffi/pull/190/commits/fa7a257113e2cfc963a0be9dca5d7b4c73999dcc
libffi_patch_disable_docs ()
{
cat <<'EOF' > Makefile.am.patch
56c56,59
< info_TEXINFOS = doc/libffi.texi
---
> info_TEXINFOS =
> if BUILD_DOCS
> #info_TEXINFOS += doc/libffi.texi
> endif
EOF
# autogen.sh is not happy when run from some directories, causing it
# to create an ltmain.sh file in our ${jm_root} directory. weird.
# https://github.com/meetecho/janus-gateway/issues/290#issuecomment-125160739
# https://github.com/meetecho/janus-gateway/commit/ac38cfdae7185f9061569b14809af4d4052da700
cat <<'EOF' > autoreconf.patch
18a19
> AC_CONFIG_AUX_DIR([.])
EOF
cat <<'EOF' > configure.ac.patch
545a546,552
> AC_ARG_ENABLE(docs,
> AC_HELP_STRING([--disable-docs],
> [Disable building of docs (default: no)]),
> [enable_docs=no],
> [enable_docs=yes])
> AM_CONDITIONAL(BUILD_DOCS, [test x$enable_docs = xyes])
>
EOF
patch Makefile.am Makefile.am.patch
patch configure.ac autoreconf.patch
patch configure.ac configure.ac.patch
}
libffi_build ()
{
./autogen.sh
./configure --disable-docs --enable-shared --prefix="${jm_root}"
$make uninstall
$make
if ! $make check; then
return 1
fi
}
libffi_install ()
{
libffi_version='libffi-3.2.1'
libffi_lib_tar="v3.2.1.tar.gz"
libffi_lib_sha='96d08dee6f262beea1a18ac9a3801f64018dc4521895e9198d029d6850febe23'
libffi_url="https://github.com/libffi/libffi/archive"
if check_skip_build "${libffi_version}"; then
return 0
fi
if ! dep_get "${libffi_lib_tar}" "${libffi_lib_sha}" "${libffi_url}"; then
return 1
fi
pushd "${libffi_version}" || return 1
if ! libffi_patch_disable_docs; then
return 1
fi
if libffi_build; then
$make install
else
return 1
fi
popd || return 1
}
libsecp256k1_build()
{
$make clean
./autogen.sh
./configure \
--enable-module-recovery \
--disable-jni \
--prefix "${jm_root}" \
--enable-experimental \
--enable-module-ecdh \
--enable-benchmark=no \
MAKE=$make
$make
if [[ $use_secp_check == '1' ]]; then
if ! $make check; then
return 1
fi
else
echo "Skipping libsecp256k1 tests."
fi
}
libsecp256k1_install()
{
secp256k1_lib_tar='490022745164b56439688b0fc04f9bd43578e5c3'
secp256k1_lib_sha="4c87e32bff6815fb632a0ffd5bc89f2f7dfce11bd8501f1c779cf1e8e354c3c9"
secp256k1_lib_url='https://github.com/bitcoin-core/secp256k1/archive'
if ! dep_get "${secp256k1_lib_tar}.tar.gz" "${secp256k1_lib_sha}" "${secp256k1_lib_url}"; then
return 1
fi
pushd "secp256k1-${secp256k1_lib_tar}" || return 1
if libsecp256k1_build; then
$make install
else
return 1
fi
popd || return 1
}
libsodium_build ()
{
$make uninstall
$make distclean
./autogen.sh
./configure \
--enable-minimal \
--enable-shared \
--prefix="${jm_root}"
$make uninstall
$make
if ! $make check; then
return 1
fi
}
libsodium_install ()
{
sodium_version='libsodium-1.0.18'
sodium_lib_tar="${sodium_version}.tar.gz"
sodium_lib_sha='6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1'
sodium_url='https://download.libsodium.org/libsodium/releases'
sodium_pubkeys='libsodium.asc'
if check_skip_build "${sodium_version}"; then
return 0
fi
if ! dep_get "${sodium_lib_tar}" "${sodium_lib_sha}" "${sodium_url}" "${sodium_pubkeys}" "${sodium_lib_tar}.sig"; then
return 1
fi
pushd "${sodium_version}" || return 1
if libsodium_build; then
$make install
else
return 1
fi
popd || return 1
}
tor_root ()
{
# jm_root will be empty for --docker-install,
# sys.prefix defaults to /usr/local
if [[ -n "$jm_root" ]]; then
echo "$jm_root"
else
echo "/usr/local"
fi
}
tor_build ()
{
$make uninstall
$make distclean
./configure \
--disable-system-torrc \
--disable-seccomp \
--disable-libscrypt \
--disable-module-relay \
--disable-lzma \
--disable-zstd \
--disable-asciidoc \
--disable-manpage \
--disable-html-manual \
--prefix="$(tor_root)"
$make
if ! $make check; then
return 1
fi
}
tor_install ()
{
tor_version='tor-0.4.7.13'
tor_tar="${tor_version}.tar.gz"
tor_sha='2079172cce034556f110048e26083ce9bea751f3154b0ad2809751815b11ea9d'
tor_url='https://dist.torproject.org'
tor_pubkeys='Tor.asc'
if ! dep_get "${tor_tar}" "${tor_sha}" "${tor_url}" "${tor_pubkeys}" "" "${tor_tar}.sha256sum" "${tor_tar}.sha256sum.asc"; then
return 1
fi
pushd "${tor_version}" || return 1
if tor_build; then
$make install
echo "# Default JoinMarket Tor configuration
Log warn stderr
SOCKSPort 9050 IsolateDestAddr IsolateDestPort
ControlPort 9051
CookieAuthentication 1
" > "$(tor_root)/etc/tor/torrc"
else
return 1
fi
popd || return 1
}
joinmarket_install ()
{
reqs=( 'base.txt' )
if [[ ${with_qt} == "1" ]]; then
reqs+=( 'gui.txt' )
fi
for req in "${reqs[@]}"; do
if [ "$with_jmvenv" == 1 ]; then pip_command=pip; else pip_command=pip3; fi
$pip_command install -r "requirements/${req}" || return 1
done
if [[ ${with_qt} == "1" ]]; then
if [[ -d ~/.local/share/icons ]] && [[ -d ~/.local/share/applications ]]; then
echo "Installing XDG desktop entry"
cp -f "$(dirname "$0")/docs/images/joinmarket_logo.png" \
~/.local/share/icons/
sed "s/\\\$JMHOME/$(dirname "$(realpath "$0")" | sed 's/\//\\\//g')/" \
"$(dirname "$0")/joinmarket-qt.desktop" > \
~/.local/share/applications/joinmarket-qt.desktop
fi
fi
}
parse_flags ()
{
while :; do
case $1 in
--develop)
# no-op for backwards compatibility
;;
--disable-os-deps-check)
use_os_deps_check='0'
;;
--disable-secp-check)
use_secp_check='0'
;;
--no-gpg-validation)
no_gpg_validation='1'
;;
-p|--python)
if [[ "$2" ]]; then
python="$2"
shift
else
echo 'ERROR: "--python" requires a non-empty option argument.'
return 1
fi
;;
--python=?*)
python="${1#*=}"
;;
--python=)
echo 'ERROR: "--python" requires a non-empty option argument.'
return 1
;;
--with-local-tor)
build_local_tor='1'
;;
--with-qt)
with_qt='1'
;;
--without-qt)
with_qt='0'
;;
--docker-install)
with_sudo='0'
with_jmvenv='0'
;;
"")
break
;;
*)
if [[ $1 != '-h' ]] && [[ $1 != '--help' ]]; then
echo "Invalid option $1"
fi
echo "
Usage: ${0} [options]
Options:
--develop code remains editable in place (currently always enabled)
--disable-os-deps-check skip OS package manager's dependency check
--disable-secp-check do not run libsecp256k1 tests (default is to run them)
--docker-install system wide install as root for minimal Docker installs
--no-gpg-validation disable GPG key validation for dependencies
--python, -p python version (only python3 versions are supported)
--with-local-tor build Tor locally and autostart when needed
--with-qt build the Qt GUI
--without-qt don't build the Qt GUI
"
return 1
;;
esac
shift
done
if [[ ${with_qt} == '' ]]; then
read -r -n 1 -p "
INFO: Joinmarket-Qt for GUI Taker and Tumbler modes is available.
Install Qt dependencies (~160mb)? (y/N) "
echo ""
if [[ ${REPLY} =~ y|Y ]]; then
echo "Building Qt GUI"
with_qt='1'
else
echo "Not building Qt GUI"
fi
fi
}
os_is_deb ()
{
( which apt-get && which dpkg-query ) 2>/dev/null 1>&2
}
os_is_dar ()
{
[[ "$(uname)" == "Darwin" ]]
}
is_python3 ()
{
if [[ ${python} == python3* ]]; then
return 0
fi
if [[ ${python} == python2* ]]; then
return 1
fi
${python} -c 'import sys; sys.exit(0) if sys.version_info >= (3,0) else sys.exit(1)'
}
install_get_os ()
{
if os_is_deb; then
echo 'debian'
elif os_is_dar; then
echo 'darwin'
else
echo 'unknown'
fi
}
main ()
{
# flags
build_local_tor=''
no_gpg_validation=''
use_os_deps_check='1'
use_secp_check='1'
with_qt=''
with_jmvenv='1'
with_sudo='1'
reinstall='false'
if ! parse_flags "${@}"; then
return 1
fi
jm_source="$PWD"
if [ "$with_jmvenv" == 1 ]; then
jm_root="${jm_source}/jmvenv"
export PKG_CONFIG_PATH="${jm_root}/lib/pkgconfig:${PKG_CONFIG_PATH}"
export LD_LIBRARY_PATH="${jm_root}/lib:${LD_LIBRARY_PATH}"
export C_INCLUDE_PATH="${jm_root}/include:${C_INCLUDE_PATH}"
else
jm_root=""
fi
# os check
install_os="$( install_get_os )"
if ! deps_install; then
echo "Dependecies could not be installed. Exiting."
return 1
fi
MAKEFLAGS="-j $(num_cores)" && export MAKEFLAGS
if [ "$with_jmvenv" == 1 ]; then
if ! venv_setup; then
echo "Joinmarket Python virtual environment could not be setup. Exiting."
return 1
fi
# shellcheck source=/dev/null
source "${jm_root}/bin/activate"
fi
if [[ ${build_local_tor} == "1" ]]; then
if ! tor_deps_install; then
echo "Tor dependencies could not be installed. Exiting."
return 1
fi
fi
mkdir -p "deps/cache"
pushd deps || return 1
if ! libsecp256k1_install; then
echo "libsecp256k1 was not built. Exiting."
return 1
fi
if ! libffi_install; then
echo "Libffi was not built. Exiting."
return 1
fi
if ! libsodium_install; then
echo "Libsodium was not built. Exiting."
return 1
fi
if [[ ${build_local_tor} == "1" ]]; then
if ! tor_install; then
echo "Building local Tor was requested, but not built. Exiting."
return 1
fi
fi
popd || return 1
if ! joinmarket_install; then
echo "Joinmarket was not installed. Exiting."
if [ "$with_jmvenv" == 1 ]; then deactivate; fi
return 1
fi
if [ "$with_jmvenv" == 1 ]; then
deactivate
echo "Joinmarket successfully installed
Before executing scripts or tests, run:
\`source jmvenv/bin/activate\`
from this directory, to activate the virtual environment."
fi
}
main "${@}"