mirror of
https://github.com/hansjone/netx.git
synced 2026-10-16 03:00:46 +08:00
Linux installer: catch paths the netx user cannot enter; --grant-access.
Installing under /root left systemd failing with 203/EXEC and empty logs. The installer now checks that the service user can enter the program and data dirs; blocked parents are granted traverse-only access (setfacl u:netx:x, else chmod o+x) with --grant-access or after asking, otherwise it stops with the exact command. Start failures show the supervisor log or journalctl when api.log is empty. README: /root installs and UME with self-signed certificates (NETX_ALLOW_INSECURE_DEFAULTS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
78db39eabe
commit
b0b64ba07d
4 changed files with 126 additions and 4 deletions
|
|
@ -26,6 +26,20 @@ sudo ./install.sh
|
||||||
sudo ./install.sh --prefix /data/netx --data /data/netx-data
|
sudo ./install.sh --prefix /data/netx --data /data/netx-data
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**目录在 `/root` 下面时**:NetX 服务以系统用户 `netx` 运行(内置 PostgreSQL 不允许用 root 运行),而 `/root` 默认不允许其他用户进入。安装程序会检测到这种情况:交互安装时询问是否授权;非交互安装加 `--grant-access` 自动授权。授权只给 `netx` 开放上级目录的"进入"权限(不能查看目录内容):优先用 `setfacl -m u:netx:x`,系统没有 ACL 工具时用 `chmod o+x`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./install.sh --prefix /root/service/netx/programfile --data /root/service/netx/userdata --grant-access
|
||||||
|
```
|
||||||
|
|
||||||
|
已经装在 `/root` 下、启动失败且日志为空的,手动授权后直接 `sudo netxctl start` 即可,不用重装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
setfacl -m u:netx:x /root /root/service /root/service/netx
|
||||||
|
```
|
||||||
|
|
||||||
|
卸载时不会撤销这些授权;需要的话用 `setfacl -x u:netx <目录>` 撤销。
|
||||||
|
|
||||||
全部安装参数见 `./install.sh --help`。
|
全部安装参数见 `./install.sh --help`。
|
||||||
|
|
||||||
交互式安装会依次询问:监听地址(默认 `0.0.0.0`)、**Web/API 端口**(默认 8890,被占用会提示重新输入)、数据库(内置 / 外置)、**内置 PostgreSQL 端口**(默认 15432,只监听 127.0.0.1)。
|
交互式安装会依次询问:监听地址(默认 `0.0.0.0`)、**Web/API 端口**(默认 8890,被占用会提示重新输入)、数据库(内置 / 外置)、**内置 PostgreSQL 端口**(默认 15432,只监听 127.0.0.1)。
|
||||||
|
|
@ -39,7 +53,19 @@ sudo ./install.sh --yes --db external --db-url 'postgresql+psycopg://netx:密码
|
||||||
|
|
||||||
装好后访问 `http://<服务器IP>:<端口>/`,初始账号 `admin`,密码在安装结束时显示(随机生成,也可用 `--admin-password` 指定;同时保存在 `/var/lib/netx/.env` 的 `NETX_BOOTSTRAP_ADMIN_PASSWORD`),首次登录必须改密码。
|
装好后访问 `http://<服务器IP>:<端口>/`,初始账号 `admin`,密码在安装结束时显示(随机生成,也可用 `--admin-password` 指定;同时保存在 `/var/lib/netx/.env` 的 `NETX_BOOTSTRAP_ADMIN_PASSWORD`),首次登录必须改密码。
|
||||||
|
|
||||||
> 对外监听(非 127.0.0.1)时 NetX 自身要求:管理员初始密码不能是 admin123,并且开启 UME 的 TLS 校验。安装程序会自动设置 `NETX_UME_VERIFY_TLS=true`;如果 UME 用的是自签名证书,需要配置证书,或者改成只监听本机再通过反向代理访问。
|
> 对外监听(非 127.0.0.1)时 NetX 自身要求:管理员初始密码不能是 admin123,并且开启 UME 的 TLS 校验,否则拒绝启动(api.log 末尾是 `security_bootstrap.py ... SystemExit: 2`)。安装程序会自动生成随机管理员密码,并设置 `NETX_UME_VERIFY_TLS=true`。
|
||||||
|
>
|
||||||
|
> **对接 UME(通常是自签名证书)**:在 `/var/lib/netx/.env` 中加入下面几行,然后 `sudo netxctl restart`:
|
||||||
|
>
|
||||||
|
> ```
|
||||||
|
> NETX_UME_BASE_URL=https://<UME地址>:<端口>
|
||||||
|
> NETX_UME_USERNAME=...
|
||||||
|
> NETX_UME_PASSWORD=...
|
||||||
|
> NETX_UME_VERIFY_TLS=false
|
||||||
|
> NETX_ALLOW_INSECURE_DEFAULTS=1
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> `NETX_ALLOW_INSECURE_DEFAULTS=1` 会跳过上面这组启动检查。由于管理员密码和 JWT 密钥在安装时已经单独生成,实际放开的只有"访问 UME 时不校验证书"这一项(与 Windows 版默认行为相同)。
|
||||||
|
|
||||||
| 路径 | 内容 |
|
| 路径 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ NO_SERVICE=0
|
||||||
AUTO_UPDATE=0
|
AUTO_UPDATE=0
|
||||||
OPEN_FIREWALL=0
|
OPEN_FIREWALL=0
|
||||||
NO_BACKUP=0
|
NO_BACKUP=0
|
||||||
|
GRANT_ACCESS=0
|
||||||
YES=0
|
YES=0
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
|
|
@ -55,6 +56,7 @@ $(t "用法" "Usage"): sudo ./install.sh [options]
|
||||||
--auto-update $(t "开启每日自动更新" "enable daily automatic updates")
|
--auto-update $(t "开启每日自动更新" "enable daily automatic updates")
|
||||||
--open-firewall $(t "自动放行防火墙端口(firewalld/ufw)" "open the port in firewalld/ufw")
|
--open-firewall $(t "自动放行防火墙端口(firewalld/ufw)" "open the port in firewalld/ufw")
|
||||||
--no-backup $(t "更新前不备份数据库" "skip the pre-update database dump")
|
--no-backup $(t "更新前不备份数据库" "skip the pre-update database dump")
|
||||||
|
--grant-access $(t "目录在 /root 等受限目录下时,自动给服务用户 netx 开放上级目录的“进入”权限" "if a parent dir (e.g. /root) blocks the netx service user, grant it traverse-only access")
|
||||||
-y, --yes $(t "非交互,使用默认值" "non-interactive, accept defaults")
|
-y, --yes $(t "非交互,使用默认值" "non-interactive, accept defaults")
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +83,7 @@ while [[ $# -gt 0 ]]; do
|
||||||
--auto-update) AUTO_UPDATE=1; shift ;;
|
--auto-update) AUTO_UPDATE=1; shift ;;
|
||||||
--open-firewall) OPEN_FIREWALL=1; shift ;;
|
--open-firewall) OPEN_FIREWALL=1; shift ;;
|
||||||
--no-backup) NO_BACKUP=1; shift ;;
|
--no-backup) NO_BACKUP=1; shift ;;
|
||||||
|
--grant-access) GRANT_ACCESS=1; shift ;;
|
||||||
-y|--yes) YES=1; shift ;;
|
-y|--yes) YES=1; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) err "unknown option: $1"; usage; exit 2 ;;
|
*) err "unknown option: $1"; usage; exit 2 ;;
|
||||||
|
|
@ -524,6 +527,7 @@ summary() {
|
||||||
|
|
||||||
do_install() { # install or reinstall
|
do_install() { # install or reinstall
|
||||||
ensure_user
|
ensure_user
|
||||||
|
check_netx_access "${PREFIX}" "${DATA}"
|
||||||
mkdir -p "${DATA}"
|
mkdir -p "${DATA}"
|
||||||
ensure_data_dirs "${DATA}"
|
ensure_data_dirs "${DATA}"
|
||||||
if [[ -x "${PREFIX}/current/packaging/linux/netxctl" ]]; then
|
if [[ -x "${PREFIX}/current/packaging/linux/netxctl" ]]; then
|
||||||
|
|
@ -539,7 +543,7 @@ do_install() { # install or reinstall
|
||||||
if [[ "${AUTO_UPDATE}" == "1" ]]; then "${PREFIX}/current/packaging/linux/netxctl" auto-update on || true; fi
|
if [[ "${AUTO_UPDATE}" == "1" ]]; then "${PREFIX}/current/packaging/linux/netxctl" auto-update on || true; fi
|
||||||
if ! start_and_wait; then
|
if ! start_and_wait; then
|
||||||
err "$(t "NetX 启动失败,日志" "NetX failed to start; logs"): ${DATA}/logs/"
|
err "$(t "NetX 启动失败,日志" "NetX failed to start; logs"): ${DATA}/logs/"
|
||||||
tail -n 30 "${DATA}/logs/api.log" >&2 2>/dev/null || true
|
show_start_failure "${DATA}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
prune_releases ""
|
prune_releases ""
|
||||||
|
|
@ -549,6 +553,7 @@ do_install() { # install or reinstall
|
||||||
do_update() {
|
do_update() {
|
||||||
[[ -f "${ENV_FILE}" ]] || die "$(t "未找到已有安装(${ENV_FILE}),请用 --mode install" "no existing install (${ENV_FILE}); use --mode install")"
|
[[ -f "${ENV_FILE}" ]] || die "$(t "未找到已有安装(${ENV_FILE}),请用 --mode install" "no existing install (${ENV_FILE}); use --mode install")"
|
||||||
ensure_user
|
ensure_user
|
||||||
|
check_netx_access "${PREFIX}" "${DATA}"
|
||||||
ensure_data_dirs "${DATA}"
|
ensure_data_dirs "${DATA}"
|
||||||
local old_ver=""
|
local old_ver=""
|
||||||
if [[ -f "${PREFIX}/current/version.json" ]]; then
|
if [[ -f "${PREFIX}/current/version.json" ]]; then
|
||||||
|
|
@ -585,7 +590,7 @@ do_update() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
err "$(t "新版本启动失败" "new version failed to start")"
|
err "$(t "新版本启动失败" "new version failed to start")"
|
||||||
tail -n 30 "${DATA}/logs/api.log" >&2 2>/dev/null || true
|
show_start_failure "${DATA}"
|
||||||
svc_stop "${PREFIX}" "${DATA}"
|
svc_stop "${PREFIX}" "${DATA}"
|
||||||
local back=""
|
local back=""
|
||||||
if [[ -n "${old_ver}" && "${old_ver}" != "${VERSION}" && -d "${PREFIX}/releases/${old_ver}" ]]; then
|
if [[ -n "${old_ver}" && "${old_ver}" != "${VERSION}" && -d "${PREFIX}/releases/${old_ver}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,97 @@ client_host() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The service runs as ${NETX_USER}: it must be able to enter the program and data
|
||||||
|
# dirs. Typical failure: paths under /root (mode 550/700) -> systemd 203/EXEC and
|
||||||
|
# empty logs, because netxctl never runs.
|
||||||
|
netx_can_enter() { as_netx /bin/sh -c 'cd "$1"' sh "$1" 2>/dev/null; }
|
||||||
|
|
||||||
|
# Let NETX_USER traverse one directory: ACL for that user only, else o+x.
|
||||||
|
grant_traverse() {
|
||||||
|
local p="$1"
|
||||||
|
if command -v setfacl >/dev/null 2>&1 && setfacl -m "u:${NETX_USER}:x" "${p}" 2>/dev/null; then
|
||||||
|
info "setfacl -m u:${NETX_USER}:x ${p}"
|
||||||
|
else
|
||||||
|
chmod o+x "${p}" && info "chmod o+x ${p}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Can NETX_USER pass through this dir judging by mode bits alone? (Used only to
|
||||||
|
# name the blocking dirs below an already blocked one, which cannot be probed.)
|
||||||
|
others_can_traverse() {
|
||||||
|
local m
|
||||||
|
m="$(stat -c %A "$1" 2>/dev/null)" || return 1
|
||||||
|
[[ "${m:9:1}" == "x" || "${m:9:1}" == "t" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# check_netx_access DIR... — every dir must be enterable by NETX_USER.
|
||||||
|
# Blocked parents (typically /root) are granted traverse-only access when
|
||||||
|
# GRANT_ACCESS=1 or the interactive user agrees; otherwise abort with the commands.
|
||||||
|
# (Plain strings, not arrays: empty arrays trip `set -u` on bash 4.2 / CentOS 7.)
|
||||||
|
check_netx_access() {
|
||||||
|
local d p rest comp ans blocked="" seen_block
|
||||||
|
for d in "$@"; do
|
||||||
|
mkdir -p "${d}"
|
||||||
|
netx_can_enter "${d}" && continue
|
||||||
|
p=""
|
||||||
|
seen_block=0
|
||||||
|
rest="${d#/}"
|
||||||
|
while [[ -n "${rest}" ]]; do
|
||||||
|
comp="${rest%%/*}"
|
||||||
|
if [[ "${rest}" == */* ]]; then rest="${rest#*/}"; else rest=""; fi
|
||||||
|
[[ -n "${comp}" ]] || continue
|
||||||
|
p="${p}/${comp}"
|
||||||
|
if [[ "${GRANT_ACCESS:-0}" == "1" ]]; then
|
||||||
|
if ! netx_can_enter "${p}"; then
|
||||||
|
grant_traverse "${p}"
|
||||||
|
blocked="${blocked} ${p}"
|
||||||
|
fi
|
||||||
|
elif [[ "${seen_block}" == "0" ]]; then
|
||||||
|
if ! netx_can_enter "${p}"; then
|
||||||
|
seen_block=1
|
||||||
|
[[ " ${blocked} " == *" ${p} "* ]] || blocked="${blocked} ${p}"
|
||||||
|
fi
|
||||||
|
elif ! others_can_traverse "${p}" && [[ " ${blocked} " != *" ${p} "* ]]; then
|
||||||
|
blocked="${blocked} ${p}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
blocked="${blocked# }"
|
||||||
|
[[ -n "${blocked}" ]] || return 0
|
||||||
|
if [[ "${GRANT_ACCESS:-0}" != "1" ]]; then
|
||||||
|
warn "$(t "服务用户 ${NETX_USER} 无法进入以下目录(常见于 /root 下面)" "service user ${NETX_USER} cannot enter these directories (typically under /root)"): ${blocked}"
|
||||||
|
if [[ "${INTERACTIVE:-0}" == "1" ]]; then
|
||||||
|
read -r -p "$(t "只给 ${NETX_USER} 开放这些目录的“进入”权限(不能查看内容)?[Y/n] " "Grant ${NETX_USER} traverse-only access to them? [Y/n] ")" ans || true
|
||||||
|
if [[ ! "${ans}" =~ ^[Nn] ]]; then
|
||||||
|
GRANT_ACCESS=1
|
||||||
|
check_netx_access "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
die "$(t "请加 --grant-access 自动授权,或手动执行:setfacl -m u:${NETX_USER}:x ${blocked}(没有 setfacl 时用 chmod o+x),或换到 /root 以外的目录" "re-run with --grant-access, or run: setfacl -m u:${NETX_USER}:x ${blocked} (chmod o+x without setfacl), or use a path outside /root")"
|
||||||
|
fi
|
||||||
|
for d in "$@"; do
|
||||||
|
netx_can_enter "${d}" || die "$(t "授权后 ${NETX_USER} 仍无法进入" "still not enterable by ${NETX_USER} after granting"): ${d}"
|
||||||
|
done
|
||||||
|
ok "$(t "已授权 ${NETX_USER} 进入程序/数据目录" "granted ${NETX_USER} access to the program/data directories")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show why NetX did not come up: app log first, else supervisor log, else systemd.
|
||||||
|
show_start_failure() {
|
||||||
|
local data="$1" f
|
||||||
|
for f in "${data}/logs/api.log" "${data}/logs/netxctl.log"; do
|
||||||
|
if [[ -s "${f}" ]]; then
|
||||||
|
echo "--- ${f}" >&2
|
||||||
|
tail -n 30 "${f}" >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if svc_unit_installed; then
|
||||||
|
echo "--- journalctl -u netx" >&2
|
||||||
|
journalctl -u netx.service -n 30 --no-pager >&2 2>/dev/null || systemctl status netx.service --no-pager >&2 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
is_loopback() { case "$1" in 127.0.0.1|localhost|::1) return 0 ;; *) return 1 ;; esac; }
|
is_loopback() { case "$1" in 127.0.0.1|localhost|::1) return 0 ;; *) return 1 ;; esac; }
|
||||||
|
|
||||||
primary_ip() {
|
primary_ip() {
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ wait_healthy() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
err "$(t "NetX 未在 ${timeout} 秒内就绪" "NetX not healthy after ${timeout}s"): $(health_url)"
|
err "$(t "NetX 未在 ${timeout} 秒内就绪" "NetX not healthy after ${timeout}s"): $(health_url)"
|
||||||
tail -n 30 "${NETX_LOG_DIR}/api.log" 2>/dev/null >&2 || true
|
show_start_failure "${NETX_DATA}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue