feat: add friendly installer progress logs
This commit is contained in:
+49
-6
@@ -69,6 +69,8 @@ EOF
|
||||
}
|
||||
die() { printf 'tallynote installer: %s\n' "$*" >&2; exit 1; }
|
||||
log() { printf 'tallynote installer: %s\n' "$*"; }
|
||||
stage() { log "[阶段] $*"; }
|
||||
stage_done() { log "[完成] $*"; }
|
||||
|
||||
[[ "$REQUIRE_SIGNATURE" == true || "$REQUIRE_SIGNATURE" == false ]] || die 'TALLYNOTE_INSTALL_REQUIRE_SIGNATURE 必须是 true 或 false'
|
||||
[[ "$ALLOW_DOWNGRADE" == true || "$ALLOW_DOWNGRADE" == false ]] || die 'TALLYNOTE_ALLOW_DOWNGRADE 必须是 true 或 false'
|
||||
@@ -197,15 +199,23 @@ assert_allowed_url() {
|
||||
download() {
|
||||
local url=$1 out=$2 max_bytes=${3:-$((MAX_RELEASE_MB * 1024 * 1024))}
|
||||
local current="$url" headers status location actual origin scheme authority
|
||||
local -a curl_args=(--proto '=https' --tlsv1.2 --fail --show-error --max-redirs 0
|
||||
--connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" --max-filesize "$max_bytes"
|
||||
--retry 2 --retry-connrefused)
|
||||
# Keep CI and journal output clean, while showing curl's standard progress
|
||||
# bar during an interactive SSH/terminal installation.
|
||||
if [[ -t 2 ]]; then
|
||||
curl_args+=(--progress-bar)
|
||||
else
|
||||
curl_args+=(--silent)
|
||||
fi
|
||||
require_https "$url"
|
||||
assert_allowed_url "$url"
|
||||
[[ ! -L "$out" && ! -e "$out" ]] || die "download destination already exists: $out"
|
||||
for _redirect in 0 1 2 3; do
|
||||
headers="${out}.headers-${RANDOM}-$$"
|
||||
status=$(curl --proto '=https' --tlsv1.2 --fail --silent --show-error --max-redirs 0 \
|
||||
--connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" --max-filesize "$max_bytes" \
|
||||
--retry 2 --retry-connrefused --output "$out" --dump-header "$headers" \
|
||||
--write-out '%{http_code}' "$current" 2>/dev/null) || status=000
|
||||
status=$(curl "${curl_args[@]}" --output "$out" --dump-header "$headers" \
|
||||
--write-out '%{http_code}' "$current") || status=000
|
||||
if [[ "$status" =~ ^2[0-9][0-9]$ ]]; then
|
||||
rm -f -- "$headers"
|
||||
break
|
||||
@@ -715,6 +725,7 @@ prune_releases() {
|
||||
}
|
||||
|
||||
main() {
|
||||
stage '检查运行环境、权限和目标架构'
|
||||
# These variables are useful for isolated tests, but a root install must
|
||||
# never execute an untrusted PATH entry supplied through sudo's environment.
|
||||
if (( APPLY )) || [[ -n "${TALLYNOTE_UNAME_BIN+x}" ]]; then
|
||||
@@ -737,14 +748,21 @@ main() {
|
||||
# TALLYNOTE_RELEASE_ALLOWED_HOSTS when the operator has reviewed it.
|
||||
append_allowed_host "$(url_host "$RELEASE_API_URL")"
|
||||
append_allowed_host "$(url_host "$REPOSITORY_URL")"
|
||||
stage_done "运行环境可用:${TALLYNOTE_ARCH}/${TALLYNOTE_LIBC}"
|
||||
if [[ "$VERSION" == "latest" ]]; then
|
||||
if (( ! APPLY )); then
|
||||
[[ -z "$RELEASE_BASE_URL" ]] || require_https "$RELEASE_BASE_URL"
|
||||
stage '预览最新版本解析(dry-run 不访问 Release)'
|
||||
log 'version: latest (release lookup skipped in dry-run)'
|
||||
log 'dry-run: pass --version VERSION to preview an exact artifact'
|
||||
stage_done 'dry-run 预览完成:不会下载、解包或修改 systemd'
|
||||
return 0
|
||||
fi
|
||||
stage '从 Release API 获取最新版本'
|
||||
resolve_latest_version
|
||||
stage_done "已解析最新版本:${VERSION#v}"
|
||||
else
|
||||
stage "使用指定版本:${VERSION#v}"
|
||||
fi
|
||||
validate_semver "$VERSION" || die 'version must be a semantic version (for example 1.2.3)'
|
||||
VERSION=${VERSION#v}
|
||||
@@ -755,15 +773,21 @@ main() {
|
||||
die "拒绝安装不高于当前版本的 release:当前 $current_version,候选 $VERSION(如确需降级请使用 --allow-downgrade)"
|
||||
fi
|
||||
fi
|
||||
stage '准备 Release 下载地址和发布包'
|
||||
release_urls
|
||||
local artifact archive checksum signature artifact_url work release_dir
|
||||
artifact=${RELEASE_FILE:+$(basename -- "$RELEASE_FILE")}
|
||||
artifact=${artifact:-tallynote-${VERSION}-linux-${TALLYNOTE_ARCH}-${TALLYNOTE_LIBC}.tar.gz}
|
||||
[[ "$artifact" =~ ^[A-Za-z0-9][A-Za-z0-9._+\-]*\.(tar\.gz|tgz|tar)$ ]] || die 'release 文件名无效'
|
||||
artifact_url="$RELEASE_BASE_URL/$artifact"
|
||||
stage_done 'Release 下载地址已准备'
|
||||
log "platform: ${TALLYNOTE_ARCH}/${TALLYNOTE_LIBC}; release: ${VERSION#v}"
|
||||
log "layout: $PREFIX/releases + atomic $PREFIX/current; data: $DATA_DIR"
|
||||
if (( ! APPLY )); then log 'dry-run: no download, extraction, or systemd changes'; return 0; fi
|
||||
if (( ! APPLY )); then
|
||||
log 'dry-run: no download, extraction, or systemd changes'
|
||||
stage_done 'dry-run 预览完成:不会下载、解包或修改 systemd'
|
||||
return 0
|
||||
fi
|
||||
[[ "$("$UNAME_BIN" -s)" == Linux ]] || die '安装器只允许在 Linux 上执行'
|
||||
[[ $EUID -eq 0 ]] || die '安装必须以 root 运行'
|
||||
for command_name in curl sha256sum tar install sed awk find systemctl; do
|
||||
@@ -777,6 +801,7 @@ main() {
|
||||
INSTALL_BACKUP_DIR="$work/original"
|
||||
trap rollback_install_if_needed EXIT
|
||||
archive="$work/$artifact"
|
||||
stage "获取发布包:$artifact"
|
||||
if [[ -n "$RELEASE_FILE" && -f "$RELEASE_FILE" && ! -L "$RELEASE_FILE" ]]; then
|
||||
cp -- "$RELEASE_FILE" "$archive"
|
||||
chmod 600 "$archive"
|
||||
@@ -785,8 +810,10 @@ main() {
|
||||
[[ -z "$RELEASE_FILE" ]] || die '本地 release 文件不存在或是符号链接'
|
||||
download "$artifact_url" "$archive"
|
||||
fi
|
||||
stage_done '发布包已下载并通过大小限制'
|
||||
checksum="$work/SHA256SUMS"
|
||||
SHA256_URL=${SHA256_URL:-$RELEASE_BASE_URL/SHA256SUMS}
|
||||
stage '获取 SHA-256 校验清单'
|
||||
if [[ -n "$SHA256_FILE" && -f "$SHA256_FILE" && ! -L "$SHA256_FILE" ]]; then
|
||||
cp -- "$SHA256_FILE" "$checksum"
|
||||
chmod 600 "$checksum"
|
||||
@@ -795,9 +822,11 @@ main() {
|
||||
[[ -z "$SHA256_FILE" ]] || die '本地 SHA256SUMS 文件不存在或是符号链接'
|
||||
download "$SHA256_URL" "$checksum" $((2 * 1024 * 1024))
|
||||
fi
|
||||
stage_done 'SHA-256 校验清单已准备'
|
||||
SIGNING_KEY=${SIGNING_KEY:-$UPDATE_PUBLIC_KEY_FILE}
|
||||
signature=''
|
||||
if [[ "$REQUIRE_SIGNATURE" == true || -n "$SIGNATURE_URL" || -n "$SIGNING_KEY" ]]; then
|
||||
stage '获取发布签名'
|
||||
if [[ "$SIGNATURE_FORMAT" == gpg ]]; then
|
||||
SIGNATURE_URL=${SIGNATURE_URL:-$RELEASE_BASE_URL/$artifact.asc}
|
||||
signature="$work/$artifact.asc"
|
||||
@@ -806,10 +835,14 @@ main() {
|
||||
signature="$work/SHA256SUMS.sig"
|
||||
fi
|
||||
download "$SIGNATURE_URL" "$signature" $((64 * 1024))
|
||||
stage_done '发布签名已准备'
|
||||
fi
|
||||
stage '校验 SHA-256 和发布签名'
|
||||
verify_archive "$archive" "$checksum" "$signature" "$SIGNING_KEY"
|
||||
stage_done '发布包校验通过'
|
||||
[[ "$PREFIX" = /* && "$DATA_DIR" = /* && "$CONFIG_DIR" = /* ]] || die '安装、数据和配置目录必须是绝对路径'
|
||||
[[ ! -L "$DATA_DIR" && ! -L "$PREFIX" && ! -L "$CONFIG_DIR" ]] || die 'installation/data/config paths must not be symlinks'
|
||||
stage '停止旧服务并准备安装、配置和数据目录'
|
||||
id tallynote >/dev/null 2>&1 || useradd --system --user-group --home-dir "$DATA_DIR" --shell /usr/sbin/nologin tallynote
|
||||
backup_install_files "$INSTALL_BACKUP_DIR"
|
||||
stop_existing_services
|
||||
@@ -821,10 +854,14 @@ main() {
|
||||
if [[ -e "$CONFIG_DIR/tallynote.env" ]]; then
|
||||
validate_existing_env "$CONFIG_DIR/tallynote.env"
|
||||
fi
|
||||
stage_done '目录、权限和旧服务状态已准备'
|
||||
stage "解包、校验包结构并原子切换到版本 ${VERSION#v}"
|
||||
install_release "$archive" "$VERSION"
|
||||
stage_done "版本 ${VERSION#v} 已切换为当前版本"
|
||||
release_dir="$PREFIX/releases/$VERSION"
|
||||
[[ -f "$release_dir/systemd/tallynote.service" && -f "$release_dir/systemd/tallynote-update.service" && -f "$release_dir/systemd/tallynote-update.path" ]] || die 'release package is missing systemd unit files'
|
||||
[[ -f "$release_dir/systemd/tallynote.env.example" && -f "$release_dir/scripts/tallynote-update-runner.sh" && -x "$release_dir/uninstall.sh" ]] || die 'release package is missing update/uninstall support files'
|
||||
stage '安装 systemd 单元、更新辅助程序和卸载器'
|
||||
install -d -m 755 /usr/local/libexec /etc/systemd/system
|
||||
local unit_tmp
|
||||
unit_tmp=$(mktemp -d)
|
||||
@@ -879,15 +916,21 @@ main() {
|
||||
printf 'TALLYNOTE_UPDATE_PUBLIC_KEY_FILE=%s\n' "$CONFIG_DIR/update-signing-key.pub" >> "$CONFIG_DIR/tallynote.env"
|
||||
fi
|
||||
fi
|
||||
stage_done 'systemd 单元、更新辅助程序和卸载器已安装'
|
||||
stage '重新加载 systemd 并启动 TallyNote'
|
||||
chown root:root "$CONFIG_DIR/tallynote.env"
|
||||
chmod 640 "$CONFIG_DIR/tallynote.env"
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now tallynote.service tallynote-update.path
|
||||
stage_done 'TallyNote 服务已启用并启动'
|
||||
stage '清理旧版本并完成安装'
|
||||
prune_releases
|
||||
stage_done '旧版本清理完成'
|
||||
INSTALL_COMMITTED=1
|
||||
trap - EXIT
|
||||
rm -rf -- "$INSTALL_WORK_DIR" 2>/dev/null || true
|
||||
INSTALL_WORK_DIR=''
|
||||
log 'installed; inspect with systemctl status tallynote.service'
|
||||
stage_done "安装完成:TallyNote ${VERSION#v}"
|
||||
log '查看服务状态:systemctl status tallynote.service'
|
||||
}
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user