Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/test-installer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test Installer

on:
pull_request:
paths:
- install.sh

jobs:
test-installer:
name: Test install.sh
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install latest release
run: |
sh install.sh
claude-profile --help

- name: Install specific version
run: |
VERSION=v0.1.0 sh install.sh
claude-profile --help

- name: Install to custom directory
run: |
mkdir -p "${{ runner.temp }}/bin"
INSTALL_DIR="${{ runner.temp }}/bin" sh install.sh
"${{ runner.temp }}/bin/claude-profile" --help

- name: Verify failure on nonexistent version
run: |
if VERSION=v99.99.99 sh install.sh 2>&1; then
echo "Expected failure for nonexistent version"
exit 1
fi
echo "Correctly failed on nonexistent version ✓"
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,37 @@ Each profile is a directory under `~/.claude-profiles/<name>/` containing:

## Installation

### From Source
### Quick Install (Recommended)

Requires Go 1.25+:
```bash
curl -fsSL https://raw.githubusercontent.com/diranged/claude-profile/main/install.sh | sh
```

This detects your OS and architecture, downloads the latest release, verifies the SHA-256 checksum, and installs to `/usr/local/bin`.

To install a specific version or to a custom directory:

```bash
git clone https://github.com/diranged/claude-profile-go.git
cd claude-profile-go
make install # Builds and copies to $GOPATH/bin
# Pin a version
curl -fsSL https://raw.githubusercontent.com/diranged/claude-profile/main/install.sh | VERSION=v0.1.0 sh

# Custom install directory
curl -fsSL https://raw.githubusercontent.com/diranged/claude-profile/main/install.sh | INSTALL_DIR=~/.local/bin sh
```

### From GitHub Releases

Download a prebuilt binary from the [Releases](https://github.com/diranged/claude-profile-go/releases) page. Binaries are available for Linux, macOS, and Windows on both amd64 and arm64.
Download a prebuilt binary from the [Releases](https://github.com/diranged/claude-profile/releases) page. Binaries are available for Linux, macOS, and Windows on both amd64 and arm64.

### From Source

Requires Go 1.25+:

```bash
git clone https://github.com/diranged/claude-profile.git
cd claude-profile
make install # Builds and copies to $GOPATH/bin
```

### From Go

Expand Down
152 changes: 152 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/bin/sh
# install.sh — install claude-profile from GitHub releases
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/diranged/claude-profile/main/install.sh | sh
# curl -fsSL ... | VERSION=v0.2.0 sh
#
# Environment variables:
# VERSION — release tag to install (default: latest)
# INSTALL_DIR — where to place the binary (default: /usr/local/bin)

set -eu

REPO="diranged/claude-profile"
BINARY="claude-profile"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"

# --- helpers ----------------------------------------------------------------

info() { printf ' \033[38;5;108m▸\033[0m %s\n' "$1"; }
warn() { printf ' \033[33m▸\033[0m %s\n' "$1"; }
error() { printf ' \033[31m✗\033[0m %s\n' "$1" >&2; exit 1; }

need_cmd() {
if ! command -v "$1" > /dev/null 2>&1; then
error "required command not found: $1"
fi
}

# --- detect platform --------------------------------------------------------

detect_os() {
case "$(uname -s)" in
Darwin) echo "darwin" ;;
Linux) echo "linux" ;;
*) error "unsupported OS: $(uname -s)" ;;
esac
}

detect_arch() {
case "$(uname -m)" in
x86_64|amd64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
*) error "unsupported architecture: $(uname -m)" ;;
esac
}

# --- resolve version --------------------------------------------------------

resolve_version() {
if [ -n "${VERSION:-}" ]; then
echo "$VERSION"
return
fi
need_cmd curl
# Follow the /releases/latest redirect and extract the tag from the final URL.
# No API auth or rate limits required.
local url
url=$(curl -fsSL -o /dev/null -w '%{url_effective}' \
"https://github.com/${REPO}/releases/latest")
local tag="${url##*/}"
if [ -z "$tag" ] || [ "$tag" = "latest" ]; then
error "could not determine latest release"
fi
echo "$tag"
}

# --- main -------------------------------------------------------------------

main() {
need_cmd curl
need_cmd tar
need_cmd uname

printf '\n \033[1mClaude Profile Installer\033[0m\n\n'

local os arch version
os=$(detect_os)
arch=$(detect_arch)
version=$(resolve_version)
local version_num="${version#v}"

info "Platform: ${os}/${arch}"
info "Version: ${version}"
info "Target: ${INSTALL_DIR}/${BINARY}"
printf '\n'

local archive="${BINARY}_${version_num}_${os}_${arch}.tar.gz"
local url="https://github.com/${REPO}/releases/download/${version}/${archive}"
local checksum_url="https://github.com/${REPO}/releases/download/${version}/checksums.txt"

# download to temp dir
TMPDIR_CLEANUP=$(mktemp -d)
trap 'rm -rf "$TMPDIR_CLEANUP"' EXIT
local tmpdir="$TMPDIR_CLEANUP"

info "Downloading ${archive}..."
if ! curl -fsSL -o "${tmpdir}/${archive}" "$url"; then
error "download failed — check that ${version} exists at https://github.com/${REPO}/releases"
fi

# verify checksum if sha256sum or shasum is available
if command -v sha256sum > /dev/null 2>&1 || command -v shasum > /dev/null 2>&1; then
info "Verifying checksum..."
curl -fsSL -o "${tmpdir}/checksums.txt" "$checksum_url"
local expected
expected=$(grep "${archive}" "${tmpdir}/checksums.txt" | awk '{print $1}')
if [ -z "$expected" ]; then
warn "checksum entry not found for ${archive}, skipping verification"
else
local actual
if command -v sha256sum > /dev/null 2>&1; then
actual=$(sha256sum "${tmpdir}/${archive}" | awk '{print $1}')
else
actual=$(shasum -a 256 "${tmpdir}/${archive}" | awk '{print $1}')
fi
if [ "$expected" != "$actual" ]; then
error "checksum mismatch!\n expected: ${expected}\n got: ${actual}"
fi
info "Checksum verified ✓"
fi
else
warn "sha256sum/shasum not found, skipping checksum verification"
fi

# extract
info "Extracting..."
tar -xzf "${tmpdir}/${archive}" -C "${tmpdir}"

# install
if [ -w "$INSTALL_DIR" ]; then
mv "${tmpdir}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
else
info "Elevated permissions required to write to ${INSTALL_DIR}"
sudo mv "${tmpdir}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
fi
chmod +x "${INSTALL_DIR}/${BINARY}"

printf '\n \033[38;5;108m✓\033[0m \033[1m%s %s\033[0m installed to %s\n\n' \
"$BINARY" "$version" "$INSTALL_DIR"

# verify it runs
if command -v "$BINARY" > /dev/null 2>&1; then
info "Run 'claude-profile --help' to get started"
else
warn "${INSTALL_DIR} may not be in your PATH"
warn "Add it with: export PATH=\"${INSTALL_DIR}:\$PATH\""
fi
printf '\n'
}

main