From 0842a82a3896b5d1bc45d191111252feb98dd031 Mon Sep 17 00:00:00 2001 From: Marc Kelechava Date: Tue, 19 May 2026 16:31:11 -0700 Subject: [PATCH] feat: add curl-based Skyvern server installer (#5804) Co-authored-by: Claude Opus 4.7 (1M context) --- install.sh | 168 ++++++++++++++++++++++++++++++++++++++++ scripts/test-install.sh | 63 +++++++++++++++ 2 files changed, 231 insertions(+) create mode 100755 install.sh create mode 100755 scripts/test-install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 000000000..38cd70338 --- /dev/null +++ b/install.sh @@ -0,0 +1,168 @@ +#!/bin/sh +# Skyvern installer. Run `install.sh --help` for usage. + +set -eu + +SKYVERN_RUN_INIT="${SKYVERN_RUN_INIT:-}" +SKYVERN_VERSION="${SKYVERN_VERSION:-}" +SKYVERN_VARIANT="${SKYVERN_VARIANT:-server}" + +while [ $# -gt 0 ]; do + case "$1" in + --no-init) SKYVERN_RUN_INIT=0; shift ;; + --run-init|--run-setup) SKYVERN_RUN_INIT=1; shift ;; + --version) SKYVERN_VERSION="${2:?--version requires a value}"; shift 2 ;; + --version=*) SKYVERN_VERSION="${1#--version=}"; shift ;; + --variant) SKYVERN_VARIANT="${2:?--variant requires a value}"; shift 2 ;; + --variant=*) SKYVERN_VARIANT="${1#--variant=}"; shift ;; + --help|-h) + # Heredoc rather than self-parsing $0: when piped, $0 is the + # shell name, not a real file path. + cat <<'EOF' +Skyvern installer. + +Usage: + curl -LsSf https://install.skyvern.com | sh + curl -LsSf https://install.skyvern.com | sh -s -- --no-init + curl -LsSf https://install.skyvern.com | sh -s -- --run-setup + curl -LsSf https://install.skyvern.com | sh -s -- --version 1.0.35 + curl -LsSf https://install.skyvern.com | sh -s -- --variant server + +Flags: + --variant V install variant (default: server). This curl installer is + intentionally for the self-hosted CLI/server path. For SDKs, + use pip inside your project: 'skyvern' or 'skyvern[local]'. + --run-setup run the setup wizard after install (requires a terminal). + --run-init alias for --run-setup. + --no-init skip the post-install setup wizard. + --version X pin a specific PyPI version (e.g., 1.0.35). + +Environment overrides: + SKYVERN_VARIANT=V same as --variant + SKYVERN_RUN_INIT=0 same as --no-init + SKYVERN_RUN_INIT=1 same as --run-setup + SKYVERN_VERSION=X same as --version + +What this does: + 1. Bootstraps 'uv' if missing — uv manages Python so the host + doesn't need one preinstalled. + 2. Installs the self-hosted PyPI package as an isolated tool + ('skyvern' on PATH). + 3. Prints the next setup command. Use --run-setup to run it immediately. +EOF + exit 0 + ;; + *) + printf 'unknown option: %s (try --help)\n' "$1" >&2 + exit 2 + ;; + esac +done + +case "$(uname -s)" in + Darwin|Linux) ;; + *) + printf 'unsupported OS: %s\n' "$(uname -s)" >&2 + printf "For Windows, use WSL or 'pip install skyvern' from a Python environment.\n" >&2 + exit 1 + ;; +esac + +uv_was_freshly_installed=0 +if ! command -v uv >/dev/null 2>&1; then + uv_was_freshly_installed=1 + printf 'installing uv (one-time, into ~/.local/bin)...\n' + # Download then execute (not `curl ... | sh`): POSIX /bin/sh has no + # pipefail, so curl failures would otherwise be swallowed silently. + UV_INSTALLER="$(mktemp -t skyvern-uv-installer.XXXXXX)" + trap 'rm -f "$UV_INSTALLER"' EXIT + if ! curl -LsSf https://astral.sh/uv/install.sh -o "$UV_INSTALLER"; then + printf 'ERROR: failed to download uv installer from astral.sh.\n' >&2 + printf ' Check your network/proxy. See https://docs.astral.sh/uv/getting-started/installation/\n' >&2 + exit 1 + fi + sh "$UV_INSTALLER" + export PATH="$HOME/.local/bin:$PATH" + if ! command -v uv >/dev/null 2>&1; then + printf 'ERROR: uv installer ran but uv is not on PATH.\n' >&2 + printf ' See https://docs.astral.sh/uv/getting-started/installation/\n' >&2 + exit 1 + fi +fi + +# Variant policy: the curl installer is for isolated CLI/server installs. +# SDK installs belong in the user's project environment, not a uv tool venv. +case "$SKYVERN_VARIANT" in + server) + PKG_SPEC="skyvern[server]" + PYTHON_SPEC=">=3.11,<3.14" + DEFAULT_RUN_INIT=0 + SUCCESS_HINT="Next: skyvern quickstart" + ;; + sdk|cloud|base) + printf "ERROR: --variant '%s' is not installed by this curl installer.\n" "$SKYVERN_VARIANT" >&2 + printf " Use 'pip install skyvern' inside your Python project instead.\n" >&2 + exit 2 + ;; + local|embedded) + printf "ERROR: --variant '%s' is not installed by this curl installer.\n" "$SKYVERN_VARIANT" >&2 + printf " Use 'pip install \"skyvern[local]\"' inside your Python project instead.\n" >&2 + exit 2 + ;; + mcp|all) + printf "ERROR: --variant '%s' is not available from this installer.\n" "$SKYVERN_VARIANT" >&2 + printf " Use --variant server for the self-hosted CLI/server tool.\n" >&2 + exit 2 + ;; + *) + printf "ERROR: unknown variant '%s'. Valid: server (default).\n" "$SKYVERN_VARIANT" >&2 + exit 2 + ;; +esac + +[ -n "$SKYVERN_RUN_INIT" ] || SKYVERN_RUN_INIT="$DEFAULT_RUN_INIT" +case "$SKYVERN_RUN_INIT" in + 0|1) ;; + *) + printf "ERROR: SKYVERN_RUN_INIT must be 0 or 1.\n" >&2 + exit 2 + ;; +esac + +PKG_SPEC_INSTALL="$PKG_SPEC" +if [ -n "$SKYVERN_VERSION" ]; then + PKG_SPEC_INSTALL="${PKG_SPEC}==${SKYVERN_VERSION}" +fi + +printf 'installing %s via uv tool install...\n' "$PKG_SPEC_INSTALL" +# Pin Python to the variant's supported range. Without this, uv may pick a +# host Python outside the range and env-marker deps (e.g., playwright with +# `<'3.14'`) silently drop from the resolution. Bump intentionally when +# upstream `requires-python` shifts — does NOT auto-track. +uv tool install --python "$PYTHON_SPEC" --force "$PKG_SPEC_INSTALL" + +SETUP_COMMAND="skyvern quickstart" +if skyvern quickstart --help 2>/dev/null | grep -q -- "--install-type"; then + SETUP_COMMAND="skyvern quickstart --install-type server" +fi +SUCCESS_HINT="Next: $SETUP_COMMAND" + +if [ "$SKYVERN_RUN_INIT" = "1" ]; then + if [ ! -t 0 ]; then + printf "\nWARN: not running setup because stdin is not an interactive terminal.\n" >&2 + printf " Run '%s' from your shell when ready.\n" "$SETUP_COMMAND" >&2 + else + printf "\nrunning '%s'...\n" "$SETUP_COMMAND" + if ! sh -c "$SETUP_COMMAND"; then + printf "\nWARN: '%s' did not complete. The CLI is installed;\n" "$SETUP_COMMAND" >&2 + printf " re-run '%s' once the issue is sorted.\n" "$SETUP_COMMAND" >&2 + fi + fi +fi + +printf '\nSkyvern installed. %s\n' "$SUCCESS_HINT" + +if [ "$uv_was_freshly_installed" = "1" ]; then + printf "\nNote: uv was just installed in ~/.local/bin. Run 'source ~/.local/bin/env'\n" + printf " or open a new terminal to use 'skyvern' in this shell.\n" +fi diff --git a/scripts/test-install.sh b/scripts/test-install.sh new file mode 100755 index 000000000..cb88c0c15 --- /dev/null +++ b/scripts/test-install.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# Smoke-test install.sh against fresh Docker base images. +# +# Usage: +# scripts/test-install.sh # default matrix, --no-init +# scripts/test-install.sh --with-init # run post-install setup when possible +# IMAGES="ubuntu:22.04" scripts/test-install.sh # subset +# +# Requires Docker on the host. + +set -eu + +WITH_INIT=0 +[ "${1:-}" = "--with-init" ] && WITH_INIT=1 + +DEFAULT_IMAGES="ubuntu:22.04 ubuntu:24.04 debian:12-slim python:3.11-slim python:3.13-slim" +IMAGES="${IMAGES:-$DEFAULT_IMAGES}" + +# This installer is intentionally server-only; SDK/local installs belong in a +# project environment via pip, not a uv tool venv. +DEFAULT_VARIANTS="server" +VARIANTS="${VARIANTS:-$DEFAULT_VARIANTS}" + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +INSTALL_SH="$REPO_ROOT/install.sh" +[ -f "$INSTALL_SH" ] || { printf 'install.sh not found at %s\n' "$INSTALL_SH" >&2; exit 1; } + +init_flag="--no-init" +[ "$WITH_INIT" = "1" ] && init_flag="" + +failed="" +for variant in $VARIANTS; do + for img in $IMAGES; do + printf '\n=== %s / variant=%s ===\n' "$img" "$variant" + if docker run --rm \ + -v "$INSTALL_SH:/tmp/install.sh:ro" \ + -e "VARIANT=$variant" \ + -e "INIT_FLAG=$init_flag" \ + "$img" sh -c ' + set -e + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq >/dev/null + apt-get install -qq -y curl ca-certificates >/dev/null + sh /tmp/install.sh --variant "$VARIANT" $INIT_FLAG + export PATH="$HOME/.local/bin:$PATH" + # --help exit 0 proves the package imports cleanly (the CLI + # has no --version flag). + skyvern --help >/dev/null + '; then + printf 'PASS: %s / variant=%s\n' "$img" "$variant" + else + printf 'FAIL: %s / variant=%s\n' "$img" "$variant" + failed="$failed $img(variant=$variant)" + fi + done +done + +if [ -n "$failed" ]; then + printf '\nFAILED:%s\n' "$failed" >&2 + exit 1 +fi + +printf '\nAll images passed.\n'