
すごいログ分析ツール(TWSLA)のインストーラーをシェルスクリプトで作っています
今朝は3時40分に、助手の猫さんが起こしにきました。リビングで寝ていた、お腹が空いて起きたらしいです。ご飯を食べて、かみさんの布団に行って寝ました。おかげで、開発時間はたっぷりあります。
Software Design 12月号
のシェルスクリプトの記事を読んでいて、最近、プログラムのインストラーがシェルスクリプトで作られているものが多いことを思い出しました。
とか
です。
$curl -sS https://starship.rs/install.sh | sh
のような感じで、スクリプトをダウンロードして実行することでインストールできます。
TWSLAをLinuxにbrewでインストールした時、苦労したのでなんとかしたいと思っていましたが、シェルスクリプトで作れば解決できそうです。
今朝は、猫さんのおかげで早起きしたので作ってみることにしました。
まずは、starshipのinnstall.shを調べてみました。
スクリプトを真面目に読んみました。
詳しい記事はqiitに書こうと思いますが、マインドマップに動作をまとめると

のような動作です。上から順番に実行していきます。
中身がクリアになったので、TWSLAで対応する方向が見えてきました。
対応するOSはLinuxとDarwin(Mac OS)
対応するCPUはx86_64とARM64
インストール後の設定情報はなくてよい
POSIXのシェルの判断は不要
強制インストールも不要
ターゲットの指定は引数でしない
という感じで、いろいろ省略して

のような感じになりました。
この方針で変更してできたスクリプトは
#!/usr/bin/env sh
# エラーと変数未定義で停止
set -eu
printf '\n'
# 表示で文字の色を変えるための変数
BOLD="$(tput bold 2>/dev/null || printf '')"
GREY="$(tput setaf 0 2>/dev/null || printf '')"
UNDERLINE="$(tput smul 2>/dev/null || printf '')"
RED="$(tput setaf 1 2>/dev/null || printf '')"
GREEN="$(tput setaf 2 2>/dev/null || printf '')"
YELLOW="$(tput setaf 3 2>/dev/null || printf '')"
BLUE="$(tput setaf 4 2>/dev/null || printf '')"
MAGENTA="$(tput setaf 5 2>/dev/null || printf '')"
NO_COLOR="$(tput sgr0 2>/dev/null || printf '')"
# ターゲット環境
SUPPORTED_TARGETS="Linux_x86_64 Linux_arm64 \
Darwin_x86_64 Darwin_arm64"
# メッセージを表示する関数
info() {
printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*"
}
warn() {
printf '%s\n' "${YELLOW}! $*${NO_COLOR}"
}
error() {
printf '%s\n' "${RED}x $*${NO_COLOR}" >&2
}
completed() {
printf '%s\n' "${GREEN}✓${NO_COLOR} $*"
}
# コマンドの有無を判定する関数
has() {
command -v "$1" 1>/dev/null 2>&1
}
# 一時ファイルを取得する
get_tmpfile() {
suffix="$1"
if has mktemp; then
printf "%s.%s" "$(mktemp)" "${suffix}"
else
printf "/tmp/twsla.%s" "${suffix}"
fi
}
# 指定の場所が書き込み可能であることを判断する関数
test_writeable() {
path="${1:-}/test.txt"
if touch "${path}" 2>/dev/null; then
rm "${path}"
return 0
else
return 1
fi
}
# ファイルをダウンロードする関数
download() {
file="$1"
url="$2"
# curl , wget ,fetchの順番でダウンロードするコマンドを選択する
if has curl ; then
cmd="curl --fail --silent --location --output $file $url"
elif has wget; then
cmd="wget --quiet --output-document=$file $url"
elif has fetch; then
cmd="fetch --quiet --output=$file $url"
else
error "No HTTP download program (curl, wget, fetch) found, exiting…"
return 1
fi
$cmd && return 0 || rc=$?
error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}"
printf "\n" >&2
info "This is likely due to twsla not yet supporting your configuration."
info "If you would like to see a build for your configuration,"
info "please create an issue requesting a build for ${MAGENTA}${TARGET}${NO_COLOR}:"
info "${BOLD}${UNDERLINE}https://github.com/twsnmp/twsla/issues/new/${NO_COLOR}"
return $rc
}
# ダウンロードしたファイルを解凍する関数
unpack() {
archive=$1
bin_dir=$2
sudo=${3-}
case "$archive" in
*.tar.gz)
flags=$(test -n "${VERBOSE-}" && echo "-xzvof" || echo "-xzof")
${sudo} tar "${flags}" "${archive}" -C "${bin_dir}"
return 0
;;
*.zip)
flags=$(test -z "${VERBOSE-}" && echo "-qqo" || echo "-o")
UNZIP="${flags}" ${sudo} unzip "${archive}" -d "${bin_dir}"
return 0
;;
esac
error "Unknown package extension."
printf "\n"
info "This almost certainly results from a bug in this script--please file a"
info "bug report at https://github.com/twsnmp/twsla/issues"
return 1
}
# スクリプトの使用方法を説明した関数
usage() {
printf "%s\n" \
"install.sh [option]" \
"" \
"Fetch and install the latest version of twsla, if twsla is already" \
"installed it will be updated to the latest version."
printf "\n%s\n" "Options"
printf "\t%s\n\t\t%s\n\n" \
"-V, --verbose" "Enable verbose output for the installer" \
"-b, --bin-dir" "Override the bin installation directory [default: ${BIN_DIR}]" \
"-v, --version" "Install a specific version of twsla [default: ${VERSION}]" \
"-h, --help" "Display this help message"
}
# sudoで管理者権限を取得できるか判断する関数
elevate_priv() {
if ! has sudo; then
error 'Could not find the command "sudo", needed to get permissions for install.'
info "If you are on Windows, please run your shell as an administrator, then"
info "rerun this script. Otherwise, please run this script as root, or install"
info "sudo."
exit 1
fi
if ! sudo -v; then
error "Superuser not granted, aborting installation"
exit 1
fi
}
# インストールする関数
install() {
ext="$1"
if test_writeable "${BIN_DIR}"; then
sudo=""
msg="Installing twsla, please wait…"
else
warn "Escalated permissions are required to install to ${BIN_DIR}"
elevate_priv
sudo="sudo"
msg="Installing twsla as root, please wait…"
fi
info "$msg"
archive=$(get_tmpfile "$ext")
# download to the temp file
download "${archive}" "${URL}"
# unpack the temp file to the bin dir, using sudo if required
unpack "${archive}" "${BIN_DIR}" "${sudo}"
}
# ターゲットのOSを判断する関数
# Currently supporting:
# - darwin
# - linux
detect_platform() {
platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "${platform}" in
linux) platform="Linux" ;;
darwin) platform="Darwin" ;;
esac
printf '%s' "${platform}"
}
# ターゲットのCPUアーキテクチャを判断する関数
# Currently supporting:
# - x86_64
# - arm64
detect_arch() {
arch="$(uname -m | tr '[:upper:]' '[:lower:]')"
case "${arch}" in
amd64) arch="x86_64" ;;
aarch64) arch="arm64" ;;
esac
printf '%s' "${arch}"
}
# ターゲットの環境(OSとCPU)を判断する関数
detect_target() {
arch="$1"
platform="$2"
target="${platform}_${arch}"
printf '%s' "${target}"
}
# インストールの実行を確認する関数
confirm() {
if [ -z "${FORCE-}" ]; then
printf "%s " "${MAGENTA}?${NO_COLOR} $* ${BOLD}[y/N]${NO_COLOR}"
set +e
read -r yn </dev/tty
rc=$?
set -e
if [ $rc -ne 0 ]; then
error "Error reading from prompt (please re-run with the '--yes' option)"
exit 1
fi
if [ "$yn" != "y" ] && [ "$yn" != "yes" ]; then
error 'Aborting (please answer "yes" to continue)'
exit 1
fi
fi
}
# 実行ファイルのディレクトリを確認する関数
check_bin_dir() {
bin_dir="${1%/}"
if [ ! -d "$BIN_DIR" ]; then
error "Installation location $BIN_DIR does not appear to be a directory"
info "Make sure the location exists and is a directory, then try again."
usage
exit 1
fi
good=$(
IFS=:
for path in $PATH; do
if [ "${path%/}" = "${bin_dir}" ]; then
printf 1
break
fi
done
)
if [ "${good}" != "1" ]; then
warn "Bin directory ${bin_dir} is not in your \$PATH"
fi
}
# ターゲット環境用の実行ファイルの有無を確認する関数
is_build_available() {
arch="$1"
platform="$2"
target="$3"
good=$(
IFS=" "
for t in $SUPPORTED_TARGETS; do
if [ "${t}" = "${target}" ]; then
printf 1
break
fi
done
)
if [ "${good}" != "1" ]; then
error "${arch} builds for ${platform} are not yet available for twsla"
printf "\n" >&2
info "If you would like to see a build for your configuration,"
info "please create an issue requesting a build for ${MAGENTA}${target}${NO_COLOR}:"
info "${BOLD}${UNDERLINE}https://github.com/twsnmp/twsla/issues/new/${NO_COLOR}"
printf "\n"
exit 1
fi
}
# メインの処理
# デフォルト値の設定
# defaults
if [ -z "${PLATFORM-}" ]; then
PLATFORM="$(detect_platform)"
fi
if [ -z "${BIN_DIR-}" ]; then
BIN_DIR=/usr/local/bin
fi
if [ -z "${ARCH-}" ]; then
ARCH="$(detect_arch)"
fi
if [ -z "${BASE_URL-}" ]; then
BASE_URL="https://github.com/twsnmp/twsla/releases"
fi
if [ -z "${VERSION-}" ]; then
VERSION="latest"
fi
# 引数の確認
while [ "$#" -gt 0 ]; do
case "$1" in
-b | --bin-dir)
BIN_DIR="$2"
shift 2
;;
-v | --version)
VERSION="$2"
shift 2
;;
-V | --verbose)
VERBOSE=1
shift 1
;;
-h | --help)
usage
exit
;;
-b=* | --bin-dir=*)
BIN_DIR="${1#*=}"
shift 1
;;
-v=* | --version=*)
VERSION="${1#*=}"
shift 1
;;
-V=* | --verbose=*)
VERBOSE="${1#*=}"
shift 1
;;
*)
error "Unknown option: $1"
usage
exit 1
;;
esac
done
# ターゲットの取得
TARGET="$(detect_target "${ARCH}" "${PLATFORM}")"
# ターゲット用の実行ファイルの有無を確認
is_build_available "${ARCH}" "${PLATFORM}" "${TARGET}"
# 引数を確認するための表示
printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}"
info "${BOLD}Bin directory${NO_COLOR}: ${GREEN}${BIN_DIR}${NO_COLOR}"
info "${BOLD}Platform${NO_COLOR}: ${GREEN}${PLATFORM}${NO_COLOR}"
info "${BOLD}Arch${NO_COLOR}: ${GREEN}${ARCH}${NO_COLOR}"
# 実行状況の表示を判断
if [ -n "${VERBOSE-}" ]; then
VERBOSE=v
info "${BOLD}Verbose${NO_COLOR}: yes"
else
VERBOSE=
fi
printf '\n'
# ダウンロードするURLを設定する
if [ "${VERSION}" != "latest" ]; then
URL="${BASE_URL}/download/${VERSION}/twsla_${TARGET}.${EXT}"
else
URL="${BASE_URL}/latest/download/twsla_${TARGET}.${EXT}"
fi
# URLを表示する
info "Tarball URL: ${UNDERLINE}${BLUE}${URL}${NO_COLOR}"
# インストールの実行を確認する
confirm "Install twsla ${GREEN}${VERSION}${NO_COLOR} to ${BOLD}${GREEN}${BIN_DIR}${NO_COLOR}?"
# 実行ファイルのディレクトリを確認する
check_bin_dir "${BIN_DIR}"
# インストールを実行する
install "${EXT}"
completed "twsla ${VERSION} installed"
です。
Mac OSで実行すると

のような感じでインストールできました。
次回のリリースに含めようと思います。
明日に続く
いいなと思ったら応援しよう!
