#!/bin/sh # Athas install script # Usage: curl -fsSL https://athas.dev/install.sh | sh # # Environment variables: # ATHAS_VERSION - Install a specific version (e.g., "0.4.4") # ATHAS_BIN_DIR - Custom Linux binary directory (default: ~/.local/bin) # ATHAS_APP_DIR - Custom Linux app directory # ATHAS_SKIP_DEPS_CHECK - Set to 1 to skip the Linux WebKitGTK 4.1 check set -eu REPO="athasdev/athas" GITHUB="https://github.com" GITHUB_API="https://api.github.com" VERSION_OVERRIDE="" # --- Helpers --- info() { printf ' \033[1;34m>\033[0m %s\n' "$@" } success() { printf ' \033[1;32m>\033[0m %s\n' "$@" } error() { printf ' \033[1;31merror:\033[0m %s\n' "$@" >&2 exit 1 } warn() { printf ' \033[1;33mwarn:\033[0m %s\n' "$@" >&2 } usage() { cat < Install a specific version, e.g. 0.15.1 -h, --help Show this help EOF } need() { if ! command -v "$1" > /dev/null 2>&1; then error "need '$1' (command not found)" fi } parse_args() { while [ "$#" -gt 0 ]; do case "$1" in --stable) ;; --preview) error "preview builds are no longer published; run the installer without --preview" ;; --version) shift if [ -z "${1:-}" ]; then error "--version requires a version" fi VERSION_OVERRIDE="$1" ;; --version=*) VERSION_OVERRIDE="${1#*=}" ;; -h | --help) usage exit 0 ;; *) error "unknown option: $1" ;; esac shift done } # --- Cleanup --- cleanup() { if [ -n "${TMPDIR_INSTALL:-}" ] && [ -d "$TMPDIR_INSTALL" ]; then rm -rf "$TMPDIR_INSTALL" fi } trap cleanup EXIT INT TERM # --- Download helper --- download() { url="$1" output="$2" show_progress="${3:-0}" if command -v curl > /dev/null 2>&1; then if [ "$show_progress" = "1" ]; then curl -fL --retry 3 -o "$output" "$url" else curl -fsSL --retry 3 -o "$output" "$url" fi elif command -v wget > /dev/null 2>&1; then if [ "$show_progress" = "1" ]; then if wget --help 2>&1 | grep -q -- '--show-progress'; then wget --show-progress --progress=bar:force:noscroll -O "$output" "$url" else wget -O "$output" "$url" fi else wget -qO "$output" "$url" fi else error "need 'curl' or 'wget' to download files" fi } fetch() { url="$1" if command -v curl > /dev/null 2>&1; then curl -fsSL --retry 3 "$url" elif command -v wget > /dev/null 2>&1; then wget -qO- "$url" else error "need 'curl' or 'wget' to download files" fi } # --- Detect platform --- detect_platform() { OS="$(uname -s)" ARCH="$(uname -m)" case "$OS" in Darwin) PLATFORM="macos" ;; Linux) PLATFORM="linux" ;; MINGW* | MSYS* | CYGWIN*) error "Windows is not supported by this script. Download from: $GITHUB/$REPO/releases" ;; *) error "unsupported operating system: $OS" ;; esac case "$ARCH" in x86_64 | amd64) ARCH="x64" ;; aarch64 | arm64) ARCH="aarch64" ;; *) error "unsupported architecture: $ARCH" ;; esac } # --- Get version --- normalize_version() { printf '%s' "$1" | sed 's/^v//' | tr -d '\r\n' } get_latest_stable_version() { info "Fetching latest stable version..." # Use the redirect from /releases/latest to extract the tag; this only returns stable releases. if command -v curl > /dev/null 2>&1; then redirect_url=$(curl -Ls -o /dev/null -w '%{url_effective}' "$GITHUB/$REPO/releases/latest" 2>/dev/null) || true elif command -v wget > /dev/null 2>&1; then redirect_url=$(wget -q --max-redirect=5 -S -O /dev/null "$GITHUB/$REPO/releases/latest" 2>&1 | grep -i '^\s*Location:' | tail -1 | awk '{print $2}' | tr -d '\r') || true fi if [ -z "${redirect_url:-}" ]; then error "failed to determine latest version. Set ATHAS_VERSION and try again." fi VERSION=$(normalize_version "$(echo "$redirect_url" | sed 's|.*/v||')") if [ -z "$VERSION" ]; then error "failed to parse version from redirect URL. Set ATHAS_VERSION and try again." fi info "Latest stable version: $VERSION" } get_version() { requested_version="${VERSION_OVERRIDE:-${ATHAS_VERSION:-}}" if [ -n "$requested_version" ]; then VERSION=$(normalize_version "$requested_version") case "$VERSION" in *-preview.*) error "preview builds are no longer published; install a stable version" ;; esac info "Using specified version: $VERSION" else get_latest_stable_version fi } # --- Build download URL --- get_download_url() { BASE="$GITHUB/$REPO/releases/download/v${VERSION}" PRODUCT_PREFIX="Athas" case "$PLATFORM" in macos) # The .app.tar.gz updater artifacts don't include the version in the filename ARTIFACT="${PRODUCT_PREFIX}_${ARCH}.app.tar.gz" ;; linux) case "$ARCH" in x64) ARTIFACT="${PRODUCT_PREFIX}_${VERSION}_linux-x86_64.tar.gz" ;; aarch64) ARTIFACT="${PRODUCT_PREFIX}_${VERSION}_linux-aarch64.tar.gz" ;; esac ;; esac DOWNLOAD_URL="${BASE}/${ARTIFACT}" CHECKSUMS_URL="${BASE}/SHA256SUMS.txt" } # --- Verify checksum --- verify_checksum() { file="$1" checksums_file="$2" if [ ! -f "$checksums_file" ]; then warn "Checksums file not available, skipping verification" return 0 fi expected=$(grep "$(basename "$file")" "$checksums_file" | awk '{print $1}') if [ -z "$expected" ]; then warn "No checksum found for $(basename "$file"), skipping verification" return 0 fi if command -v sha256sum > /dev/null 2>&1; then actual=$(sha256sum "$file" | awk '{print $1}') elif command -v shasum > /dev/null 2>&1; then actual=$(shasum -a 256 "$file" | awk '{print $1}') else warn "No sha256 tool found, skipping checksum verification" return 0 fi if [ "$expected" != "$actual" ]; then error "Checksum verification failed for $(basename "$file") Expected: $expected Actual: $actual" fi success "Checksum verified" } # --- Install macOS --- install_macos() { app_name="Athas.app" info "Installing ${app_name%.app} to /Applications..." tar -xzf "$TMPDIR_INSTALL/$ARTIFACT" -C "$TMPDIR_INSTALL" # Remove quarantine attribute (curl doesn't set it, but just in case) xattr -cr "$TMPDIR_INSTALL/$app_name" 2>/dev/null || true if [ -d "/Applications/$app_name" ]; then rm -rf "/Applications/$app_name" fi mv "$TMPDIR_INSTALL/$app_name" "/Applications/$app_name" success "Installed ${app_name%.app} $VERSION to /Applications/$app_name" info "Launch with: open /Applications/$app_name" } # --- Linux runtime dependencies --- # The tarball links against the system WebKitGTK 4.1 and GTK 3. Bundles from # releases that still shipped CEF carry their own runtime in libexec/. bundle_uses_system_webview() { [ ! -e "$1/libexec/libcef.so" ] } has_webkitgtk() { for ldconfig_cmd in ldconfig /sbin/ldconfig /usr/sbin/ldconfig; do if command -v "$ldconfig_cmd" > /dev/null 2>&1; then if "$ldconfig_cmd" -p 2>/dev/null | grep -q 'libwebkit2gtk-4\.1\.so\.0'; then return 0 fi break fi done for lib_dir in /usr/lib /usr/lib64 /usr/local/lib /lib /lib64 \ /usr/lib/x86_64-linux-gnu /usr/lib/aarch64-linux-gnu; do if [ -e "$lib_dir/libwebkit2gtk-4.1.so.0" ]; then return 0 fi done return 1 } webkitgtk_install_hint() { if command -v apt-get > /dev/null 2>&1; then echo "sudo apt install libwebkit2gtk-4.1-0" elif command -v dnf > /dev/null 2>&1; then echo "sudo dnf install webkit2gtk4.1" elif command -v pacman > /dev/null 2>&1; then echo "sudo pacman -S webkit2gtk-4.1" elif command -v zypper > /dev/null 2>&1; then echo "sudo zypper install libwebkit2gtk-4_1-0" fi } require_webkitgtk() { if [ "${ATHAS_SKIP_DEPS_CHECK:-}" = "1" ] || has_webkitgtk; then return 0 fi hint=$(webkitgtk_install_hint) if [ -n "$hint" ]; then error "Athas needs WebKitGTK 4.1 (libwebkit2gtk-4.1), which is not installed. Install it with: $hint Then run this installer again. The AppImage download at https://athas.dev/download needs no system WebKitGTK." fi error "Athas needs WebKitGTK 4.1 (libwebkit2gtk-4.1), which is not installed. Install it with your distribution's package manager, then run this installer again. Set ATHAS_SKIP_DEPS_CHECK=1 to install anyway." } # --- Install Linux --- install_linux() { bin_dir="${ATHAS_BIN_DIR:-$HOME/.local/bin}" app_dir_name="athas.app" bin_name="athas" desktop_id="com.code.athas" product_name="Athas" app_dir="${ATHAS_APP_DIR:-$HOME/.local/share/athas/$app_dir_name}" app_parent=$(dirname "$app_dir") extract_dir="$TMPDIR_INSTALL/linux-extract" info "Installing $product_name to $app_dir..." need tar mkdir -p "$bin_dir" "$app_parent" "$extract_dir" tar -xzf "$TMPDIR_INSTALL/$ARTIFACT" -C "$extract_dir" extracted_app="$extract_dir/$app_dir_name" if [ ! -d "$extracted_app" ]; then extracted_app=$(find "$extract_dir" -maxdepth 2 -type d -name "$app_dir_name" | head -n 1) fi if [ -z "$extracted_app" ] || [ ! -d "$extracted_app" ]; then error "downloaded Linux bundle does not contain $app_dir_name" fi if [ ! -x "$extracted_app/bin/athas" ]; then error "downloaded Linux bundle does not contain bin/athas" fi if bundle_uses_system_webview "$extracted_app"; then require_webkitgtk fi rm -rf "$app_dir" mv "$extracted_app" "$app_dir" chmod +x "$app_dir/bin/athas" ln -sf "$app_dir/bin/athas" "$bin_dir/$bin_name" if [ -d "$app_dir/share/icons" ]; then mkdir -p "$HOME/.local/share/icons" cp -R "$app_dir/share/icons/." "$HOME/.local/share/icons/" 2>/dev/null || true fi desktop_file="$app_dir/share/applications/$desktop_id.desktop" if [ -f "$desktop_file" ]; then mkdir -p "$HOME/.local/share/applications" sed "s|^Exec=.*|Exec=$bin_dir/$bin_name %U|" "$desktop_file" \ > "$HOME/.local/share/applications/$desktop_id.desktop" fi success "Installed $product_name $VERSION to $app_dir" case ":$PATH:" in *":$bin_dir:"*) ;; *) warn "$bin_dir is not in your PATH" info "Add this to your shell profile:" info " export PATH=\"$bin_dir:\$PATH\"" ;; esac info "Launch with: $bin_name" } # --- Main --- main() { printf '\n \033[1mAthas Installer\033[0m\n\n' parse_args "$@" detect_platform get_version get_download_url TMPDIR_INSTALL=$(mktemp -d) info "Downloading $PRODUCT_PREFIX $VERSION for $PLATFORM ($ARCH)..." download "$DOWNLOAD_URL" "$TMPDIR_INSTALL/$ARTIFACT" 1 # Try to download checksums (may not exist for older releases) download "$CHECKSUMS_URL" "$TMPDIR_INSTALL/SHA256SUMS.txt" 2>/dev/null || true verify_checksum "$TMPDIR_INSTALL/$ARTIFACT" "$TMPDIR_INSTALL/SHA256SUMS.txt" case "$PLATFORM" in macos) install_macos ;; linux) install_linux ;; esac printf '\n' } main "$@"