#!/usr/bin/env bash
set -euo pipefail
REPO="argotorg/fe"
FE_HOME="${FE_HOME:-$HOME/.fe}"
FE_BIN_DIR="$FE_HOME/bin"
MODIFY_PATH=true
REQUESTED_VERSION=""
# --- Colors ---
setup_colors() {
if [ -t 1 ]; then
BOLD="\033[1m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
RED="\033[0;31m"
CYAN="\033[0;36m"
RESET="\033[0m"
else
BOLD=""
GREEN=""
YELLOW=""
RED=""
CYAN=""
RESET=""
fi
}
say() {
printf "%b\n" "${GREEN}${BOLD}feup${RESET}: $1"
}
warn() {
printf "%b\n" "${YELLOW}${BOLD}warning${RESET}: $1" >&2
}
err() {
printf "%b\n" "${RED}${BOLD}error${RESET}: $1" >&2
exit 1
}
# --- Dependency checks ---
need_cmd() {
if ! command -v "$1" > /dev/null 2>&1; then
err "need '$1' (command not found)"
fi
}
# --- Usage ---
usage() {
cat </dev/null | grep -q 1; then
warn "running under Rosetta 2 â installing native arm64 binary"
ARCH="arm64"
else
ARCH="amd64"
fi
;;
aarch64|arm64)
ARCH="arm64"
;;
*) err "unsupported architecture: $arch" ;;
esac
}
# --- Version resolution ---
resolve_version() {
if [ -n "$REQUESTED_VERSION" ]; then
# Ensure version starts with 'v'
case "$REQUESTED_VERSION" in
v*) VERSION="$REQUESTED_VERSION" ;;
*) VERSION="v${REQUESTED_VERSION}" ;;
esac
return
fi
say "fetching latest release..."
local curl_args=(-sL --fail)
if [ -n "${GITHUB_TOKEN:-}" ]; then
curl_args+=(-H "Authorization: token $GITHUB_TOKEN")
fi
# Get the most recent release (including pre-releases) via the API
local api_response
api_response=$(curl "${curl_args[@]}" "https://api.github.com/repos/${REPO}/releases?per_page=1") || {
err "failed to fetch release info from GitHub. If rate-limited, set GITHUB_TOKEN"
}
if command -v jq >/dev/null 2>&1; then
VERSION=$(printf '%s' "$api_response" | jq -r 'map(select(.draft == false))[0].tag_name // empty')
else
VERSION=$(printf '%s' "$api_response" | grep -o '"tag_name":[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tag_name":[[:space:]]*"//;s/"//')
fi
if [ -z "$VERSION" ]; then
err "could not determine latest version from GitHub API"
fi
}
# --- Download ---
download_fe() {
local asset_name="fe_${OS}_${ARCH}"
local url="https://github.com/${REPO}/releases/download/${VERSION}/${asset_name}"
say "installing Fe ${CYAN}${VERSION}${RESET}"
say " platform: ${CYAN}${OS}_${ARCH}${RESET}"
say " url: ${CYAN}${url}${RESET}"
mkdir -p "$FE_BIN_DIR"
local tmp_file
tmp_file=$(mktemp)
trap 'rm -f "$tmp_file"' EXIT
local curl_args=(-L --fail --progress-bar -o "$tmp_file")
if [ -n "${GITHUB_TOKEN:-}" ]; then
curl_args+=(-H "Authorization: token $GITHUB_TOKEN")
fi
curl "${curl_args[@]}" "$url" || {
rm -f "$tmp_file"
err "download failed â check that version '${VERSION}' exists and has an asset for ${OS}_${ARCH}
url: ${url}"
}
chmod +x "$tmp_file"
# macOS quarantines binaries downloaded via curl; remove the flag so the
# binary can execute without Gatekeeper killing it (Abort trap: 6).
if [ "$OS" = "mac" ]; then
xattr -d com.apple.quarantine "$tmp_file" 2>/dev/null || true
fi
mv "$tmp_file" "$FE_BIN_DIR/fe"
trap - EXIT
say " installed to: ${CYAN}${FE_BIN_DIR}/fe${RESET}"
}
# --- Install feup itself ---
install_feup() {
# Place this script into FE_BIN_DIR so `feup` is available for future updates.
local target="$FE_BIN_DIR/feup"
local self
local script_ref="${VERSION}"
local script_url
local fallback_url
self="$(cd "$(dirname "$0")" 2>/dev/null && pwd)/$(basename "$0")"
if [ -f "$self" ]; then
# Running from a file â copy it (unless it IS the target already).
local self_abs target_abs
self_abs="$(cd "$(dirname "$self")" && pwd -P)/$(basename "$self")"
target_abs="$(cd "$(dirname "$target")" 2>/dev/null && pwd -P)/$(basename "$target")" 2>/dev/null || true
if [ "$self_abs" = "$target_abs" ]; then
return
fi
cp "$self" "$target"
else
# Running via pipe (curl | bash) â prefer matching version, then fall back to master.
script_url="https://raw.githubusercontent.com/${REPO}/${script_ref}/feup/feup.sh"
fallback_url="https://raw.githubusercontent.com/${REPO}/master/feup/feup.sh"
say "downloading feup script (${script_ref})..."
local curl_args=(-fsSL -o "$target")
if [ -n "${GITHUB_TOKEN:-}" ]; then
curl_args+=(-H "Authorization: token $GITHUB_TOKEN")
fi
if ! curl "${curl_args[@]}" "$script_url"; then
warn "could not download feup script from ${script_url}"
if [ "$script_ref" != "main" ]; then
say "falling back to feup script (master)..."
if ! curl "${curl_args[@]}" "$fallback_url"; then
rm -f "$target"
warn "could not download feup script from ${fallback_url} â 'feup' command won't be available"
return 0
fi
else
rm -f "$target"
warn "'feup' command won't be available"
return 0
fi
fi
fi
chmod +x "$target"
}
# --- PATH configuration ---
create_env_file() {
local env_file="$FE_HOME/env"
cat > "$env_file" </dev/null; then
cat > "$fish_conf" </dev/null; then
continue
fi
printf '\n# Fe toolchain\n%s\n' "$source_line" >> "$profile"
modified=true
say " modified: ${CYAN}${profile}${RESET}"
done
if [ "$modified" = true ]; then
say ""
say "restart your shell or run:"
say " ${CYAN}source \"$FE_HOME/env\"${RESET}"
fi
}
# --- Main ---
main() {
setup_colors
parse_args "$@"
need_cmd curl
need_cmd chmod
need_cmd mkdir
need_cmd uname
printf "\n"
say "${BOLD}feup${RESET} â the Fe toolchain installer"
printf "\n"
detect_platform
resolve_version
download_fe
install_feup
create_env_file
modify_shell_profile
printf "\n"
say "${GREEN}done!${RESET} Fe ${CYAN}${VERSION}${RESET} is installed at ${CYAN}${FE_BIN_DIR}/fe${RESET}"
# Verify the installation
if "$FE_BIN_DIR/fe" --version > /dev/null 2>&1; then
local installed_version
installed_version=$("$FE_BIN_DIR/fe" --version 2>&1 || true)
say " ${installed_version}"
fi
printf "\n"
}
main "$@"