Files
ChuBlog/scripts/install.sh
T
LIm1tZz7 df6d348995 大更新:
1、完善SQL语法
2、移除node前端,只保留静态资源
3、写好安装脚本
4、做好linux适配
2026-10-01 20:48:38 +08:00

407 lines
14 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# ChuSQL 安装脚本(Linux / macOS / POSIX sh)。
#
# 三种来源,脚本自己判断走哪条:
#
# 1) 包内安装:解压一个发行包,脚本旁边就是 bin/,直接装本地文件
# ./install.sh --component web
#
# 2) 在线安装:脚本从 GitHub 取发行包(也可用 --url 指任意 URL 或本地归档)
# curl -fsSL https://raw.githubusercontent.com/TheBadRoger/ChuSQL/main/scripts/install.sh \
# | sh -s -- --component web
# sh install.sh --component web --url dist/chusql-web-linux-x86_64.tar.gz
#
# 3) 源码安装:拉仓库、本地编译(要有 git + cargo + stack + ghc)
# ./install.sh --component web --from-source
# ./install.sh --component web --from-source --source-dir ../ChuSQL
#
# 安装 = 释放到目标目录 + 配好环境变量 + 写全局配置,装完把包目录删掉。
# storage/engine 一定装;web 与 cli 二选一。
#
# 包目录结构(解压后就是这个样子):
# bin/ chusql-storage(必装)、chusql-web 或 csql(二选一)
# static/ Web 前端静态资源(装 web 组件时带)
# scripts/ chusql.toml、init.sql
# csql-web.sh
# install.sh
set -eu
default_repo='TheBadRoger/ChuSQL'
usage() {
cat <<'EOF'
usage: ./install.sh --component web|cli [options]
--component web|cli which front end to install (required)
--install-dir DIR where to install (default: $HOME/.local/share/chusql)
--data-dir DIR where to keep data (default: <install-dir>/data)
--user NAME administrator name (default: root)
--password PW administrator password, empty means no password
(administrator-only sign-in)
--keep-package do not delete the downloaded/extracted package
where the files come from:
(default) a package next to this script, else the newest GitHub release
--url URL|PATH install from this archive (URL, file:// URL or local path)
--repo OWNER/NAME GitHub repository (default: TheBadRoger/ChuSQL)
--version TAG release tag to download / git ref to build (default: latest)
--from-source clone and build locally (needs git, cargo, stack, ghc)
--source-dir DIR build from this checkout instead of cloning
-h, --help this text
EOF
}
component=''
install_dir=''
data_dir=''
root_user='root'
root_password=''
keep_package='no'
repo="$default_repo"
version='latest'
url_arg=''
from_source='no'
source_dir=''
while [ $# -gt 0 ]; do
case "$1" in
--component) component="${2:-}"; shift 2 ;;
--install-dir) install_dir="${2:-}"; shift 2 ;;
--data-dir) data_dir="${2:-}"; shift 2 ;;
--user) root_user="${2:-}"; shift 2 ;;
--password) root_password="${2:-}"; shift 2 ;;
--keep-package) keep_package='yes'; shift ;;
--url) url_arg="${2:-}"; shift 2 ;;
--repo) repo="${2:-}"; shift 2 ;;
--version) version="${2:-}"; shift 2 ;;
--from-source) from_source='yes'; shift ;;
--source-dir) source_dir="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "!! unknown option: $1" >&2; usage >&2; exit 1 ;;
esac
done
fail() { echo "!! $1" >&2; exit 1; }
step() { echo; echo "==> $1"; }
note() { echo " $1"; }
case "$component" in
web|cli) ;;
'') fail '--component is required (web or cli)' ;;
*) fail "--component must be web or cli, got: $component" ;;
esac
# ---- 本脚本在哪(管道进来时 $0 是 sh,这时没有本地包)----
self=$0
script_dir=''
case "$self" in
sh|bash|dash|ash|ksh|'-sh'|*/sh|*/bash|*/dash|*/ash|*/ksh) script_dir='' ;;
*) script_dir=$(CDPATH= cd -- "$(dirname -- "$self")" 2>/dev/null && pwd) || script_dir='' ;;
esac
[ -n "$install_dir" ] || install_dir="$HOME/.local/share/chusql"
[ -n "$data_dir" ] || data_dir="$install_dir/data"
tmp=''
cleanup() { if [ -n "$tmp" ]; then rm -rf "$tmp"; fi; }
new_tmp() {
tmp=$(mktemp -d "${TMPDIR:-/tmp}/chusql-install.XXXXXX") || fail 'cannot create a temporary directory'
# --keep-package 时不注册清理,临时目录就留着
if [ "$keep_package" = 'no' ]; then trap cleanup EXIT INT TERM HUP; fi
}
download() {
# download URL FILE
if command -v curl >/dev/null 2>&1; then
curl -fsSL -o "$2" "$1" || return 1
elif command -v wget >/dev/null 2>&1; then
wget -q -O "$2" "$1" || return 1
else
fail 'need curl or wget to download the package'
fi
}
verify_checksum() {
# verify_checksum ASSET_URL FILE —— 发布带 .sha256 就校验,没带就说明一下
sums="$tmp/asset.sha256"
if ! download "$1.sha256" "$sums" 2>/dev/null; then
note 'checksum no .sha256 published for this asset (skipped)'
return 0
fi
expected=$(awk 'NR==1 { print $1 }' "$sums")
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$2" | awk '{ print $1 }')
elif command -v shasum >/dev/null 2>&1; then
actual=$(shasum -a 256 "$2" | awk '{ print $1 }')
else
fail 'no sha256sum/shasum available to verify the checksum'
fi
[ "$actual" = "$expected" ] || fail "checksum mismatch: expected $expected, got $actual"
note "checksum $expected (ok)"
}
unpack() {
# unpack ARCHIVE DEST
mkdir -p "$2"
case "$1" in
*.zip)
command -v unzip >/dev/null 2>&1 || fail 'unzip is required to open a .zip package'
unzip -q "$1" -d "$2"
;;
*)
tar -xzf "$1" -C "$2"
;;
esac
}
# 平台标签(全局:os、arch、ext、labels)
detect_platform() {
os=$(uname -s 2>/dev/null || echo unknown)
case "$os" in
Linux) os='linux' ;;
Darwin) os='macos' ;;
MINGW*|MSYS*|CYGWIN*) os='windows' ;;
esac
arch=$(uname -m 2>/dev/null || echo unknown)
case "$arch" in
x86_64|amd64) arch='x86_64' ;;
aarch64|arm64) arch='arm64' ;;
esac
if [ "$os" = 'windows' ]; then ext='zip'; else ext='tar.gz'; fi
labels="$os-$arch $os"
}
# 发行资源(全局:url、archive),成功返回 0
fetch_asset() {
asset="$1"
if [ "$version" = 'latest' ]; then
url="https://github.com/$repo/releases/latest/download/$asset"
else
url="https://github.com/$repo/releases/download/$version/$asset"
fi
archive="$tmp/$asset"
if download "$url" "$archive" 2>/dev/null; then
note "asset $asset"
return 0
fi
return 1
}
pack=''
url=''
archive=''
if [ "$from_source" = 'yes' ]; then
# ---------- 源码安装 ----------
if [ -z "$source_dir" ]; then
command -v git >/dev/null 2>&1 || fail 'git is required to clone the repository'
fi
command -v cargo >/dev/null 2>&1 || fail 'cargo (Rust) is required for --from-source'
command -v stack >/dev/null 2>&1 || fail 'stack (Haskell) is required for --from-source'
new_tmp
if [ -n "$source_dir" ]; then
src=$(CDPATH= cd -- "$source_dir" 2>/dev/null && pwd) || fail "no such directory: $source_dir"
note "source $src (using this checkout as is)"
else
src="$tmp/src"
if [ "$version" = 'latest' ]; then
step "Cloning https://github.com/$repo.git"
git clone --depth 1 "https://github.com/$repo.git" "$src" >/dev/null 2>&1 ||
fail "cannot clone https://github.com/$repo.git"
else
step "Cloning https://github.com/$repo.git at $version"
git clone --depth 1 --branch "$version" "https://github.com/$repo.git" "$src" >/dev/null 2>&1 ||
fail "cannot clone $repo at $version"
fi
note "source $src"
fi
[ -f "$src/chusql-storage/Cargo.toml" ] || fail "not a ChuSQL checkout (chusql-storage/Cargo.toml missing): $src"
[ -f "$src/scripts/chusql.toml" ] || fail "not a ChuSQL checkout (scripts/chusql.toml missing): $src"
if command -v pkg-config >/dev/null 2>&1 && ! pkg-config --exists zlib 2>/dev/null; then
note 'hint the Haskell zlib binding needs the C library: apt-get install zlib1g-dev (or dnf install zlib-devel)'
fi
step 'Building the Rust storage (cargo build --release)'
( cd "$src/chusql-storage" && cargo build --release ) || fail 'cargo build --release failed'
storage_bin="$src/chusql-storage/target/release/chusql-storage"
[ -f "$storage_bin" ] || fail "storage binary not found after the build: $storage_bin"
if [ "$component" = 'web' ]; then
comp_dir='chusql-web'; front_name='chusql-web'
else
comp_dir='chusql-cli'; front_name='csql'
fi
step "Building the Haskell binaries (stack build --fast in $comp_dir)"
note 'the first build downloads the Hackage index (about 139 MB)'
( cd "$src/$comp_dir" && stack build --fast ) || fail "stack build failed in $comp_dir"
stack_root=$( cd "$src/$comp_dir" && stack path --local-install-root 2>/dev/null | tr -d '\r' )
[ -n "$stack_root" ] || fail 'cannot ask stack for --local-install-root'
front_bin="$stack_root/bin/$front_name"
[ -f "$front_bin" ] || fail "front end binary not found after the build: $front_bin"
step 'Staging the package'
pack="$tmp/stage"
mkdir -p "$pack/bin" "$pack/scripts"
cp "$storage_bin" "$pack/bin/"
cp "$front_bin" "$pack/bin/"
for extra in "$stack_root/bin/"*.so "$stack_root/bin/"*.dll; do
if [ -f "$extra" ]; then cp "$extra" "$pack/bin/"; fi
done
cp "$src/scripts/chusql.toml" "$src/scripts/init.sql" "$pack/scripts/"
cp "$src/scripts/install.sh" "$pack/"
if [ "$component" = 'web' ]; then
[ -d "$src/chusql-web/static" ] || fail "static assets not found: $src/chusql-web/static"
mkdir -p "$pack/static"
cp -R "$src/chusql-web/static/." "$pack/static/"
cp "$src/scripts/csql-web.sh" "$pack/"
fi
elif [ -n "$url_arg" ]; then
# ---------- 指定 URL / 本地归档 ----------
new_tmp
archive="$tmp/package"
case "$url_arg" in
http://*|https://*|file://*) url="$url_arg" ;;
*)
[ -f "$url_arg" ] || fail "no such package: $url_arg"
url="file://$(CDPATH= cd -- "$(dirname -- "$url_arg")" && pwd)/$(basename -- "$url_arg")"
;;
esac
step "Downloading $url"
download "$url" "$archive" || fail "cannot download $url"
case "$url_arg" in
http://*|https://*|file://*) verify_checksum "$url" "$archive" ;;
esac
step 'Unpacking'
unpack "$archive" "$tmp/pkg"
pack="$tmp/pkg"
elif [ -n "$script_dir" ] && [ -f "$script_dir/bin/chusql-storage" ]; then
# ---------- 包内安装 ----------
pack="$script_dir"
else
# ---------- 从 GitHub 发行版下载 ----------
new_tmp
detect_platform
step "Looking for a ChuSQL package for $os-$arch (component $component) in $repo"
found=''
for label in $labels; do
if fetch_asset "chusql-$component-$label.$ext"; then
found="$asset"
break
fi
done
if [ -z "$found" ]; then
if fetch_asset "chusql-$component.$ext"; then found="$asset"; fi
fi
[ -n "$found" ] || fail "no package for $os-$arch in $repo ($version); try --url or --from-source"
verify_checksum "$url" "$archive"
step 'Unpacking'
unpack "$archive" "$tmp/pkg"
pack="$tmp/pkg"
fi
# ---- 校验包内容 ----
[ -f "$pack/bin/chusql-storage" ] || fail "package is incomplete: bin/chusql-storage not found in $pack"
if [ "$component" = 'web' ]; then
[ -f "$pack/bin/chusql-web" ] || fail 'package is incomplete: bin/chusql-web not found'
[ -d "$pack/static" ] || fail 'package is incomplete: static/ not found'
else
[ -f "$pack/bin/csql" ] || fail 'package is incomplete: bin/csql not found'
fi
[ -f "$pack/scripts/chusql.toml" ] || fail 'package is incomplete: scripts/chusql.toml not found'
echo 'ChuSQL installer'
echo " component $component"
echo " install to $install_dir"
echo " data dir $data_dir"
if [ -n "$root_password" ]; then
echo " root user $root_user"
else
echo " root user $root_user (no password: administrator-only sign-in)"
fi
# ---- 释放文件 ----
step 'Installing files'
mkdir -p "$install_dir/bin" "$install_dir/logs" "$data_dir"
cp -R "$pack/bin/." "$install_dir/bin/"
chmod +x "$install_dir/bin/"* 2>/dev/null || true
if [ "$component" = 'web' ]; then
mkdir -p "$install_dir/static"
cp -R "$pack/static/." "$install_dir/static/"
if [ -f "$pack/csql-web.sh" ]; then
cp "$pack/csql-web.sh" "$install_dir/"
chmod +x "$install_dir/csql-web.sh"
fi
fi
# ---- 写全局配置 ----
step 'Writing chusql.toml'
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ChuSQL"
config_file="$config_dir/chusql.toml"
mkdir -p "$config_dir"
sed -e "s|^\(user[[:space:]]*=[[:space:]]*\).*|\1\"$root_user\"|" \
-e "s|^\(password[[:space:]]*=[[:space:]]*\).*|\1\"$root_password\"|" \
-e "s|^\(data_dir[[:space:]]*=[[:space:]]*\).*|\1\"$data_dir\"|" \
"$pack/scripts/chusql.toml" > "$config_file"
echo " config file $config_file"
if [ -f "$pack/scripts/init.sql" ]; then cp "$pack/scripts/init.sql" "$install_dir/"; fi
# ---- 命令入口 ----
step 'Creating commands'
if [ "$component" = 'web' ]; then
cat > "$install_dir/csql-web" <<'EOF'
#!/bin/sh
exec "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/csql-web.sh" "$@"
EOF
chmod +x "$install_dir/csql-web"
else
cat > "$install_dir/csql" <<'EOF'
#!/bin/sh
exec "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/bin/csql" "$@"
EOF
chmod +x "$install_dir/csql"
fi
# ---- 环境变量 ----
step 'Updating PATH'
profile="$HOME/.profile"
marker='# ChuSQL'
if [ -f "$profile" ] && grep -q "^$marker" "$profile"; then
echo "PATH entry already present in $profile"
else
{
echo ''
echo "$marker"
echo "PATH=\"\$PATH:$install_dir\""
echo "export PATH"
} >> "$profile"
echo "added $install_dir to PATH in $profile (login again or: . \"$profile\")"
fi
# ---- 删掉安装包 ----
if [ "$keep_package" = 'no' ]; then
step 'Removing the package'
case "$install_dir" in
"$pack"*|"$pack") echo "kept the package (install dir is inside it): $pack" ;;
*)
if [ -f "$pack/bin/chusql-storage" ]; then
( cd / && rm -rf "$pack" )
echo 'package removed'
else
echo "kept the package (not safe to delete automatically): $pack"
fi
;;
esac
else
step 'Keeping the package'
echo " package kept at $pack"
fi
step 'Done'
echo " chusql.toml $config_file"
echo " data $data_dir"
if [ "$component" = 'web' ]; then echo ' start with csql-web'; else echo ' start with csql'; fi