diff --git a/docs/about/installation.mdx b/docs/about/installation.mdx index cd9973f13..bf39c8317 100644 --- a/docs/about/installation.mdx +++ b/docs/about/installation.mdx @@ -16,7 +16,7 @@ Install OpenShell with a single command: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh ``` -The script detects your operating system and installs the OpenShell CLI and gateway with your native package manager. It then starts the local gateway server so you can begin creating sandboxes. +The script detects your operating system and installs the OpenShell CLI and gateway. On Linux, the Snap path is preferred when `snapd` is available; otherwise the script uses the native DEB or RPM package. The gateway then starts automatically so you can begin creating sandboxes. You can also download release artifacts directly from the [OpenShell GitHub Releases](https://github.com/NVIDIA/OpenShell/releases) page. @@ -51,6 +51,8 @@ brew services restart openshell ## Linux +On distributions that ship with `snapd`, the install script uses the Snap path described below. On hosts without `snapd` (or with `OPENSHELL_INSTALL_METHOD=classic` set), the script falls back to the classic package manager. + On Fedora and RHEL, the install script uses RPM packages. The RPM installs the `openshell` CLI, the `openshell-gateway` daemon, and a systemd user service. On Debian and Ubuntu, the install script uses a Debian package. The Debian package installs the `openshell` CLI, the `openshell-gateway` daemon, VM sandbox support, and a systemd user service. @@ -75,6 +77,61 @@ To keep the user service running after logout, enable linger: sudo loginctl enable-linger $USER ``` +## Snap + +On Linux distributions that ship with `snapd`, the install script installs OpenShell from the Snap Store. The OpenShell snap bundles the CLI, the terminal UI, and a managed gateway daemon. snapd handles upgrades and rollback; the gateway runs as a system service inside the snap. + +You can also install the snap directly: + +```shell +sudo snap install openshell +``` + +The snap requires the Docker snap. `default-provider: docker` on the OpenShell snap installs the Docker snap automatically on first use, but you can install it up front with: + +```shell +sudo snap install docker +``` + +### Connect the required interfaces + +Strict confinement requires explicit `snap connect` for several interfaces. The installer runs these for you on Snap installs; run them by hand if you install the snap manually: + +```shell +sudo snap connect openshell:docker docker:docker-daemon +sudo snap connect openshell:log-observe +sudo snap connect openshell:ssh-keys +sudo snap connect openshell:system-observe +``` + +The Docker slot is the Docker snap's `docker-daemon` slot; OpenShell does not work with a host-installed Docker Engine. The installer is best-effort: if a connect fails (for example because the Docker snap is not yet running), the snap still installs and the installer prints a warning. + +### Verify the gateway + +The snap-managed gateway service is `openshell.gateway`. Inspect it with: + +```shell +snap services openshell +snap logs -n 100 openshell.gateway +``` + +Register the gateway with the CLI: + +```shell +openshell gateway add https://127.0.0.1:17670 --local --name openshell +openshell status +``` + +The gateway listens on `https://127.0.0.1:17670` and stores its state under `/var/snap/openshell/common/`. Override gateway settings by creating `/var/snap/openshell/common/gateway.toml`. + +### When to choose Snap + +Use Snap when `snapd` is available and you want atomic upgrades and rollback, a single self-contained install that bundles the Docker provider, or a desktop launcher that surfaces the OpenShell terminal UI in the application menu. + +Use DEB or RPM when `snapd` is unavailable or inappropriate (immutable distros, headless servers, locked-down images), when you need direct `systemd --user` integration for the gateway, or when you already run Docker Engine from a non-snap source. + +Set `OPENSHELL_INSTALL_METHOD=classic` to force the classic package on hosts that also have `snapd` available. + ## Kubernetes Kubernetes deployments use the OpenShell Helm chart. For step-by-step installation, refer to [Kubernetes Setup](/kubernetes/setup). For chart values and packaging details, refer to the [Helm chart README](https://github.com/NVIDIA/OpenShell/blob/main/deploy/helm/openshell/README.md). diff --git a/install.sh b/install.sh index 6a8bc3029..ca4ff226f 100755 --- a/install.sh +++ b/install.sh @@ -4,9 +4,10 @@ # # Install OpenShell from a GitHub release. # -# Linux installs either the Debian or RPM packages from the selected release. -# Apple Silicon macOS installs the generated Homebrew formula, so Homebrew owns -# the binary layout and launchd service lifecycle. +# Linux prefers the Snap path when snapd is available. Otherwise Linux installs +# the Debian or RPM packages from the selected release. Apple Silicon macOS +# installs the generated Homebrew formula, so Homebrew owns the binary layout +# and launchd service lifecycle. # set -e @@ -21,6 +22,12 @@ HOMEBREW_FORMULA_NAME="openshell" BREAKING_RELEASE_VERSION="0.0.37" LINUX_PACKAGE_GLIBC_MIN_VERSION="2.31" UPGRADE_NOTICE_ACK="${OPENSHELL_ACK_BREAKING_UPGRADE:-}" +SNAP_PACKAGE_NAME="openshell" +SNAP_DOCKER_SLOT="docker:docker-daemon" +SNAP_DEFAULT_CHANNEL="latest/stable" +SNAP_DEV_CHANNEL="latest/edge" +LINUX_INSTALL_METHOD_SNAP="snap" +LINUX_INSTALL_METHOD_CLASSIC="classic" info() { printf '%s: %s\n' "$APP_NAME" "$*" >&2 @@ -54,13 +61,27 @@ ENVIRONMENT VARIABLES: OPENSHELL_ACK_BREAKING_UPGRADE Set to 1 only after backing up and cleaning up a pre-v0.0.37 installation. + OPENSHELL_INSTALL_METHOD + Linux only. Selects snap or classic. Accepted values: + snap install from the Snap Store + classic install via dpkg or rpm + When unset, snap is used when snapd is available and + running, otherwise the classic package manager is used. + Set OPENSHELL_INSTALL_METHOD=classic on distros where + snapd is not desired. NOTES: When OPENSHELL_VERSION is unset, this resolves the latest tagged release from ${GITHUB_URL}/releases/latest. - Linux installs the Debian package on amd64/arm64 or the RPM packages on - x86_64/aarch64, depending on the host package manager. + Linux prefers the snap method when snapd is available. The Snap channel + follows OPENSHELL_VERSION: 'dev' selects ${SNAP_DEV_CHANNEL:-latest/edge}, + tagged releases select ${SNAP_DEFAULT_CHANNEL:-latest/stable}. snapd + manages the gateway as the 'openshell.gateway' service. + + On systems without snapd, Linux installs the Debian package on + amd64/arm64 or the RPM packages on x86_64/aarch64, depending on the + host package manager. macOS installs the release Homebrew formula on Apple Silicon and starts a brew services-backed local gateway. EOF @@ -477,6 +498,69 @@ linux_package_method() { fi } +has_snapd() { + if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ]; then + case "${OPENSHELL_TEST_SNAPD_AVAILABLE:-0}" in + 1) return 0 ;; + 0) return 1 ;; + esac + fi + + command -v snap >/dev/null 2>&1 || return 1 + [ -S /run/snapd.socket ] || [ -S /var/run/snapd.socket ] || return 1 + return 0 +} + +host_supports_native_package() { + has_cmd dpkg || has_cmd rpm +} + +check_docker_not_installed_natively() { + if ! command -v docker >/dev/null 2>&1; then + return 0 + fi + + _docker_path="$(command -v docker)" + case "$_docker_path" in + /snap/bin/docker | /var/lib/snapd/snap/bin/docker) + return 0 + ;; + esac + + error "Docker is installed via a non-snap package. The Docker snap cannot be installed alongside a native Docker installation. Please uninstall the native Docker package and try again, or install OpenShell via dpkg/rpm instead of snap." +} + +resolve_linux_install_method() { + _method="${OPENSHELL_INSTALL_METHOD:-}" + if [ -n "$_method" ]; then + case "$_method" in + "$LINUX_INSTALL_METHOD_SNAP" | "$LINUX_INSTALL_METHOD_CLASSIC") + echo "$_method" + return 0 + ;; + *) + error "OPENSHELL_INSTALL_METHOD must be '${LINUX_INSTALL_METHOD_SNAP}' or '${LINUX_INSTALL_METHOD_CLASSIC}' (got: ${_method})" + ;; + esac + fi + + if has_snapd; then + echo "$LINUX_INSTALL_METHOD_SNAP" + elif host_supports_native_package; then + echo "$LINUX_INSTALL_METHOD_CLASSIC" + else + error "OpenShell Linux installs require either snapd or dpkg/rpm" + fi +} + +resolve_snap_channel() { + if [ "${RELEASE_TAG:-}" = "dev" ]; then + echo "$SNAP_DEV_CHANNEL" + else + echo "$SNAP_DEFAULT_CHANNEL" + fi +} + set_linux_target_runtime_dir() { if [ "$(id -u)" -eq "$TARGET_UID" ] && [ -n "${XDG_RUNTIME_DIR:-}" ]; then TARGET_RUNTIME_DIR="$XDG_RUNTIME_DIR" @@ -866,6 +950,44 @@ print_gateway_add_output() { done } +install_linux_snap() { + _channel="$(resolve_snap_channel)" + + info "installing ${APP_NAME} from the Snap Store (channel: ${_channel})..." + as_root snap install "$SNAP_PACKAGE_NAME" --channel="$_channel" + + # TODO: Remove this check once snapd 2.76 is released, which lifts the + # restriction preventing snap packages from being installed alongside a + # native Docker installation. At that point the docker snap can be + # pre-installed unconditionally. + check_docker_not_installed_natively + + info "pre-installing the Docker snap..." + as_root snap install docker + + info "connecting required interfaces..." + as_root snap connect "${SNAP_PACKAGE_NAME}:docker" "$SNAP_DOCKER_SLOT" || \ + warn "could not connect ${SNAP_PACKAGE_NAME}:docker to ${SNAP_DOCKER_SLOT}; the Docker driver will not work until this is fixed" + for _plug in log-observe system-observe ssh-keys; do + as_root snap connect "${SNAP_PACKAGE_NAME}:${_plug}" || \ + warn "could not connect ${SNAP_PACKAGE_NAME}:${_plug}; some features may be limited" + done + + info "installed ${APP_NAME} from the Snap Store (${_channel})" + info "snapd manages the OpenShell gateway as the 'openshell.gateway' service." + info "" + info "Next steps:" + info " snap services openshell" + info " openshell status" + info " openshell gateway add https://127.0.0.1:17670 --local --name openshell" + if ! command -v snap >/dev/null 2>&1; then + info "" + info "note: the 'openshell' binary is provided by the snap." + info "Make sure snap is in your PATH, or invoke openshell with the" + info "absolute path, before running the next-step commands." + fi +} + install_linux_deb() { check_linux_deb_platform set_linux_target_runtime_dir @@ -1033,16 +1155,23 @@ main() { case "$PLATFORM" in linux) - require_linux_package_glibc - case "$(linux_package_method)" in - deb) - install_linux_deb - ;; - rpm) - install_linux_rpm + case "$(resolve_linux_install_method)" in + "$LINUX_INSTALL_METHOD_SNAP") + install_linux_snap ;; - *) - error "unsupported Linux package method" + "$LINUX_INSTALL_METHOD_CLASSIC") + require_linux_package_glibc + case "$(linux_package_method)" in + deb) + install_linux_deb + ;; + rpm) + install_linux_rpm + ;; + *) + error "unsupported Linux package method" + ;; + esac ;; esac ;; diff --git a/tasks/scripts/test-install-sh.sh b/tasks/scripts/test-install-sh.sh index 5bf98071a..dea1a0669 100755 --- a/tasks/scripts/test-install-sh.sh +++ b/tasks/scripts/test-install-sh.sh @@ -99,4 +99,82 @@ assert_glibc_preflight_fails \ "OpenShell Linux packages require glibc >= 2.31; detected musl or unsupported libc." \ setup_ldd_musl +assert_eq() { + local name=$1 + local expected=$2 + local actual=$3 + if [ "$expected" != "$actual" ]; then + echo "FAIL: ${name}: expected '${expected}', got '${actual}'" >&2 + exit 1 + fi +} + +# has_snapd: with the test-mode override, OPENSHELL_TEST_SNAPD_AVAILABLE selects +# the result. In real environments the function probes for `snap` and the snapd +# socket, which the test cannot exercise without root or a real snapd. +(export OPENSHELL_TEST_SNAPD_AVAILABLE=1; has_snapd) || { + echo "FAIL: has_snapd with OPENSHELL_TEST_SNAPD_AVAILABLE=1" >&2 + exit 1 +} +(export OPENSHELL_TEST_SNAPD_AVAILABLE=0; has_snapd) && { + echo "FAIL: has_snapd with OPENSHELL_TEST_SNAPD_AVAILABLE=0 should fail" >&2 + exit 1 +} + +# resolve_snap_channel +export RELEASE_TAG="" +assert_eq "resolve_snap_channel default" "latest/stable" "$(resolve_snap_channel)" +export RELEASE_TAG="dev" +assert_eq "resolve_snap_channel dev" "latest/edge" "$(resolve_snap_channel)" +export RELEASE_TAG="v0.0.37" +assert_eq "resolve_snap_channel tagged" "latest/stable" "$(resolve_snap_channel)" +export RELEASE_TAG="" +unset RELEASE_TAG + +# resolve_linux_install_method: explicit env var wins over the probe +export OPENSHELL_TEST_SNAPD_AVAILABLE=1 +export OPENSHELL_INSTALL_METHOD=snap +assert_eq "resolve_linux_install_method snap explicit" "snap" "$(resolve_linux_install_method)" +export OPENSHELL_INSTALL_METHOD=classic +assert_eq "resolve_linux_install_method classic explicit overrides snap probe" "classic" "$(resolve_linux_install_method)" +export OPENSHELL_TEST_SNAPD_AVAILABLE=0 +export OPENSHELL_INSTALL_METHOD=classic +assert_eq "resolve_linux_install_method classic explicit" "classic" "$(resolve_linux_install_method)" +unset OPENSHELL_INSTALL_METHOD + +# resolve_linux_install_method: snap when available +export OPENSHELL_TEST_SNAPD_AVAILABLE=1 +unset OPENSHELL_INSTALL_METHOD +assert_eq "resolve_linux_install_method snap auto" "snap" "$(resolve_linux_install_method)" + +# resolve_linux_install_method: invalid env var exits non-zero +export OPENSHELL_INSTALL_METHOD=invalid +_snap_err="${tmpdir}/snap-err" +if (resolve_linux_install_method) >/dev/null 2>"$_snap_err"; then + echo "FAIL: resolve_linux_install_method should fail on invalid value" >&2 + exit 1 +fi +if ! grep -Fq "OPENSHELL_INSTALL_METHOD must be" "$_snap_err"; then + echo "FAIL: resolve_linux_install_method: missing expected error message" >&2 + cat "$_snap_err" >&2 || true + exit 1 +fi +unset OPENSHELL_INSTALL_METHOD + +# resolve_linux_install_method: snap absent + clean PATH (no dpkg/rpm) exits non-zero +export OPENSHELL_TEST_SNAPD_AVAILABLE=0 +_real_path="$PATH" +_clean_path="${tmpdir}/clean-path" +mkdir -p "$_clean_path" +PATH="$_clean_path" +unset OPENSHELL_INSTALL_METHOD +if (resolve_linux_install_method) >/dev/null 2>"$_snap_err"; then + PATH="$_real_path" + echo "FAIL: resolve_linux_install_method should fail without snap or native" >&2 + exit 1 +fi +PATH="$_real_path" +export OPENSHELL_TEST_SNAPD_AVAILABLE=1 +unset OPENSHELL_INSTALL_METHOD + echo "install.sh libc preflight tests passed"