#!/bin/sh # Rendezvous bootstrap. Inspect/download this script before running if preferred. # Runtime and Tor run as YOU. sudo is used only for missing OS packages. set -eu umask 077 rdzv_main() { VERSION='0.1.1' RELEASE_ORIGIN='https://join.rdzv.sh' RELEASE_SHA256='8530674b39d2fdcf05cd89845f4ab58b7341d329cd22910da6c815bc70676728' MODE='join' case "${1-}" in host|join|install) MODE=$1; shift ;; esac case "$MODE" in host|join|install) ;; *) printf '%s\n' 'Invalid mode' >&2; return 1 ;; esac if [ "$(uname -s)" != Linux ]; then printf '%s\n' 'This release supports Linux. macOS and Windows are not packaged yet.' >&2 return 1 fi command -v curl >/dev/null 2>&1 || { printf '%s\n' 'curl is required to bootstrap Rendezvous.' >&2; return 1; } : "${HOME:?HOME must be set}" ROOT="$HOME/.local/share/rendezvous" BIN="$HOME/.local/bin" if [ -L "$ROOT" ]; then printf '%s\n' 'Refusing symlinked installation root.' >&2; return 1; fi mkdir -p "$ROOT" "$BIN" chmod 700 "$ROOT" WORK=$(mktemp -d "$ROOT/.install.XXXXXXXX") PENDING_ENV='' trap 'rm -rf -- "$WORK"; if [ -n "$PENDING_ENV" ]; then rm -rf -- "$PENDING_ENV"; fi' EXIT trap 'exit 130' INT trap 'exit 143' TERM HUP as_root() { if [ "$(id -u)" = 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else printf '%s\n' 'Missing OS dependencies require sudo or installation by your administrator.' >&2; return 1 fi } need_packages=0 command -v tor >/dev/null 2>&1 || need_packages=1 if ! command -v python3 >/dev/null 2>&1; then need_packages=1 elif ! python3 -c 'import sys,venv,ensurepip; sys.exit(sys.version_info < (3,11))' 2>/dev/null; then need_packages=1 fi if [ "$need_packages" = 1 ]; then printf '%s\n' 'Installing missing Tor/Python prerequisites using the OS package manager…' >&2 if command -v apt-get >/dev/null 2>&1; then as_root apt-get update as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tor python3 python3-venv ca-certificates elif command -v dnf >/dev/null 2>&1; then as_root dnf install -y tor python3 python3-pip ca-certificates elif command -v pacman >/dev/null 2>&1; then as_root pacman -S --needed --noconfirm tor python python-pip ca-certificates else printf '%s\n' 'Automatic prerequisites support apt, dnf, or pacman. Install Tor and Python 3.11+ with venv, then rerun.' >&2 return 1 fi fi python3 -c 'import sys,venv,ensurepip; sys.exit(sys.version_info < (3,11))' || { printf '%s\n' 'Python 3.11+ with venv is required. Debian 12+/Ubuntu 24.04+ provide it.' >&2; return 1; } command -v tor >/dev/null 2>&1 || { printf '%s\n' 'Tor installation failed.' >&2; return 1; } TARGET="$ROOT/$VERSION" if [ ! -f "$TARGET/.complete" ] || [ "$(cat "$TARGET/.complete" 2>/dev/null)" != "$RELEASE_SHA256" ]; then printf 'Downloading Rendezvous %s…\n' "$VERSION" >&2 curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --connect-timeout 20 --max-time 180 \ "$RELEASE_ORIGIN/releases/$VERSION/rendezvous-$VERSION.tar.gz" -o "$WORK/release.tar.gz" python3 - "$WORK/release.tar.gz" "$RELEASE_SHA256" "$WORK/source" <<'PY' import hashlib,hmac,pathlib,sys,tarfile archive,expected,destination=sys.argv[1:] if not hmac.compare_digest(hashlib.sha256(pathlib.Path(archive).read_bytes()).hexdigest(),expected): raise SystemExit('Release checksum mismatch; nothing installed') root=pathlib.Path(destination); root.mkdir(mode=0o700) allowed={'rendezvous.py','requirements.lock','README.md','VERIFICATION.md','pyproject.toml'} with tarfile.open(archive,'r:gz') as bundle: members=bundle.getmembers() if len(members)!=len(allowed) or {m.name for m in members}!=allowed: raise SystemExit('Unexpected release contents') for member in members: if not member.isfile() or member.size>1048576: raise SystemExit('Invalid release member') (root/member.name).write_bytes(bundle.extractfile(member).read()) PY ENV_DIR=$(mktemp -d "$ROOT/runtime-$VERSION.XXXXXXXX") PENDING_ENV=$ENV_DIR python3 -m venv "$ENV_DIR" "$ENV_DIR/bin/python" -m pip --isolated install --disable-pip-version-check --only-binary=:all: --require-hashes \ --index-url https://pypi.org/simple -r "$WORK/source/requirements.lock" cp "$WORK/source/rendezvous.py" "$ENV_DIR/rendezvous.py" mkdir -p "$TARGET" # Only activate a complete environment. Existing sessions retain their files. "$ENV_DIR/bin/python" -c 'import asyncssh,cryptography' || return 1 printf '%s\n' "$ENV_DIR" > "$WORK/environment" mv "$WORK/environment" "$TARGET/environment" printf '%s\n' "$RELEASE_SHA256" > "$WORK/complete" mv "$WORK/complete" "$TARGET/.complete" PENDING_ENV='' fi ENV_DIR=$(cat "$TARGET/environment") case "$ENV_DIR" in "$ROOT"/runtime-*) ;; *) printf '%s\n' 'Invalid local runtime path.' >&2; return 1 ;; esac # A fixed launcher reads local state; no eval or executable invitation strings. cat > "$WORK/rdzv" <<'SH' #!/bin/sh set -eu ROOT="$HOME/.local/share/rendezvous" VERSION=0.1.1 ENV_DIR=$(cat "$ROOT/$VERSION/environment") case "$ENV_DIR" in "$ROOT"/runtime-*) ;; *) exit 1 ;; esac exec "$ENV_DIR/bin/python" "$ENV_DIR/rendezvous.py" "$@" SH chmod 700 "$WORK/rdzv" mv "$WORK/rdzv" "$BIN/rdzv" cp "$BIN/rdzv" "$WORK/rendezvous" mv "$WORK/rendezvous" "$BIN/rendezvous" printf 'Installed: %s/rdzv\n' "$BIN" >&2 case ":$PATH:" in *":$BIN:"*) ;; *) printf 'Add to PATH for future shells: export PATH="$HOME/.local/bin:$PATH"\n' >&2 ;; esac rm -rf -- "$WORK" trap - EXIT INT TERM HUP [ "$MODE" != install ] || return 0 if [ "$MODE" = join ]; then case " $* " in *' --invitation-file '*) exec "$BIN/rdzv" "$MODE" "$@" ;; *) if [ -r /dev/tty ]; then exec "$BIN/rdzv" "$MODE" "$@" &2; return 1; fi ;; esac fi exec "$BIN/rdzv" "$MODE" "$@" } # The shell parses the entire function before starting downloads or reading a TTY. rdzv_main "$@"