feat: add epctl installer, docker validation script, and bilingual docs

This commit is contained in:
line-6000
2026-06-29 14:26:54 +08:00
parent 7d09adff20
commit 1cdb1edd86
6 changed files with 2287 additions and 1 deletions
+8 -1
View File
@@ -10,7 +10,7 @@
<p align="center">
<a href="./README.en.md">English</a> |
<a href="./README.zh-CN.md">简体中文</a>
<a href="./README.md">简体中文</a>
</p>
<p align="center">
@@ -88,17 +88,24 @@ Quick-start links:
| 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 |
| [aaPanel Deployment](https://epusdt.com/guide/installation/aapanel) | Great for aaPanel users |
| [Manual Deployment](https://epusdt.com/guide/installation/manual.html) | Full manual control |
| [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
```text
Epusdt
├── epctl Linux binary installation and operations script
├── src/ Core project source code
└── wiki/ Documentation assets and knowledge base
```
+7
View File
@@ -94,17 +94,24 @@ Epusdt 已完成第三方安全审计。
| 教程 | 说明 |
|------|------|
| [仓库内:epctl 安装脚本](wiki/EPCTL.md) | Linux 二进制安装、升级、状态查看与 Docker 验收脚本 |
| [Docker 部署](https://epusdt.com/guide/installation/docker) | 推荐方式,一键启动 |
| [宝塔面板部署](https://epusdt.com/guide/installation/aapanel) | 适合宝塔用户 |
| [手动部署](https://epusdt.com/guide/installation/manual.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
Epusdt
├── epctl Linux 二进制安装与运维脚本
├── src/ 项目核心代码
└── wiki/ 文档与知识库
```
Executable
+1467
View File
File diff suppressed because it is too large Load Diff
+379
View File
@@ -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}}")"
+213
View File
@@ -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
View File
@@ -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`