|
| 1 | +#!/usr/bin/env sh |
| 2 | +# install.sh - Install Oite (oitec + unroll + rolls registry config) |
| 3 | +# Usage: curl -fsSL https://oite.org/install | sh |
| 4 | +# or: sh install.sh [--prefix DIR] [--no-modify-path] |
| 5 | +set -eu |
| 6 | + |
| 7 | +OITE_VERSION="${OITE_VERSION:-latest}" |
| 8 | +GITHUB_REPO="warpy-ai/oite" |
| 9 | +UNROLL_REPO="warpy-ai/unroll" |
| 10 | +REGISTRY_URL="https://registry.oite.org/api/v1" |
| 11 | +PREFIX="${OITE_PREFIX:-$HOME/.oite}" |
| 12 | +MODIFY_PATH=1 |
| 13 | + |
| 14 | +# Colors (only if terminal supports them) |
| 15 | +if [ -t 1 ]; then |
| 16 | + BOLD='\033[1m' |
| 17 | + GREEN='\033[0;32m' |
| 18 | + YELLOW='\033[0;33m' |
| 19 | + RED='\033[0;31m' |
| 20 | + RESET='\033[0m' |
| 21 | +else |
| 22 | + BOLD='' |
| 23 | + GREEN='' |
| 24 | + YELLOW='' |
| 25 | + RED='' |
| 26 | + RESET='' |
| 27 | +fi |
| 28 | + |
| 29 | +info() { printf "${GREEN}info${RESET}: %s\n" "$1"; } |
| 30 | +warn() { printf "${YELLOW}warn${RESET}: %s\n" "$1"; } |
| 31 | +err() { printf "${RED}error${RESET}: %s\n" "$1" >&2; exit 1; } |
| 32 | + |
| 33 | +# Parse args |
| 34 | +for arg in "$@"; do |
| 35 | + case "$arg" in |
| 36 | + --prefix=*) PREFIX="${arg#*=}" ;; |
| 37 | + --no-modify-path) MODIFY_PATH=0 ;; |
| 38 | + --version=*) OITE_VERSION="${arg#*=}" ;; |
| 39 | + --help) |
| 40 | + echo "Install Oite programming language toolchain" |
| 41 | + echo "" |
| 42 | + echo "Usage: curl -fsSL https://oite.org/install | sh" |
| 43 | + echo " or: sh install.sh [OPTIONS]" |
| 44 | + echo "" |
| 45 | + echo "Options:" |
| 46 | + echo " --prefix=DIR Install to DIR (default: ~/.oite)" |
| 47 | + echo " --version=TAG Install specific version (default: latest)" |
| 48 | + echo " --no-modify-path Don't modify shell profile" |
| 49 | + echo " --help Show this help" |
| 50 | + exit 0 |
| 51 | + ;; |
| 52 | + *) warn "Unknown option: $arg" ;; |
| 53 | + esac |
| 54 | +done |
| 55 | + |
| 56 | +detect_platform() { |
| 57 | + local os arch target |
| 58 | + |
| 59 | + os="$(uname -s)" |
| 60 | + arch="$(uname -m)" |
| 61 | + |
| 62 | + case "$os" in |
| 63 | + Darwin) os="apple-darwin" ;; |
| 64 | + Linux) os="unknown-linux-gnu" ;; |
| 65 | + *) err "Unsupported OS: $os. Oite supports macOS and Linux." ;; |
| 66 | + esac |
| 67 | + |
| 68 | + case "$arch" in |
| 69 | + x86_64|amd64) arch="x86_64" ;; |
| 70 | + arm64|aarch64) arch="aarch64" ;; |
| 71 | + *) err "Unsupported architecture: $arch. Oite supports x86_64 and aarch64." ;; |
| 72 | + esac |
| 73 | + |
| 74 | + target="${arch}-${os}" |
| 75 | + echo "$target" |
| 76 | +} |
| 77 | + |
| 78 | +check_dependencies() { |
| 79 | + for cmd in curl tar git; do |
| 80 | + if ! command -v "$cmd" >/dev/null 2>&1; then |
| 81 | + err "Required command '$cmd' not found. Please install it and try again." |
| 82 | + fi |
| 83 | + done |
| 84 | +} |
| 85 | + |
| 86 | +get_latest_version() { |
| 87 | + local url="https://api.github.com/repos/${GITHUB_REPO}/releases/latest" |
| 88 | + local response |
| 89 | + |
| 90 | + response=$(curl -fsSL "$url" 2>/dev/null) || err "Failed to fetch latest version from GitHub" |
| 91 | + |
| 92 | + # Extract tag_name from JSON |
| 93 | + echo "$response" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"//;s/".*//' |
| 94 | +} |
| 95 | + |
| 96 | +download_oitec() { |
| 97 | + local target="$1" |
| 98 | + local version="$2" |
| 99 | + local bin_dir="${PREFIX}/bin" |
| 100 | + local tmpdir |
| 101 | + |
| 102 | + tmpdir=$(mktemp -d) |
| 103 | + trap "rm -rf '$tmpdir'" EXIT |
| 104 | + |
| 105 | + local artifact="oitec-${target}.tar.gz" |
| 106 | + local url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${artifact}" |
| 107 | + |
| 108 | + info "Downloading oitec ${version} for ${target}..." |
| 109 | + if ! curl -fSL --progress-bar -o "${tmpdir}/${artifact}" "$url"; then |
| 110 | + rm -rf "$tmpdir" |
| 111 | + err "Failed to download ${url}. Check that a release exists for your platform." |
| 112 | + fi |
| 113 | + |
| 114 | + # Verify download |
| 115 | + if [ ! -s "${tmpdir}/${artifact}" ]; then |
| 116 | + rm -rf "$tmpdir" |
| 117 | + err "Downloaded file is empty" |
| 118 | + fi |
| 119 | + |
| 120 | + info "Extracting oitec..." |
| 121 | + mkdir -p "$bin_dir" |
| 122 | + tar -xzf "${tmpdir}/${artifact}" -C "$bin_dir" |
| 123 | + chmod +x "${bin_dir}/oitec" |
| 124 | + |
| 125 | + rm -rf "$tmpdir" |
| 126 | + trap - EXIT |
| 127 | + |
| 128 | + info "Installed oitec to ${bin_dir}/oitec" |
| 129 | +} |
| 130 | + |
| 131 | +install_unroll() { |
| 132 | + local version="$1" |
| 133 | + local lib_dir="${PREFIX}/lib/unroll" |
| 134 | + local bin_dir="${PREFIX}/bin" |
| 135 | + local tmpdir |
| 136 | + |
| 137 | + tmpdir=$(mktemp -d) |
| 138 | + trap "rm -rf '$tmpdir'" EXIT |
| 139 | + |
| 140 | + # Try downloading unroll source tarball from the release |
| 141 | + local url="https://github.com/${GITHUB_REPO}/releases/download/${version}/unroll-src.tar.gz" |
| 142 | + |
| 143 | + info "Downloading unroll package manager..." |
| 144 | + if curl -fSL --progress-bar -o "${tmpdir}/unroll-src.tar.gz" "$url" 2>/dev/null; then |
| 145 | + # Extract from release tarball |
| 146 | + mkdir -p "$lib_dir" |
| 147 | + tar -xzf "${tmpdir}/unroll-src.tar.gz" -C "$lib_dir" |
| 148 | + else |
| 149 | + # Fallback: clone from git |
| 150 | + warn "Release tarball not found, cloning unroll from git..." |
| 151 | + if ! git clone --depth 1 "https://github.com/${UNROLL_REPO}.git" "${tmpdir}/unroll" 2>/dev/null; then |
| 152 | + rm -rf "$tmpdir" |
| 153 | + err "Failed to download unroll source" |
| 154 | + fi |
| 155 | + mkdir -p "$lib_dir" |
| 156 | + cp -r "${tmpdir}/unroll/src" "$lib_dir/" |
| 157 | + if [ -f "${tmpdir}/unroll/unroll.toml" ]; then |
| 158 | + cp "${tmpdir}/unroll/unroll.toml" "$lib_dir/" |
| 159 | + fi |
| 160 | + fi |
| 161 | + |
| 162 | + rm -rf "$tmpdir" |
| 163 | + trap - EXIT |
| 164 | + |
| 165 | + # Create wrapper script |
| 166 | + mkdir -p "$bin_dir" |
| 167 | + cat > "${bin_dir}/unroll" << 'WRAPPER' |
| 168 | +#!/usr/bin/env sh |
| 169 | +OITE_HOME="${OITE_HOME:-$HOME/.oite}" |
| 170 | +exec "$OITE_HOME/bin/oitec" "$OITE_HOME/lib/unroll/src/main.ot" -- "$@" |
| 171 | +WRAPPER |
| 172 | + chmod +x "${bin_dir}/unroll" |
| 173 | + |
| 174 | + info "Installed unroll to ${lib_dir}" |
| 175 | +} |
| 176 | + |
| 177 | +configure_registry() { |
| 178 | + local unroll_dir="$HOME/.unroll" |
| 179 | + local config_file="${unroll_dir}/config.toml" |
| 180 | + |
| 181 | + mkdir -p "$unroll_dir" |
| 182 | + |
| 183 | + if [ ! -f "$config_file" ]; then |
| 184 | + cat > "$config_file" << EOF |
| 185 | +# Unroll package manager configuration |
| 186 | +# Generated by install.sh |
| 187 | +
|
| 188 | +[registry] |
| 189 | +default = "${REGISTRY_URL}" |
| 190 | +EOF |
| 191 | + info "Configured registry: ${REGISTRY_URL}" |
| 192 | + else |
| 193 | + info "Registry config already exists at ${config_file}, skipping" |
| 194 | + fi |
| 195 | +} |
| 196 | + |
| 197 | +setup_path() { |
| 198 | + if [ "$MODIFY_PATH" -eq 0 ]; then |
| 199 | + return 0 |
| 200 | + fi |
| 201 | + |
| 202 | + local bin_dir="${PREFIX}/bin" |
| 203 | + local path_entry="export PATH=\"${bin_dir}:\$PATH\"" |
| 204 | + |
| 205 | + # Check if already in PATH |
| 206 | + case ":$PATH:" in |
| 207 | + *":${bin_dir}:"*) info "PATH already includes ${bin_dir}"; return 0 ;; |
| 208 | + esac |
| 209 | + |
| 210 | + local shell_name |
| 211 | + shell_name="$(basename "${SHELL:-/bin/sh}")" |
| 212 | + |
| 213 | + local profile="" |
| 214 | + case "$shell_name" in |
| 215 | + zsh) |
| 216 | + profile="$HOME/.zshrc" |
| 217 | + ;; |
| 218 | + bash) |
| 219 | + if [ -f "$HOME/.bash_profile" ]; then |
| 220 | + profile="$HOME/.bash_profile" |
| 221 | + elif [ -f "$HOME/.bashrc" ]; then |
| 222 | + profile="$HOME/.bashrc" |
| 223 | + else |
| 224 | + profile="$HOME/.profile" |
| 225 | + fi |
| 226 | + ;; |
| 227 | + fish) |
| 228 | + # Fish uses a different syntax |
| 229 | + local fish_dir="$HOME/.config/fish/conf.d" |
| 230 | + mkdir -p "$fish_dir" |
| 231 | + echo "set -gx PATH ${bin_dir} \$PATH" > "${fish_dir}/oite.fish" |
| 232 | + info "Added ${bin_dir} to PATH in ${fish_dir}/oite.fish" |
| 233 | + return 0 |
| 234 | + ;; |
| 235 | + *) |
| 236 | + profile="$HOME/.profile" |
| 237 | + ;; |
| 238 | + esac |
| 239 | + |
| 240 | + if [ -n "$profile" ]; then |
| 241 | + # Check if already added |
| 242 | + if [ -f "$profile" ] && grep -q "\.oite/bin" "$profile" 2>/dev/null; then |
| 243 | + info "PATH entry already exists in ${profile}" |
| 244 | + return 0 |
| 245 | + fi |
| 246 | + |
| 247 | + echo "" >> "$profile" |
| 248 | + echo "# Oite programming language" >> "$profile" |
| 249 | + echo "$path_entry" >> "$profile" |
| 250 | + info "Added ${bin_dir} to PATH in ${profile}" |
| 251 | + fi |
| 252 | +} |
| 253 | + |
| 254 | +verify_install() { |
| 255 | + local bin_dir="${PREFIX}/bin" |
| 256 | + |
| 257 | + echo "" |
| 258 | + if [ -x "${bin_dir}/oitec" ]; then |
| 259 | + local version |
| 260 | + version=$("${bin_dir}/oitec" --version 2>/dev/null || echo "unknown") |
| 261 | + info "oitec installed: ${version}" |
| 262 | + else |
| 263 | + warn "oitec binary not found at ${bin_dir}/oitec" |
| 264 | + return 1 |
| 265 | + fi |
| 266 | + |
| 267 | + if [ -x "${bin_dir}/unroll" ]; then |
| 268 | + info "unroll installed: ${bin_dir}/unroll" |
| 269 | + else |
| 270 | + warn "unroll not found at ${bin_dir}/unroll" |
| 271 | + return 1 |
| 272 | + fi |
| 273 | + |
| 274 | + return 0 |
| 275 | +} |
| 276 | + |
| 277 | +print_success() { |
| 278 | + echo "" |
| 279 | + printf "${BOLD}${GREEN}Oite has been installed successfully!${RESET}\n" |
| 280 | + echo "" |
| 281 | + echo "To get started, you may need to restart your shell or run:" |
| 282 | + echo "" |
| 283 | + printf " ${BOLD}export PATH=\"${PREFIX}/bin:\$PATH\"${RESET}\n" |
| 284 | + echo "" |
| 285 | + echo "Then create your first project:" |
| 286 | + echo "" |
| 287 | + printf " ${BOLD}unroll new hello${RESET}\n" |
| 288 | + printf " ${BOLD}cd hello${RESET}\n" |
| 289 | + printf " ${BOLD}unroll build${RESET}\n" |
| 290 | + echo "" |
| 291 | + echo "Documentation: https://oite.org/docs" |
| 292 | + echo "Registry: https://registry.oite.org" |
| 293 | + echo "" |
| 294 | +} |
| 295 | + |
| 296 | +# ─── Main ─────────────────────────────────────────────────────────────────── |
| 297 | + |
| 298 | +main() { |
| 299 | + echo "" |
| 300 | + printf "${BOLD}Oite Installer${RESET}\n" |
| 301 | + echo "" |
| 302 | + |
| 303 | + check_dependencies |
| 304 | + |
| 305 | + local target |
| 306 | + target=$(detect_platform) |
| 307 | + info "Detected platform: ${target}" |
| 308 | + |
| 309 | + local version |
| 310 | + if [ "$OITE_VERSION" = "latest" ]; then |
| 311 | + version=$(get_latest_version) |
| 312 | + if [ -z "$version" ]; then |
| 313 | + err "Could not determine latest version" |
| 314 | + fi |
| 315 | + info "Latest version: ${version}" |
| 316 | + else |
| 317 | + version="$OITE_VERSION" |
| 318 | + fi |
| 319 | + |
| 320 | + download_oitec "$target" "$version" |
| 321 | + install_unroll "$version" |
| 322 | + configure_registry |
| 323 | + setup_path |
| 324 | + |
| 325 | + if verify_install; then |
| 326 | + print_success |
| 327 | + else |
| 328 | + warn "Installation completed with warnings. Check the output above." |
| 329 | + fi |
| 330 | +} |
| 331 | + |
| 332 | +main |
0 commit comments