Files
zero/scripts/install.sh
Anandan f1502f0bfe Add local control browser and terminal tools (#319)
* Add local control browser and terminal tools

Add configurable local browser, desktop, and terminal control helpers with packaged helper discovery for install and release flows.

Improve sandbox scope classification for local control commands and shared temporary paths.

Add structured browser actions, terminal session line input/key normalization/read support, and artifact capture for local-control outputs.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config -run 'Test.*LocalControl|TestTerminal|TestBrowser|TestDesktop|TestCapture'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config ./internal/agent

Tested: tuistory smoke launched bash, sent interactive input, waited for expected output, and captured a no-cursor snapshot

Tested: git diff --cached --check

* Fix npm wrapper helper manifest test on macOS

Canonicalize expected helper paths in the npm wrapper manifest test so macOS /var and /private/var temp path aliases compare correctly.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/npmwrapper

Tested: git diff --check

* Harden local control review fixes

Respect resolved local-control config before tool listing and filter validation while preserving stream-json usage error ordering.

Harden browser, desktop, and artifact tool validation; guard Linux-only app launch behavior; clear stale helper manifests from the npm wrapper.

Make helper packaging lockfile-based with npm ci and add installer/package tests for helper staging.

Tested: focused local-control/tool/CLI/release/install/sandbox suite

Tested: isolated full go test ./...

Tested: macOS and Windows compile-only checks for touched packages
2026-06-26 08:22:56 +05:30

237 lines
5.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ZERO_REPO="${ZERO_REPO:-Gitlawb/zero}"
ZERO_VERSION="${ZERO_VERSION:-latest}"
ZERO_INSTALL_DIR="${ZERO_INSTALL_DIR:-$HOME/.local/bin}"
ZERO_GITHUB_API="${ZERO_GITHUB_API:-https://api.github.com}"
ZERO_GITHUB_BASE_URL="${ZERO_GITHUB_BASE_URL:-https://github.com}"
usage() {
cat <<'EOF'
Install Zero from GitHub Releases.
Usage:
scripts/install.sh [--version <version>] [--repo <owner/repo>] [--install-dir <path>]
Environment:
ZERO_VERSION Release version or tag. Defaults to latest.
ZERO_REPO GitHub repository. Defaults to Gitlawb/zero.
ZERO_INSTALL_DIR Directory for the zero binary. Defaults to ~/.local/bin.
ZERO_GITHUB_API GitHub API base URL. Defaults to https://api.github.com.
ZERO_GITHUB_BASE_URL GitHub web base URL. Defaults to https://github.com.
EOF
}
fail() {
echo "zero install: $*" >&2
exit 1
}
while [ "$#" -gt 0 ]; do
case "$1" in
--version)
[ "$#" -ge 2 ] || fail "--version requires a value"
ZERO_VERSION="$2"
shift 2
;;
--repo)
[ "$#" -ge 2 ] || fail "--repo requires a value"
ZERO_REPO="$2"
shift 2
;;
--install-dir)
[ "$#" -ge 2 ] || fail "--install-dir requires a value"
ZERO_INSTALL_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
fail "unknown argument: $1"
;;
esac
done
need_command() {
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
}
download() {
local url="$1"
local output="$2"
if command -v curl >/dev/null 2>&1; then
curl --fail --location --show-error --silent "$url" --output "$output"
elif command -v wget >/dev/null 2>&1; then
wget --quiet "$url" --output-document "$output"
else
fail "curl or wget is required"
fi
}
download_json() {
local url="$1"
local output="$2"
if command -v curl >/dev/null 2>&1; then
curl --fail --location --show-error --silent --header 'Accept: application/vnd.github+json' "$url" --output "$output"
elif command -v wget >/dev/null 2>&1; then
wget --quiet --header='Accept: application/vnd.github+json' "$url" --output-document "$output"
else
fail "curl or wget is required"
fi
}
detect_platform() {
case "$(uname -s)" in
Linux) echo "linux" ;;
Darwin) echo "macos" ;;
*) fail "unsupported platform: $(uname -s)" ;;
esac
}
detect_arch() {
case "$(uname -m)" in
x86_64|amd64) echo "x64" ;;
arm64|aarch64) echo "arm64" ;;
*) fail "unsupported architecture: $(uname -m)" ;;
esac
}
latest_tag() {
local metadata_file="$1"
local api_url="${ZERO_GITHUB_API%/}/repos/${ZERO_REPO}/releases/latest"
local tag
download_json "$api_url" "$metadata_file"
tag="$(sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata_file" | head -n 1)"
[ -n "$tag" ] || fail "could not read tag_name from $api_url"
echo "$tag"
}
verify_checksum() {
local checksum_file="$1"
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 -c "$checksum_file"
elif command -v sha256sum >/dev/null 2>&1; then
sha256sum -c "$checksum_file"
else
fail "shasum or sha256sum is required"
fi
}
find_extracted_entry() {
local root="$1"
local name="$2"
local kind="$3"
local candidate
if [ "$kind" = "dir" ] && [ -d "$root/$name" ]; then
echo "$root/$name"
return 0
fi
if [ "$kind" = "file" ] && [ -f "$root/$name" ]; then
echo "$root/$name"
return 0
fi
for candidate in "$root"/*/"$name"; do
if [ "$kind" = "dir" ] && [ -d "$candidate" ]; then
echo "$candidate"
return 0
fi
if [ "$kind" = "file" ] && [ -f "$candidate" ]; then
echo "$candidate"
return 0
fi
done
return 1
}
find_extracted_binary() {
find_extracted_entry "$1" "zero" "file"
}
copy_optional_file() {
local name="$1"
local source_path
if source_path="$(find_extracted_entry "$extract_dir" "$name" "file")"; then
cp "$source_path" "$ZERO_INSTALL_DIR/$name"
chmod 755 "$ZERO_INSTALL_DIR/$name"
fi
}
copy_optional_dir() {
local name="$1"
local source_path
if source_path="$(find_extracted_entry "$extract_dir" "$name" "dir")"; then
rm -rf "$ZERO_INSTALL_DIR/$name"
cp -R "$source_path" "$ZERO_INSTALL_DIR/$name"
fi
}
need_command uname
need_command sed
need_command tar
need_command mktemp
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/zero-install.XXXXXX")"
cleanup() {
rm -rf "$tmp_dir"
}
trap cleanup EXIT
if [ "$ZERO_VERSION" = "latest" ]; then
tag="$(latest_tag "$tmp_dir/latest.json")"
else
case "$ZERO_VERSION" in
v*) tag="$ZERO_VERSION" ;;
*) tag="v$ZERO_VERSION" ;;
esac
fi
version="${tag#v}"
platform="$(detect_platform)"
arch="$(detect_arch)"
archive_name="zero-v${version}-${platform}-${arch}.tar.gz"
checksum_name="${archive_name}.sha256"
release_url="${ZERO_GITHUB_BASE_URL%/}/${ZERO_REPO}/releases/download/${tag}"
archive_path="$tmp_dir/$archive_name"
checksum_path="$tmp_dir/$checksum_name"
extract_dir="$tmp_dir/extract"
echo "Installing Zero ${tag} for ${platform}-${arch}"
download "${release_url}/${archive_name}" "$archive_path"
download "${release_url}/${checksum_name}" "$checksum_path"
(
cd "$tmp_dir"
verify_checksum "$checksum_name"
)
mkdir -p "$extract_dir"
tar -xzf "$archive_path" -C "$extract_dir"
binary_path="$(find_extracted_binary "$extract_dir")" || fail "release archive did not contain zero"
mkdir -p "$ZERO_INSTALL_DIR"
cp "$binary_path" "$ZERO_INSTALL_DIR/zero"
chmod 755 "$ZERO_INSTALL_DIR/zero"
copy_optional_file "zero-linux-sandbox"
copy_optional_file "zero-seccomp"
copy_optional_dir "helpers"
echo "Installed $ZERO_INSTALL_DIR/zero"
case ":$PATH:" in
*":$ZERO_INSTALL_DIR:"*) ;;
*) echo "Add $ZERO_INSTALL_DIR to PATH to run zero from any directory." ;;
esac