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:
+8
-1
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="./README.en.md">English</a> |
|
<a href="./README.en.md">English</a> |
|
||||||
<a href="./README.zh-CN.md">简体中文</a>
|
<a href="./README.md">简体中文</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -88,17 +88,24 @@ Quick-start links:
|
|||||||
|
|
||||||
| Guide | Description |
|
| Guide | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
|
| [Repo-local: epctl installer](wiki/EPCTL.en.md) | Linux binary install, upgrade, status inspection, and Docker validation |
|
||||||
| [Docker Deployment](https://epusdt.com/guide/installation/docker) | Recommended one-command setup |
|
| [Docker Deployment](https://epusdt.com/guide/installation/docker) | Recommended one-command setup |
|
||||||
| [aaPanel Deployment](https://epusdt.com/guide/installation/aapanel) | Great for aaPanel users |
|
| [aaPanel Deployment](https://epusdt.com/guide/installation/aapanel) | Great for aaPanel users |
|
||||||
| [Manual Deployment](https://epusdt.com/guide/installation/manual.html) | Full manual control |
|
| [Manual Deployment](https://epusdt.com/guide/installation/manual.html) | Full manual control |
|
||||||
| [Developer API Docs](https://epusdt.com) | Integration reference |
|
| [Developer API Docs](https://epusdt.com) | Integration reference |
|
||||||
|
|
||||||
|
The repository also ships these top-level scripts:
|
||||||
|
|
||||||
|
- [`./epctl`](./epctl) for Linux binary install, upgrade, config inspection, service status, logs, and initial password retrieval
|
||||||
|
- [`./epctl-docker-test.sh`](./epctl-docker-test.sh) for real Ubuntu + systemd installation validation inside local Docker
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Epusdt
|
Epusdt
|
||||||
|
├── epctl Linux binary installation and operations script
|
||||||
├── src/ Core project source code
|
├── src/ Core project source code
|
||||||
└── wiki/ Documentation assets and knowledge base
|
└── wiki/ Documentation assets and knowledge base
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -94,17 +94,24 @@ Epusdt 已完成第三方安全审计。
|
|||||||
|
|
||||||
| 教程 | 说明 |
|
| 教程 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
|
| [仓库内:epctl 安装脚本](wiki/EPCTL.md) | Linux 二进制安装、升级、状态查看与 Docker 验收脚本 |
|
||||||
| [Docker 部署](https://epusdt.com/guide/installation/docker) | 推荐方式,一键启动 |
|
| [Docker 部署](https://epusdt.com/guide/installation/docker) | 推荐方式,一键启动 |
|
||||||
| [宝塔面板部署](https://epusdt.com/guide/installation/aapanel) | 适合宝塔用户 |
|
| [宝塔面板部署](https://epusdt.com/guide/installation/aapanel) | 适合宝塔用户 |
|
||||||
| [手动部署](https://epusdt.com/guide/installation/manual.html) | 完全手动控制 |
|
| [手动部署](https://epusdt.com/guide/installation/manual.html) | 完全手动控制 |
|
||||||
| [开发者 API 文档](https://epusdt.com/zh/guide/integration/gmpay.html) | 接口集成指南 |
|
| [开发者 API 文档](https://epusdt.com/zh/guide/integration/gmpay.html) | 接口集成指南 |
|
||||||
|
|
||||||
|
仓库内还提供顶层脚本:
|
||||||
|
|
||||||
|
- [`./epctl`](./epctl) 用于 Linux 二进制安装、升级、查看配置、状态和初始化密码
|
||||||
|
- [`./epctl-docker-test.sh`](./epctl-docker-test.sh) 用于在本机 Docker 里跑 Ubuntu + systemd 的真实安装验收
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Epusdt
|
Epusdt
|
||||||
|
├── epctl Linux 二进制安装与运维脚本
|
||||||
├── src/ 项目核心代码
|
├── src/ 项目核心代码
|
||||||
└── wiki/ 文档与知识库
|
└── wiki/ 文档与知识库
|
||||||
```
|
```
|
||||||
|
|||||||
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}}")"
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# epctl Installation and Verification Scripts
|
||||||
|
|
||||||
|
`epctl` is the repo-root Linux binary installer and service manager for released `epusdt` binaries on GitHub Releases.
|
||||||
|
`epctl-docker-test.sh` is the matching end-to-end validation script. It launches Ubuntu + systemd in Docker and verifies download, install, startup, upgrade, and init-password behavior with real release artifacts.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Linux only
|
||||||
|
- Binary installation only
|
||||||
|
- Release source is fixed to `https://github.com/GMWalletApp/epusdt/releases`
|
||||||
|
- Service management is based on systemd
|
||||||
|
|
||||||
|
## Dependencies and Privileges
|
||||||
|
|
||||||
|
`epctl` expects these commands:
|
||||||
|
|
||||||
|
- `curl`
|
||||||
|
- `tar`
|
||||||
|
- `systemctl`
|
||||||
|
- `install`
|
||||||
|
- `grep`
|
||||||
|
- `sed`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `install`, `upgrade`, and `self-install` write into `/opt`, `/etc/systemd`, and `/usr/local/bin`
|
||||||
|
- `status` and `logs` automatically re-run through `sudo` when needed
|
||||||
|
- in practice, the operator should have `sudo` access
|
||||||
|
|
||||||
|
## Fixed Paths
|
||||||
|
|
||||||
|
| Item | Path |
|
||||||
|
|------|------|
|
||||||
|
| Install directory | `/opt/epusdt` |
|
||||||
|
| Main binary | `/opt/epusdt/epusdt` |
|
||||||
|
| Active config | `/opt/epusdt/.env` |
|
||||||
|
| Example config | `/opt/epusdt/.env.example` |
|
||||||
|
| Extracted frontend assets | `/opt/epusdt/www` |
|
||||||
|
| Download cache | `/tmp/epusdt/<tag>/` |
|
||||||
|
| systemd unit | `/etc/systemd/system/epusdt.service` |
|
||||||
|
| Global epctl path | `/usr/local/bin/epctl` |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Run the interactive menu from the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl
|
||||||
|
```
|
||||||
|
|
||||||
|
The default interactive language is Chinese. You can force either language:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl zh
|
||||||
|
./epctl en
|
||||||
|
./epctl --lang zh help
|
||||||
|
./epctl --lang en help
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the script into PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl self-install
|
||||||
|
epctl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
Download a specific release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl download --tag v1.0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl install --tag v1.0.8 \
|
||||||
|
--app-uri https://pay.example.com \
|
||||||
|
--listen 127.0.0.1:18000
|
||||||
|
```
|
||||||
|
|
||||||
|
Upgrade to a newer release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl upgrade --tag v1.0.9
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect config, status, and logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl show-config
|
||||||
|
./epctl status
|
||||||
|
./epctl logs --lines 200
|
||||||
|
```
|
||||||
|
|
||||||
|
Request the initial admin password:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl init-password
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior When `--tag` Is Omitted
|
||||||
|
|
||||||
|
For `download`, `install`, and `upgrade`, `epctl` resolves the current latest GitHub release tag first, then shows the exact tag and asks for confirmation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl install --app-uri https://pay.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
In interactive use, the script will display the resolved latest tag before continuing.
|
||||||
|
For automation, passing `--tag` explicitly is preferred. If you intentionally want to skip the confirmation, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EPCTL_ASSUME_YES=1 ./epctl download
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Happens on First Install
|
||||||
|
|
||||||
|
`install` performs these steps:
|
||||||
|
|
||||||
|
1. Detect the current CPU architecture and download the matching GitHub Release archive
|
||||||
|
2. Extract into `/tmp/epusdt/<tag>/extract/`
|
||||||
|
3. Install the binary to `/opt/epusdt/epusdt`
|
||||||
|
4. Install `.env.example` to `/opt/epusdt/.env.example`
|
||||||
|
5. Create the system user and group `epusdt`
|
||||||
|
6. Auto-create `/opt/epusdt/.env` from `.env.example` if it does not exist
|
||||||
|
7. Write and enable `epusdt.service`
|
||||||
|
|
||||||
|
When `.env` is auto-created, the script applies only the minimum bootstrap changes:
|
||||||
|
|
||||||
|
- `install=false`
|
||||||
|
- `app_uri=<--app-uri, default http://127.0.0.1:8000>`
|
||||||
|
- `http_listen=<--listen, default 127.0.0.1:8000>`
|
||||||
|
|
||||||
|
If `/opt/epusdt/.env` already exists, install and upgrade keep it unchanged.
|
||||||
|
|
||||||
|
## systemd Service Details
|
||||||
|
|
||||||
|
The service name is fixed to `epusdt.service` and uses these core settings:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
WorkingDirectory=/opt/epusdt
|
||||||
|
ExecStart=/opt/epusdt/epusdt http start
|
||||||
|
User=epusdt
|
||||||
|
Group=epusdt
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
```
|
||||||
|
|
||||||
|
The working directory stays at `/opt/epusdt` because the program extracts its `www/` assets next to the binary.
|
||||||
|
|
||||||
|
## What `init-password` Does
|
||||||
|
|
||||||
|
`epctl init-password` only calls the local HTTP route:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /admin/api/v1/auth/init-password
|
||||||
|
```
|
||||||
|
|
||||||
|
It does not read the database directly.
|
||||||
|
|
||||||
|
The script reads `http_listen` from `/opt/epusdt/.env` and normalizes these listen values into a local request target:
|
||||||
|
|
||||||
|
- `:8000` -> `127.0.0.1:8000`
|
||||||
|
- `0.0.0.0:8000` -> `127.0.0.1:8000`
|
||||||
|
|
||||||
|
If the API returns `10040`, the bootstrap plaintext password is no longer available. Common reasons are:
|
||||||
|
|
||||||
|
- the admin password has already been changed
|
||||||
|
- the initial password has already been consumed and cannot be fetched again
|
||||||
|
|
||||||
|
In that case, `epctl` prints the original HTTP error body for diagnosis.
|
||||||
|
|
||||||
|
## Docker Validation Script
|
||||||
|
|
||||||
|
The repo also provides:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl-docker-test.sh <install-tag> [upgrade-tag]
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl-docker-test.sh v1.0.6
|
||||||
|
./epctl-docker-test.sh --lang en v1.0.6 v1.0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
It performs these checks on the local machine:
|
||||||
|
|
||||||
|
- builds an `ubuntu:24.04` + systemd test image
|
||||||
|
- starts a privileged container
|
||||||
|
- runs `epctl self-install` inside the container
|
||||||
|
- downloads real GitHub Release artifacts
|
||||||
|
- installs `epusdt`
|
||||||
|
- verifies the systemd service, `www/index.html`, config output, logs, and status
|
||||||
|
- verifies that `init-password` succeeds once and later returns `10040` after the admin password is changed
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Docker installed locally
|
||||||
|
- permission to run Docker
|
||||||
|
- outbound access to GitHub Releases
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
- Prefer explicit `--tag` values in automation
|
||||||
|
- After installation, run `./epctl show-config` once to verify the active `.env`
|
||||||
|
- Change the admin password immediately after retrieving the initial password
|
||||||
|
- If you want to validate the installer itself, run `./epctl-docker-test.sh` first
|
||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
# epctl 安装与验证脚本
|
||||||
|
|
||||||
|
`epctl` 是仓库顶层的 Linux 二进制安装管理脚本,面向已经发布到 GitHub Releases 的 `epusdt` 二进制包。
|
||||||
|
`epctl-docker-test.sh` 是配套的真实验收脚本,用本机 Docker 启动 Ubuntu + systemd 容器,完整验证安装、启动、升级和初始化密码流程。
|
||||||
|
|
||||||
|
## 适用范围
|
||||||
|
|
||||||
|
- 仅支持 Linux
|
||||||
|
- 仅支持二进制安装
|
||||||
|
- 安装源固定为 `https://github.com/GMWalletApp/epusdt/releases`
|
||||||
|
- 默认通过 systemd 管理服务
|
||||||
|
|
||||||
|
## 依赖与权限
|
||||||
|
|
||||||
|
`epctl` 依赖这些基础命令:
|
||||||
|
|
||||||
|
- `curl`
|
||||||
|
- `tar`
|
||||||
|
- `systemctl`
|
||||||
|
- `install`
|
||||||
|
- `grep`
|
||||||
|
- `sed`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `install`、`upgrade`、`self-install` 需要写入 `/opt`、`/etc/systemd`、`/usr/local/bin`
|
||||||
|
- `status`、`logs` 会在需要时自动通过 `sudo` 重新执行
|
||||||
|
- 所以日常使用建议当前用户具备 `sudo` 权限
|
||||||
|
|
||||||
|
## 固定路径
|
||||||
|
|
||||||
|
| 项目 | 路径 |
|
||||||
|
|------|------|
|
||||||
|
| 安装目录 | `/opt/epusdt` |
|
||||||
|
| 主程序 | `/opt/epusdt/epusdt` |
|
||||||
|
| 配置文件 | `/opt/epusdt/.env` |
|
||||||
|
| 示例配置 | `/opt/epusdt/.env.example` |
|
||||||
|
| 前端释放目录 | `/opt/epusdt/www` |
|
||||||
|
| 下载缓存 | `/tmp/epusdt/<tag>/` |
|
||||||
|
| systemd unit | `/etc/systemd/system/epusdt.service` |
|
||||||
|
| epctl 全局安装位置 | `/usr/local/bin/epctl` |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
直接在仓库根目录运行交互菜单:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl
|
||||||
|
```
|
||||||
|
|
||||||
|
默认优先进入中文界面。你也可以显式指定语言:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl zh
|
||||||
|
./epctl en
|
||||||
|
./epctl --lang zh help
|
||||||
|
./epctl --lang en help
|
||||||
|
```
|
||||||
|
|
||||||
|
如果想把脚本装进 PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl self-install
|
||||||
|
epctl
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
下载指定版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl download --tag v1.0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
安装服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl install --tag v1.0.8 \
|
||||||
|
--app-uri https://pay.example.com \
|
||||||
|
--listen 127.0.0.1:18000
|
||||||
|
```
|
||||||
|
|
||||||
|
升级到新版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl upgrade --tag v1.0.9
|
||||||
|
```
|
||||||
|
|
||||||
|
查看配置、状态、日志:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl show-config
|
||||||
|
./epctl status
|
||||||
|
./epctl logs --lines 200
|
||||||
|
```
|
||||||
|
|
||||||
|
请求初始化管理员密码:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl init-password
|
||||||
|
```
|
||||||
|
|
||||||
|
## 不传 `--tag` 时的行为
|
||||||
|
|
||||||
|
`download`、`install`、`upgrade` 在未传 `--tag` 时,会先调用 GitHub API 解析当前 latest release tag,再向用户显示实际 tag 并确认。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl install --app-uri https://pay.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
交互模式下会先提示检测到的最新 tag。
|
||||||
|
非交互脚本执行时,建议显式传入 `--tag`。如果你明确要跳过确认,可以设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EPCTL_ASSUME_YES=1 ./epctl download
|
||||||
|
```
|
||||||
|
|
||||||
|
## 首次安装时会发生什么
|
||||||
|
|
||||||
|
执行 `install` 时,脚本会:
|
||||||
|
|
||||||
|
1. 按当前机器架构下载 GitHub Release 压缩包
|
||||||
|
2. 解压到 `/tmp/epusdt/<tag>/extract/`
|
||||||
|
3. 安装二进制到 `/opt/epusdt/epusdt`
|
||||||
|
4. 安装 `.env.example` 到 `/opt/epusdt/.env.example`
|
||||||
|
5. 创建系统用户和组 `epusdt`
|
||||||
|
6. 若 `/opt/epusdt/.env` 不存在,则从 `.env.example` 自动生成
|
||||||
|
7. 写入并启用 `epusdt.service`
|
||||||
|
|
||||||
|
自动生成 `.env` 时,脚本只会补默认上线所需的最小改动:
|
||||||
|
|
||||||
|
- `install=false`
|
||||||
|
- `app_uri=<--app-uri,默认 http://127.0.0.1:8000>`
|
||||||
|
- `http_listen=<--listen,默认 127.0.0.1:8000>`
|
||||||
|
|
||||||
|
如果 `/opt/epusdt/.env` 已存在,则安装和升级都会保留它,不会覆盖。
|
||||||
|
|
||||||
|
## systemd 服务说明
|
||||||
|
|
||||||
|
脚本注册的服务名固定为 `epusdt.service`,核心参数如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
WorkingDirectory=/opt/epusdt
|
||||||
|
ExecStart=/opt/epusdt/epusdt http start
|
||||||
|
User=epusdt
|
||||||
|
Group=epusdt
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
```
|
||||||
|
|
||||||
|
`WorkingDirectory` 固定为 `/opt/epusdt`,因为程序会在二进制同级目录释放 `www/` 静态文件。
|
||||||
|
|
||||||
|
## `init-password` 的含义
|
||||||
|
|
||||||
|
`epctl init-password` 只会请求本地 HTTP 路由:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /admin/api/v1/auth/init-password
|
||||||
|
```
|
||||||
|
|
||||||
|
它不会直接读数据库。
|
||||||
|
|
||||||
|
脚本会从 `/opt/epusdt/.env` 解析 `http_listen`,然后自动把这些监听写法转成本地可请求地址:
|
||||||
|
|
||||||
|
- `:8000` -> `127.0.0.1:8000`
|
||||||
|
- `0.0.0.0:8000` -> `127.0.0.1:8000`
|
||||||
|
|
||||||
|
如果接口返回 `10040`,含义是初始化明文密码已经不可用。常见原因是:
|
||||||
|
|
||||||
|
- 管理员已经登录并修改过密码
|
||||||
|
- 初始化密码已经被消费,当前不再允许再次取回
|
||||||
|
|
||||||
|
此时脚本会直接把接口原始错误输出出来,方便排查。
|
||||||
|
|
||||||
|
## Docker 验收脚本
|
||||||
|
|
||||||
|
仓库顶层提供:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl-docker-test.sh <install-tag> [upgrade-tag]
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./epctl-docker-test.sh v1.0.6
|
||||||
|
./epctl-docker-test.sh --lang zh v1.0.6 v1.0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
它会在本机:
|
||||||
|
|
||||||
|
- 构建 `ubuntu:24.04` + systemd 测试镜像
|
||||||
|
- 启动一个特权容器
|
||||||
|
- 在容器内执行 `epctl self-install`
|
||||||
|
- 下载真实 GitHub Release
|
||||||
|
- 安装 `epusdt`
|
||||||
|
- 检查 `systemd` 服务、`www/index.html`、配置文件、日志、状态输出
|
||||||
|
- 验证 `init-password` 首次成功、修改管理员密码后再次请求返回 `10040`
|
||||||
|
|
||||||
|
运行前提:
|
||||||
|
|
||||||
|
- 本机已安装 Docker
|
||||||
|
- 当前用户有权限执行 Docker
|
||||||
|
- 宿主机能够访问 GitHub Releases
|
||||||
|
|
||||||
|
## 建议
|
||||||
|
|
||||||
|
- 自动化部署场景优先显式传 `--tag`
|
||||||
|
- 生产环境建议安装完成后先执行一次 `./epctl show-config`
|
||||||
|
- 首次拿到初始化密码后,建议立即登录后台修改管理员密码
|
||||||
|
- 如果只是验证脚本是否可用,优先跑 `./epctl-docker-test.sh`
|
||||||
Reference in New Issue
Block a user