diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..c543b9d --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,31 @@ +name: ShellCheck + +on: + push: + branches: [main] + paths: ["**.sh"] + pull_request: + branches: [main] + paths: ["**.sh"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + shellcheck: + name: ShellCheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run ShellCheck (strictest settings) + run: | + shellcheck --version + shellcheck \ + --shell=sh \ + --severity=style \ + --enable=all \ + --external-sources \ + --format=gcc \ + ./install.sh diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml new file mode 100644 index 0000000..76282c9 --- /dev/null +++ b/.github/workflows/test-install.yml @@ -0,0 +1,92 @@ +name: Test install.sh + +on: + push: + branches: [main] + paths: [install.sh] + pull_request: + branches: [main] + paths: [install.sh] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + deb: + name: DEB · ${{ matrix.distro.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.distro.image }} + options: --user root + strategy: + fail-fast: false + matrix: + distro: + - { name: debian-12, image: "debian:12" } + - { name: debian-13, image: "debian:trixie" } + - { name: ubuntu-20.04, image: "ubuntu:20.04" } + - { name: ubuntu-22.04, image: "ubuntu:22.04" } + - { name: ubuntu-24.04, image: "ubuntu:24.04" } + steps: + - name: Install curl + run: | + apt-get update + apt-get install -y curl + + - uses: actions/checkout@v4 + + - name: Run install.sh -y + run: sh install.sh -y + + - name: Verify git-fs is on PATH + run: git-fs --version + + rpm: + name: RPM · ${{ matrix.distro.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.distro.image }} + options: --user root + strategy: + fail-fast: false + matrix: + distro: + - { name: rocky-8, image: "rockylinux:8" } + - { name: rocky-9, image: "rockylinux:9" } + - { name: alma-8, image: "almalinux:8" } + - { name: alma-9, image: "almalinux:9" } + - { name: fedora-40, image: "fedora:40" } + - { name: fedora-41, image: "fedora:41" } + - { name: fedora-42, image: "fedora:42" } + - { name: fedora-43, image: "fedora:43" } + steps: + - name: Install curl and git + run: dnf install -y --allowerasing curl git + + - uses: actions/checkout@v4 + + - name: Run install.sh -y + run: sh install.sh -y + + - name: Verify git-fs is on PATH + run: git-fs --version + + tarball: + name: Tarball · archlinux + runs-on: ubuntu-latest + container: + image: archlinux:latest + options: --user root + steps: + - name: Install dependencies + run: pacman -Sy --noconfirm curl git + + - uses: actions/checkout@v4 + + - name: Run install.sh -y + run: sh install.sh -y + + - name: Verify git-fs was installed + run: test -x /usr/local/bin/git-fs diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..80d6c7d --- /dev/null +++ b/install.sh @@ -0,0 +1,278 @@ +#!/bin/sh +# shellcheck disable=SC1091 # /etc/os-release is not available at lint time +set -eu + +BASE_URL="https://github.com/mesa-dot-dev/git-fs/releases/latest/download" +DEFAULT_INSTALL_DIR="/usr/local/bin" +AUTO_YES=false +TMPDIR="" + +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + CYAN='\033[0;36m' + YELLOW='\033[0;33m' + BOLD='\033[1m' + RESET='\033[0m' +else + RED='' GREEN='' CYAN='' YELLOW='' BOLD='' RESET='' +fi + +info() { printf "%b-->%b %b\n" "${CYAN}" "${RESET}" "$1"; } +success() { printf "%b-->%b %b\n" "${GREEN}" "${RESET}" "$1"; } +warn() { printf "%b-->%b %b\n" "${YELLOW}" "${RESET}" "$1" >&2; } +error() { printf "%b-->%b %b\n" "${RED}" "${RESET}" "$1" >&2; exit 1; } + +cleanup() { + if [ -n "${TMPDIR}" ] && [ -d "${TMPDIR}" ]; then + rm -rf "${TMPDIR}" + fi +} +trap cleanup EXIT + +sudo_cmd() { + _uid=$(id -u) + if [ "${_uid}" -eq 0 ]; then + "$@" + else + sudo "$@" + fi +} + +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + -y|--yes) AUTO_YES=true ;; + -h|--help) + printf "Usage: install.sh [OPTIONS]\n\n" + printf "Install or update git-fs.\n\n" + printf "Options:\n" + printf " -y, --yes Non-interactive mode (use defaults, requires root)\n" + printf " -h, --help Show this help message\n" + exit 0 + ;; + *) error "Unknown option: $1 (use -h for help)" ;; + esac + shift + done +} + +check_deps() { + if ! command -v curl >/dev/null 2>&1; then + error "curl is required but not found. Install it with your package manager." + fi +} + +detect_os() { + _os=$(uname -s) + case "${_os}" in + Darwin) + warn "macOS is not yet supported by this install script." + info "Download manually from:" + info " ${BASE_URL}/git-fs-macos-universal.tar.gz" + printf "\n" + exit 0 + ;; + Linux) ;; + *) error "Unsupported operating system: ${_os}" ;; + esac +} + +detect_arch() { + _machine=$(uname -m) + case "${_machine}" in + x86_64) echo "amd64 x86_64 amd64" ;; + aarch64) echo "arm64 aarch64 arm64" ;; + arm64) echo "arm64 aarch64 arm64" ;; + *) error "Unsupported architecture: ${_machine}" ;; + esac +} + +detect_distro() { + if [ ! -f /etc/os-release ]; then + echo "tarball" + return + fi + + # shellcheck disable=SC2154 # ID and VERSION_ID are defined by os-release + _distro_id=$(. /etc/os-release && echo "${ID}") + _version_id=$(. /etc/os-release && echo "${VERSION_ID:-}") + + case "${_distro_id}" in + debian) + case "${_version_id}" in + 12|13) echo "deb debian-${_version_id} ${_distro_id} ${_version_id}" ;; + *) echo "tarball _ ${_distro_id} ${_version_id}" ;; + esac + ;; + ubuntu) + case "${_version_id}" in + 20.04|22.04|24.04) echo "deb ubuntu-${_version_id} ${_distro_id} ${_version_id}" ;; + *) echo "tarball _ ${_distro_id} ${_version_id}" ;; + esac + ;; + rocky) + _major=$(echo "${_version_id}" | cut -d. -f1) + case "${_major}" in + 8|9) echo "rpm rocky-${_major} ${_distro_id} ${_version_id}" ;; + *) echo "tarball _ ${_distro_id} ${_version_id}" ;; + esac + ;; + almalinux) + _major=$(echo "${_version_id}" | cut -d. -f1) + case "${_major}" in + 8|9) echo "rpm alma-${_major} ${_distro_id} ${_version_id}" ;; + *) echo "tarball _ ${_distro_id} ${_version_id}" ;; + esac + ;; + fedora) + case "${_version_id}" in + 40|41|42|43) echo "rpm fedora-${_version_id} ${_distro_id} ${_version_id}" ;; + *) echo "tarball _ ${_distro_id} ${_version_id}" ;; + esac + ;; + *) + echo "tarball _ ${_distro_id} ${_version_id}" + ;; + esac +} + +prompt_tarball_install() { + _auto_yes="$1" _distro_id="$2" _distro_ver="$3" + _install_dir="${DEFAULT_INSTALL_DIR}" + + if [ "${_auto_yes}" = true ]; then + info "No native package for this distro. Installing generic Linux binary to ${_install_dir}." >&2 + echo "${_install_dir}" + return + fi + + printf "\n" >&2 + if [ "${_distro_id}" != "_" ] && [ -n "${_distro_id}" ]; then + warn "No native package available for ${_distro_id} ${_distro_ver}." + else + warn "Could not detect your Linux distribution." + fi + printf " Would you like to install the generic Linux binary instead? [Y/n] " >&2 + read -r answer &2; exit 0 ;; + *) ;; + esac + + printf " Install location [%s]: " "${DEFAULT_INSTALL_DIR}" >&2 + read -r custom_dir &2 + read -r create_answer /dev/null 2>&1; then + sudo_cmd dnf install -y "${_filepath}" + elif command -v yum >/dev/null 2>&1; then + sudo_cmd yum install -y "${_filepath}" + else + error "Neither dnf nor yum found. Cannot install rpm package." + fi + ;; + tarball) + info "Installing binary to ${_install_dir}..." + tar -xzf "${_filepath}" -C "$(dirname "${_filepath}")" + sudo_cmd install -m 755 "$(dirname "${_filepath}")/git-fs" "${_install_dir}/git-fs" + warn "Note: git-fs requires FUSE3. Install it with your package manager if not already present." + ;; + *) + error "Unknown package type: ${_pkg_type}" + ;; + esac +} + +verify_install() { + if command -v git-fs >/dev/null 2>&1; then + _version=$(git-fs --version 2>/dev/null || echo "unknown") + success "git-fs installed successfully! (${_version})" + else + success "git-fs installed successfully!" + fi +} + +main() { + parse_args "$@" + check_deps + + printf "\n" + info "${BOLD}git-fs installer${RESET}" + printf "\n" + + detect_os + + arch=$(detect_arch) + arch_deb=$(echo "${arch}" | cut -d' ' -f1) + arch_rpm=$(echo "${arch}" | cut -d' ' -f2) + arch_tar=$(echo "${arch}" | cut -d' ' -f3) + + distro_info=$(detect_distro) + pkg_type=$(echo "${distro_info}" | cut -d' ' -f1) + distro=$(echo "${distro_info}" | cut -d' ' -f2) + distro_id=$(echo "${distro_info}" | cut -d' ' -f3) + distro_ver=$(echo "${distro_info}" | cut -d' ' -f4) + + install_dir="${DEFAULT_INSTALL_DIR}" + if [ "${pkg_type}" = "tarball" ]; then + install_dir=$(prompt_tarball_install "${AUTO_YES}" "${distro_id:-_}" "${distro_ver:-}") + fi + + _uid=$(id -u) + if [ "${AUTO_YES}" = true ] && [ "${_uid}" -ne 0 ]; then + error "Non-interactive mode requires root. Re-run with: sudo $0 -y" + fi + + filename=$(build_filename "${pkg_type}" "${distro}" "${arch_deb}" "${arch_rpm}" "${arch_tar}") + url="${BASE_URL}/${filename}" + + download "${filename}" "${url}" + install_package "${pkg_type}" "${TMPDIR}/${filename}" "${install_dir}" + verify_install + printf "\n" +} + +main "$@"