#!/bin/sh
ARCH=$(uname -m)
EPKG_STATIC="epkg"
EPKG_CACHE="$HOME/.cache/epkg/downloads/epkg"
GITEE_API_BASE="https://gitee.com/api/v5/repos"
GITEE_OWNER="wu_fengguang"
GITEE_REPO="epkg"
CHANNEL=""
STORE_MODE="auto"
parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
-c|--channel)
if [ -z "$2" ] || [ "${2#-}" != "$2" ]; then
print_error "Option $1 requires an argument"
fi
CHANNEL="$2"
shift 2
;;
--store)
if [ -z "$2" ] || [ "${2#-}" != "$2" ]; then
print_error "Option $1 requires an argument"
fi
case "$2" in
shared|private|auto)
STORE_MODE="$2"
;;
*)
print_error "Invalid store mode: $2. Must be one of: shared, private, auto"
;;
esac
shift 2
;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo
echo "Options:"
echo " -c, --channel CHANNEL Set the channel for the main environment"
echo " --store MODE Store mode: shared, private, or auto (default: auto)"
echo " -h, --help Show this help message"
echo
echo "Examples:"
echo " $0 --channel alpine:3.21"
echo " $0 --store shared"
echo " $0 --channel debian:trixie --store private"
exit 0
;;
*)
print_error "Unknown option: $1"
;;
esac
done
}
print_step() {
echo ">> $1"
}
print_info() {
echo "$1"
}
print_error() {
echo "ERROR: $1" >&2
exit 1
}
check_architecture() {
case "$ARCH" in
x86_64|aarch64|riscv64|loongarch64) : ;;
*)
print_error "Unsupported architecture: $ARCH"
;;
esac
}
detect_os_family() {
local uname_s
uname_s=$(uname -s 2>/dev/null || echo "unknown")
case "$uname_s" in
Linux)
echo "linux"
;;
Darwin)
echo "macos"
;;
CYGWIN*|MINGW*|MSYS*)
echo "windows"
;;
*)
print_error "Unsupported OS: $uname_s"
;;
esac
}
normalize_arch() {
case "$ARCH" in
amd64) ARCH="x86_64" ;;
arm64) ARCH="aarch64" ;;
*) ;;
esac
}
download_epkg_asset() {
local asset_name="$1"
local latest_version="$2"
local binary_url="https://gitee.com/${GITEE_OWNER}/${GITEE_REPO}/releases/download/${latest_version}/${asset_name}"
local sha_url="${binary_url}.sha256"
local sha_file="${asset_name}.sha256"
echo
echo "Downloading ${sha_file} ..."
rm -f "./${sha_file}" "./${asset_name}" 2>/dev/null || true
curl -L -# -o "./${sha_file}" "${sha_url}" --connect-timeout 15 --max-time 30 || return 1
if [ ! -s "./${sha_file}" ]; then
return 1
fi
if grep -q -i '<html\|<!DOCTYPE\|<body' "./${sha_file}" 2>/dev/null; then
return 1
fi
if grep -q '{' "./${sha_file}" 2>/dev/null; then
return 1
fi
if ! grep -q -E '^[0-9a-f]{64}[ *]' "./${sha_file}" 2>/dev/null; then
return 1
fi
echo "Downloading ${asset_name} ..."
curl -L -# -o "./${asset_name}" "${binary_url}" --retry 5 --connect-timeout 15 --max-time 300 || return 1
chmod +x "./${asset_name}" || true
command -v sha256sum >/dev/null || return 0
sha256sum -c "./${sha_file}" || return 1
return 0
}
setup_environment() {
mkdir -p "$EPKG_CACHE" || exit
}
check_git_tree() {
local SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
local PROJECT_ROOT=$(dirname "$SCRIPT_DIR")
if [ -d "$PROJECT_ROOT/.git" ] && [ -x "$PROJECT_ROOT/target/debug/epkg" ]; then
EPKG_PATH="$PROJECT_ROOT/target/debug/epkg"
return 0
fi
return 1
}
fetch_show_latest_release() {
local api_url="${GITEE_API_BASE}/${GITEE_OWNER}/${GITEE_REPO}/releases/latest"
local response
response=$(curl -s --connect-timeout 15 --max-time 30 "$api_url") || {
print_error "Failed to fetch release info from Gitee API: $api_url"
}
local tag_name
if command -v jq >/dev/null 2>&1; then
tag_name=$(printf '%s' "$response" | jq -r '.tag_name // empty')
elif command -v python3 >/dev/null 2>&1; then
tag_name=$(printf '%s' "$response" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tag_name', ''))" 2>/dev/null)
elif command -v python >/dev/null 2>&1; then
tag_name=$(printf '%s' "$response" | python -c "import sys, json; print(json.load(sys.stdin).get('tag_name', ''))" 2>/dev/null)
fi
if [ -z "$tag_name" ] || [ "$tag_name" = "null" ] || [ "$tag_name" = "" ]; then
tag_name=$(printf '%s' "$response" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
fi
if [ -z "$tag_name" ] || [ "$tag_name" = "null" ] || [ "$tag_name" = "" ]; then
print_error "Failed to parse release tag from Gitee API response: $api_url"
fi
echo "$tag_name"
}
download_files() {
if check_git_tree; then
echo
print_info "Using local binary from git tree: $EPKG_PATH"
return
fi
print_info "Fetching latest release from Gitee..."
local latest_version
latest_version=$(fetch_show_latest_release) || exit 1
local OS_FAMILY
OS_FAMILY=$(detect_os_family)
local ASSET_NAME
case "$OS_FAMILY" in
linux)
ASSET_NAME="${EPKG_STATIC}-linux-${ARCH}"
;;
macos)
case "$ARCH" in
x86_64|aarch64) ;;
*) print_error "Unsupported architecture for macOS: $ARCH" ;;
esac
ASSET_NAME="${EPKG_STATIC}-macos-${ARCH}"
;;
windows)
case "$ARCH" in
x86_64|aarch64) ;;
*) print_error "Unsupported architecture for Windows: $ARCH" ;;
esac
ASSET_NAME="${EPKG_STATIC}-windows-${ARCH}.exe"
;;
*)
print_error "Unsupported OS family: $OS_FAMILY"
;;
esac
cd "$EPKG_CACHE" || exit
echo
print_info "Latest release: $latest_version"
print_info "Destination: $EPKG_CACHE"
local LEGACY_ASSET_NAME=""
case "$OS_FAMILY" in
linux)
LEGACY_ASSET_NAME="${EPKG_STATIC}-${ARCH}"
;;
esac
if download_epkg_asset "$ASSET_NAME" "$latest_version"; then
EPKG_PATH="./${ASSET_NAME}"
return 0
fi
if [ -n "$LEGACY_ASSET_NAME" ]; then
print_info "New linux asset not found, falling back to legacy: $LEGACY_ASSET_NAME"
if download_epkg_asset "$LEGACY_ASSET_NAME" "$latest_version"; then
EPKG_PATH="./${LEGACY_ASSET_NAME}"
return 0
fi
fi
print_error "Failed to download epkg binary for ${OS_FAMILY}/${ARCH} (${ASSET_NAME}${LEGACY_ASSET_NAME:+, ${LEGACY_ASSET_NAME}})"
}
initialize_epkg() {
local install_cmd="$EPKG_PATH self install --store=$STORE_MODE"
if [ -n "$CHANNEL" ]; then
install_cmd="$install_cmd --channel=$CHANNEL"
fi
echo
if [ "$USER" = "root" ] || [ "$LOGNAME" = "root" ] || [ "$(id -u)" = "0" ]; then
print_info "Installation mode: shared (system-wide)"
else
print_info "Installation mode: private (user-local)"
fi
if [ -n "$CHANNEL" ]; then
print_info "Installing epkg with channel: $CHANNEL"
fi
print_info "Store mode: $STORE_MODE"
$install_cmd || exit
}
print_completion() {
echo
echo "================================================="
echo " Installation Complete "
echo "================================================="
print_info "Usage:"
print_info " epkg search [pattern] - Search for packages"
print_info " epkg install [pkg] - Install packages"
print_info " epkg remove [pkg] - Remove packages"
print_info " epkg list - List packages"
print_info " epkg update - Update repo data"
print_info " epkg upgrade - Upgrade packages"
print_info " epkg --help - Show detailed help"
}
check_duplicate_install()
{
test -d $HOME/.epkg/envs/main && {
echo "epkg was already initialized for current user"
echo "TO upgrade epkg: epkg self upgrade"
echo "TO uninstall epkg: epkg self remove"
exit 1
}
}
main() {
check_duplicate_install
parse_args "$@"
normalize_arch
check_architecture
setup_environment
download_files
initialize_epkg
print_completion
}
main "$@"