mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 02:06:16 +00:00
feat: add epctl installer, docker validation script, and bilingual docs
This commit is contained in:
Executable
+379
@@ -0,0 +1,379 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly IMAGE_TAG="epctl-ubuntu-systemd-test:$(date +%s)-$$"
|
||||
readonly CONTAINER_NAME="epctl-systemd-test-$$"
|
||||
readonly BASE_IMAGE="ubuntu:24.04"
|
||||
|
||||
ACTIVE_LANG=""
|
||||
POSITIONAL_ARGS=()
|
||||
|
||||
detect_default_language() {
|
||||
local raw normalized
|
||||
|
||||
raw="${EPCTL_LANG:-}"
|
||||
if [[ -z "${raw}" ]]; then
|
||||
printf 'zh\n'
|
||||
return
|
||||
fi
|
||||
|
||||
normalized="${raw,,}"
|
||||
case "${normalized}" in
|
||||
zh|zh-*|zh_*|cn|chs|cht|中文)
|
||||
printf 'zh\n'
|
||||
;;
|
||||
en|en-*|en_*|english)
|
||||
printf 'en\n'
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "${raw}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
set_language() {
|
||||
local requested normalized fallback_lang
|
||||
|
||||
requested="${1:-$(detect_default_language)}"
|
||||
normalized="${requested,,}"
|
||||
fallback_lang="${ACTIVE_LANG:-$(detect_default_language)}"
|
||||
case "${normalized}" in
|
||||
zh|zh-*|zh_*|cn|chs|cht|中文)
|
||||
ACTIVE_LANG="zh"
|
||||
;;
|
||||
en|en-*|en_*|english)
|
||||
ACTIVE_LANG="en"
|
||||
;;
|
||||
*)
|
||||
ACTIVE_LANG="${fallback_lang}"
|
||||
die "$(trf unsupported_language "${requested}")"
|
||||
;;
|
||||
esac
|
||||
|
||||
export EPCTL_LANG="${ACTIVE_LANG}"
|
||||
}
|
||||
|
||||
trf() {
|
||||
local key="$1"
|
||||
shift || true
|
||||
local template
|
||||
|
||||
case "${ACTIVE_LANG}:${key}" in
|
||||
en:usage) template='Usage:' ;;
|
||||
zh:usage) template='用法:' ;;
|
||||
en:example) template='Example:' ;;
|
||||
zh:example) template='示例:' ;;
|
||||
en:missing_dependency) template='missing dependency: %s' ;;
|
||||
zh:missing_dependency) template='缺少依赖:%s' ;;
|
||||
en:arg_requires_value) template='%s requires a value' ;;
|
||||
zh:arg_requires_value) template='%s 需要一个参数值' ;;
|
||||
en:unsupported_language) template='unsupported language: %s' ;;
|
||||
zh:unsupported_language) template='不支持的语言:%s' ;;
|
||||
en:unsupported_arch) template='unsupported architecture: %s' ;;
|
||||
zh:unsupported_arch) template='不支持的架构:%s' ;;
|
||||
en:test_failed_collecting) template='test failed; collecting container diagnostics' ;;
|
||||
zh:test_failed_collecting) template='测试失败,正在收集容器诊断信息' ;;
|
||||
en:install_tag_invalid) template='install tag must be a real GitHub release tag such as v1.0.6' ;;
|
||||
zh:install_tag_invalid) template='安装 tag 必须是真实的 GitHub release tag,例如 v1.0.6' ;;
|
||||
en:upgrade_tag_invalid) template='upgrade tag must be a real GitHub release tag such as v1.0.6' ;;
|
||||
zh:upgrade_tag_invalid) template='升级 tag 必须是真实的 GitHub release tag,例如 v1.0.6' ;;
|
||||
en:base_image) template='base image: %s' ;;
|
||||
zh:base_image) template='基础镜像:%s' ;;
|
||||
en:building_image) template='building the Ubuntu systemd test image' ;;
|
||||
zh:building_image) template='正在构建 Ubuntu systemd 测试镜像' ;;
|
||||
en:starting_container) template='starting the test container' ;;
|
||||
zh:starting_container) template='正在启动测试容器' ;;
|
||||
en:systemd_not_ready) template='systemd did not become ready inside the container' ;;
|
||||
zh:systemd_not_ready) template='容器内的 systemd 未能及时就绪' ;;
|
||||
en:self_installing) template='installing epctl into PATH' ;;
|
||||
zh:self_installing) template='正在将 epctl 安装到 PATH' ;;
|
||||
en:downloading_release) template='downloading release %s' ;;
|
||||
zh:downloading_release) template='正在下载 release %s' ;;
|
||||
en:installing_release) template='installing epusdt from release %s' ;;
|
||||
zh:installing_release) template='正在从 release %s 安装 epusdt' ;;
|
||||
en:waiting_assets) template='waiting for epusdt to start and release www assets' ;;
|
||||
zh:waiting_assets) template='正在等待 epusdt 启动并释放 www 静态文件' ;;
|
||||
en:upgrading_release) template='upgrading epusdt from %s to %s' ;;
|
||||
zh:upgrading_release) template='正在将 epusdt 从 %s 升级到 %s' ;;
|
||||
en:checking_init_password) template='checking init-password behavior' ;;
|
||||
zh:checking_init_password) template='正在检查 init-password 行为' ;;
|
||||
en:expected_second_failure) template='expected init-password to fail after the password was changed' ;;
|
||||
zh:expected_second_failure) template='密码修改后,init-password 本应失败,但它却成功了' ;;
|
||||
en:verification_succeeded) template='docker verification succeeded for install=%s%s' ;;
|
||||
zh:verification_succeeded) template='Docker 验证通过,install=%s%s' ;;
|
||||
*)
|
||||
template="missing translation: ${key}"
|
||||
;;
|
||||
esac
|
||||
|
||||
printf -- "${template}" "$@"
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[epctl-docker-test] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
printf '[epctl-docker-test] error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
if [[ "${ACTIVE_LANG}" == "zh" ]]; then
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
./epctl-docker-test.sh [--lang zh|en] <install-tag> [upgrade-tag]
|
||||
|
||||
示例:
|
||||
./epctl-docker-test.sh v1.0.6
|
||||
./epctl-docker-test.sh --lang zh v1.0.6 v1.0.8
|
||||
EOF
|
||||
else
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./epctl-docker-test.sh [--lang zh|en] <install-tag> [upgrade-tag]
|
||||
|
||||
Example:
|
||||
./epctl-docker-test.sh v1.0.6
|
||||
./epctl-docker-test.sh --lang en v1.0.6 v1.0.8
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
parse_global_options() {
|
||||
POSITIONAL_ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--lang)
|
||||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--lang")"
|
||||
set_language "$2"
|
||||
shift 2
|
||||
;;
|
||||
--lang=*)
|
||||
set_language "${1#*=}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
POSITIONAL_ARGS+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
require_command() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "$(trf missing_dependency "$1")"
|
||||
}
|
||||
|
||||
map_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64)
|
||||
printf 'amd64\n'
|
||||
;;
|
||||
aarch64|arm64)
|
||||
printf 'arm64\n'
|
||||
;;
|
||||
*)
|
||||
die "$(trf unsupported_arch "$(uname -m)")"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
docker_bash() {
|
||||
docker exec "${CONTAINER_NAME}" bash -lc "$1"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
local exit_code="$?"
|
||||
if [[ "${exit_code}" -ne 0 ]]; then
|
||||
log "$(trf test_failed_collecting)"
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'systemctl status epusdt --no-pager || true' || true
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'journalctl -u epusdt --no-pager -n 100 || true' || true
|
||||
fi
|
||||
docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true
|
||||
docker rmi -f "${IMAGE_TAG}" >/dev/null 2>&1 || true
|
||||
rm -rf "${TMPDIR_PATH:-}"
|
||||
}
|
||||
|
||||
wait_for_systemd() {
|
||||
local attempt
|
||||
for attempt in $(seq 1 30); do
|
||||
if docker exec "${CONTAINER_NAME}" bash -lc 'systemctl show --property=Version --value >/dev/null 2>&1'; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
set_language ""
|
||||
parse_global_options "$@"
|
||||
|
||||
INSTALL_TAG="${POSITIONAL_ARGS[0]:-}"
|
||||
UPGRADE_TAG="${POSITIONAL_ARGS[1]:-}"
|
||||
[[ -n "${INSTALL_TAG}" ]] || {
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
[[ "${INSTALL_TAG}" == v* ]] || die "$(trf install_tag_invalid)"
|
||||
if [[ -n "${UPGRADE_TAG}" && "${UPGRADE_TAG}" != v* ]]; then
|
||||
die "$(trf upgrade_tag_invalid)"
|
||||
fi
|
||||
|
||||
require_command docker
|
||||
require_command curl
|
||||
|
||||
ASSET_ARCH="$(map_arch)"
|
||||
ASSET_URL="https://github.com/GMWalletApp/epusdt/releases/download/${INSTALL_TAG}/epusdt-${INSTALL_TAG#v}-linux-${ASSET_ARCH}.tar.gz"
|
||||
SUMS_URL="https://github.com/GMWalletApp/epusdt/releases/download/${INSTALL_TAG}/SHA256SUMS"
|
||||
|
||||
curl --fail --silent --show-error --head "${ASSET_URL}" >/dev/null
|
||||
curl --fail --silent --show-error --head "${SUMS_URL}" >/dev/null
|
||||
if [[ -n "${UPGRADE_TAG}" ]]; then
|
||||
curl --fail --silent --show-error --head "https://github.com/GMWalletApp/epusdt/releases/download/${UPGRADE_TAG}/epusdt-${UPGRADE_TAG#v}-linux-${ASSET_ARCH}.tar.gz" >/dev/null
|
||||
curl --fail --silent --show-error --head "https://github.com/GMWalletApp/epusdt/releases/download/${UPGRADE_TAG}/SHA256SUMS" >/dev/null
|
||||
fi
|
||||
|
||||
TMPDIR_PATH="$(mktemp -d)"
|
||||
trap cleanup EXIT
|
||||
|
||||
cat > "${TMPDIR_PATH}/Dockerfile" <<EOF
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
ENV container=docker
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
jq \
|
||||
sudo \
|
||||
systemd \
|
||||
systemd-sysv \
|
||||
tar \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd -m -s /bin/bash tester \
|
||||
&& echo 'tester ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/tester \
|
||||
&& chmod 0440 /etc/sudoers.d/tester
|
||||
|
||||
STOPSIGNAL SIGRTMIN+3
|
||||
|
||||
CMD ["/sbin/init"]
|
||||
EOF
|
||||
|
||||
log "$(trf base_image "${BASE_IMAGE}")"
|
||||
log "$(trf building_image)"
|
||||
docker build -t "${IMAGE_TAG}" "${TMPDIR_PATH}" >/dev/null
|
||||
|
||||
log "$(trf starting_container)"
|
||||
docker run -d \
|
||||
--name "${CONTAINER_NAME}" \
|
||||
--privileged \
|
||||
--cgroupns=host \
|
||||
-e container=docker \
|
||||
--tmpfs /run \
|
||||
--tmpfs /run/lock \
|
||||
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
||||
-v "${SCRIPT_DIR}:/work" \
|
||||
-w /work \
|
||||
"${IMAGE_TAG}" \
|
||||
/sbin/init >/dev/null
|
||||
|
||||
wait_for_systemd || die "$(trf systemd_not_ready)"
|
||||
|
||||
log "$(trf self_installing)"
|
||||
docker exec "${CONTAINER_NAME}" bash -lc "su - tester -c 'cd /work && ./epctl self-install'"
|
||||
docker_bash '[[ -x /usr/local/bin/epctl ]]'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'su - tester -c "EPCTL_NO_COLOR=1 epctl help | grep -q \"^用法:\""'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'su - tester -c "EPCTL_NO_COLOR=1 epctl --lang zh help | grep -q \"^用法:\""'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'su - tester -c "EPCTL_NO_COLOR=1 epctl --lang en help | grep -q \"^Usage:\""'
|
||||
|
||||
log "$(trf downloading_release "${INSTALL_TAG}")"
|
||||
docker exec "${CONTAINER_NAME}" env TAG="${INSTALL_TAG}" bash -lc 'su - tester -c "epctl download --tag ${TAG}"'
|
||||
|
||||
log "$(trf installing_release "${INSTALL_TAG}")"
|
||||
docker exec "${CONTAINER_NAME}" env TAG="${INSTALL_TAG}" bash -lc 'su - tester -c "epctl install --tag ${TAG} --app-uri http://127.0.0.1:8000"'
|
||||
|
||||
log "$(trf waiting_assets)"
|
||||
docker exec "${CONTAINER_NAME}" bash -lc '
|
||||
for _ in $(seq 1 30); do
|
||||
if systemctl is-active --quiet epusdt && [[ -f /opt/epusdt/www/index.html ]]; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
systemctl status epusdt --no-pager || true
|
||||
journalctl -u epusdt --no-pager -n 100 || true
|
||||
exit 1
|
||||
'
|
||||
|
||||
docker_bash '[[ -x /opt/epusdt/epusdt ]]'
|
||||
docker_bash '[[ -f /opt/epusdt/.env ]]'
|
||||
docker_bash 'systemctl is-active --quiet epusdt'
|
||||
docker_bash '[[ -f /opt/epusdt/www/index.html ]]'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'su - tester -c "epctl show-config | grep -q \"^install=false$\""'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'su - tester -c "EPCTL_NO_COLOR=1 epctl --lang zh show-config | grep -q \"^install=false$\""'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc '
|
||||
su - tester -c "epctl status >/tmp/epctl-status.out 2>/tmp/epctl-status.err"
|
||||
! grep -q "systemd-journal" /tmp/epctl-status.err
|
||||
'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc '
|
||||
su - tester -c "epctl logs --lines 50 >/tmp/epctl-logs.out 2>/tmp/epctl-logs.err"
|
||||
! grep -q "systemd-journal" /tmp/epctl-logs.err
|
||||
'
|
||||
|
||||
if [[ -n "${UPGRADE_TAG}" ]]; then
|
||||
log "$(trf upgrading_release "${INSTALL_TAG}" "${UPGRADE_TAG}")"
|
||||
docker exec "${CONTAINER_NAME}" env TAG="${UPGRADE_TAG}" bash -lc 'su - tester -c "epctl upgrade --tag ${TAG}"'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc '
|
||||
for _ in $(seq 1 30); do
|
||||
if systemctl is-active --quiet epusdt && [[ -f /opt/epusdt/www/index.html ]]; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
systemctl status epusdt --no-pager || true
|
||||
journalctl -u epusdt --no-pager -n 100 || true
|
||||
exit 1
|
||||
'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc 'su - tester -c "epctl show-config | grep -q \"^install=false$\""'
|
||||
docker exec "${CONTAINER_NAME}" bash -lc '
|
||||
su - tester -c "epctl status >/tmp/epctl-status-upgrade.out 2>/tmp/epctl-status-upgrade.err"
|
||||
! grep -q "systemd-journal" /tmp/epctl-status-upgrade.err
|
||||
'
|
||||
fi
|
||||
|
||||
log "$(trf checking_init_password)"
|
||||
docker exec "${CONTAINER_NAME}" bash -lc '
|
||||
response="$(su - tester -c "epctl init-password")"
|
||||
password="$(printf "%s\n" "${response}" | jq -r ".data.password")"
|
||||
printf "%s\n" "${response}" | jq -e ".status_code == 200 and (.data.password | type == \"string\" and length > 0)" >/dev/null
|
||||
|
||||
login_payload="$(jq -nc --arg username admin --arg password "${password}" "{username:\$username,password:\$password}")"
|
||||
token="$(curl --fail --silent --show-error \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "${login_payload}" \
|
||||
http://127.0.0.1:8000/admin/api/v1/auth/login | jq -r ".data.token")"
|
||||
[[ -n "${token}" && "${token}" != "null" ]]
|
||||
|
||||
change_payload="$(jq -nc --arg old_password "${password}" --arg new_password "new-pass-789" "{old_password:\$old_password,new_password:\$new_password}")"
|
||||
curl --fail --silent --show-error \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "${change_payload}" \
|
||||
http://127.0.0.1:8000/admin/api/v1/auth/password | jq -e ".status_code == 200" >/dev/null
|
||||
|
||||
failure_output="$(mktemp)"
|
||||
if su - tester -c "epctl init-password" >"${failure_output}" 2>&1; then
|
||||
cat "${failure_output}" >&2
|
||||
rm -f "${failure_output}"
|
||||
echo "'"$(trf expected_second_failure)"'" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -Eq "\"status_code\"[[:space:]]*:[[:space:]]*10040" "${failure_output}"
|
||||
rm -f "${failure_output}"
|
||||
'
|
||||
|
||||
log "$(trf verification_succeeded "${INSTALL_TAG}" "${UPGRADE_TAG:+ upgrade=${UPGRADE_TAG}}")"
|
||||
Reference in New Issue
Block a user