mirror of
https://github.com/hansjone/netx.git
synced 2026-10-14 22:20:50 +08:00
- packaging/linux: netx-<ver>-linux-x86_64.tar.gz with bundled Python 3.12, PostgreSQL 16 and web/dist (glibc >= 2.17). install.sh (install/update/ reinstall), netxctl (start/stop/status/logs, set-port, set-bind, set-pg-port, reconfigure-db, check-update/update/auto-update, backup, uninstall), systemd units, nohup fallback without systemd. Privilege drop, detaching and tar go through the bundled Python, so bare images without su/runuser/setsid/tar work. - Updates: same GitHub/Forgejo/manifest sources as Windows, sha256 verified, pre-update pg_dump, automatic rollback when the new version does not come up. - Non-loopback bind: generated bootstrap admin password and NETX_UME_VERIFY_TLS=true, as security_bootstrap requires. - Build in manylinux2014 + check_bundle.sh (GLIBC <= 2.17, no missing libs); release-linux.yml builds on v* tags and smoke-tests offline on CentOS 7, Rocky 9, Ubuntu 18.04/24.04, Debian 12, openSUSE Leap 15 and openEuler 22.03 before attaching to the release. - Windows: Setup DB page gets a bundled PG port field and /BundledPgPort=; set_pg_port.ps1 + tray "Built-in database port"; reconfigure keeps the configured port; postgresql.conf is written without a BOM (PS 5.1 Set-Content -Encoding utf8 broke postgres). - Forgejo/GitHub publish scripts include the Linux package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
171 lines
5.9 KiB
Bash
171 lines
5.9 KiB
Bash
# Shared helpers for install.sh and netxctl. Sourced, never executed.
|
|
# Portable bash 4.2+ (CentOS 7): no `wait -n`, no `${var@Q}`, no mapfile -d.
|
|
|
|
NETX_ZH=0
|
|
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in zh*|ZH*) NETX_ZH=1 ;; esac
|
|
|
|
# t "中文" "English"
|
|
t() { if [[ "${NETX_ZH}" == "1" ]]; then printf '%s' "$1"; else printf '%s' "$2"; fi; }
|
|
info() { printf '==> %s\n' "$*"; }
|
|
ok() { printf '\033[32m==> %s\033[0m\n' "$*"; }
|
|
warn() { printf '\033[33m[WARN] %s\033[0m\n' "$*" >&2; }
|
|
err() { printf '\033[31m[ERR] %s\033[0m\n' "$*" >&2; }
|
|
die() { err "$*"; exit 1; }
|
|
|
|
NETX_USER="${NETX_USER:-netx}"
|
|
|
|
have_systemd() { [[ -d /run/systemd/system ]] && command -v systemctl >/dev/null 2>&1; }
|
|
is_root() { [[ "$(id -u)" -eq 0 ]]; }
|
|
|
|
# Run a command as the netx service user (no-op when already that user).
|
|
as_netx() {
|
|
if [[ "$(id -un)" == "${NETX_USER}" ]]; then
|
|
"$@"
|
|
else
|
|
# Bundled Python drops privileges itself: minimal images may lack su/runuser.
|
|
# It also cd's to / (the service user usually cannot read root's cwd).
|
|
netx_admin run-as "${NETX_USER}" "$@"
|
|
fi
|
|
}
|
|
|
|
# netx_admin <subcommand> ... — stdlib helper on the bundled Python.
|
|
# Needs NETX_PY and NETX_ADMIN_PY set by the caller.
|
|
netx_admin() { "${NETX_PY}" -I "${NETX_ADMIN_PY}" "$@"; }
|
|
|
|
env_get() { netx_admin env-get "$1" "$2" 2>/dev/null || true; }
|
|
|
|
# Export every KEY=VALUE from a .env file without eval (values may hold any char).
|
|
load_env_file() {
|
|
local f="$1" kv
|
|
[[ -f "${f}" ]] || return 0
|
|
while IFS= read -r -d '' kv; do
|
|
export "${kv%%=*}=${kv#*=}"
|
|
done < <(netx_admin env-export "${f}")
|
|
}
|
|
|
|
# Runtime paths always live under the data root (same keys as Windows Get-NetxDataEnvMap).
|
|
data_env_pairs() {
|
|
local d="$1"
|
|
printf '%s\n' \
|
|
"NETX_AUTH_MCP_TOKEN_FILE=${d}/data/auth/mcp_token" \
|
|
"NETX_AUTH_SECRET_FILE=${d}/data/auth/jwt_secret" \
|
|
"NETX_CREDENTIAL_SECRET_FILE=${d}/data/auth/credential_secret" \
|
|
"NETX_SCHEDULER_HEARTBEAT_PATH=${d}/data/runtime/scheduler_heartbeat.json" \
|
|
"NETX_RUN_DIR=${d}/data/runtime" \
|
|
"NETX_BIZ_STATE_SPOOL_DIR=${d}/data/biz_state_spool" \
|
|
"NETX_NE_COLLECTION_DATA_DIR=${d}/data/ne_collections" \
|
|
"NETX_NE_EXEC_JOB_DIR=${d}/data/ne_exec_jobs" \
|
|
"NETX_WEBCRT_DATA_DIR=${d}/data/webcrt"
|
|
}
|
|
|
|
ensure_data_dirs() {
|
|
local d="$1" sub
|
|
for sub in data data/auth data/runtime data/biz_state_spool data/ne_collections \
|
|
data/ne_exec_jobs data/webcrt backups logs updates pgdata; do
|
|
mkdir -p "${d}/${sub}"
|
|
done
|
|
if is_root; then
|
|
chown -R "${NETX_USER}:${NETX_USER}" "${d}"
|
|
chmod 0750 "${d}"
|
|
chmod 0700 "${d}/pgdata"
|
|
[[ -f "${d}/.env" ]] && chmod 0600 "${d}/.env"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Client-side host for health checks when bound to a wildcard.
|
|
client_host() {
|
|
case "$1" in
|
|
""|0.0.0.0|"::"|"[::]") echo "127.0.0.1" ;;
|
|
*) echo "$1" ;;
|
|
esac
|
|
}
|
|
|
|
is_loopback() { case "$1" in 127.0.0.1|localhost|::1) return 0 ;; *) return 1 ;; esac; }
|
|
|
|
primary_ip() {
|
|
local ip=""
|
|
if command -v hostname >/dev/null 2>&1; then
|
|
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
fi
|
|
[[ -n "${ip}" ]] || ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')"
|
|
echo "${ip:-<server-ip>}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------- PostgreSQL
|
|
# Callers set: PGBIN (…/pgsql/bin), PGDATA_DIR, NETX_LOG_DIR.
|
|
|
|
pg_ctl_netx() { as_netx env LC_ALL=C LANG=C "${PGBIN}/pg_ctl" "$@"; }
|
|
pg_is_running() { pg_ctl_netx -D "${PGDATA_DIR}" status >/dev/null 2>&1; }
|
|
pg_start() {
|
|
pg_ctl_netx -D "${PGDATA_DIR}" -l "${NETX_LOG_DIR}/pg.log" -w -t 90 start >/dev/null
|
|
}
|
|
pg_stop() { pg_ctl_netx -D "${PGDATA_DIR}" -m fast -w -t 60 stop >/dev/null 2>&1 || true; }
|
|
|
|
# Major version of an initialised cluster (empty when none).
|
|
pg_data_major() { [[ -f "${PGDATA_DIR}/PG_VERSION" ]] && tr -d ' \n' <"${PGDATA_DIR}/PG_VERSION"; }
|
|
|
|
# ---------------------------------------------------------------- service control
|
|
# svc_* PREFIX DATA
|
|
|
|
svc_unit_installed() { have_systemd && [[ -f /etc/systemd/system/netx.service ]]; }
|
|
svc_pidfile() { echo "$1/data/runtime/netx.pid"; }
|
|
|
|
svc_pid_alive() {
|
|
local pf pid
|
|
pf="$(svc_pidfile "$1")"
|
|
[[ -f "${pf}" ]] || return 1
|
|
pid="$(cat "${pf}" 2>/dev/null)"
|
|
[[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null
|
|
}
|
|
|
|
svc_running() {
|
|
local data="$2"
|
|
if svc_unit_installed; then
|
|
systemctl is-active --quiet netx.service
|
|
else
|
|
svc_pid_alive "${data}"
|
|
fi
|
|
}
|
|
|
|
svc_start() {
|
|
local prefix="$1" data="$2"
|
|
if svc_unit_installed; then
|
|
systemctl start netx.service
|
|
return
|
|
fi
|
|
if svc_pid_alive "${data}"; then
|
|
return 0
|
|
fi
|
|
local ctl="${prefix}/current/packaging/linux/netxctl" pf
|
|
pf="$(svc_pidfile "${data}")"
|
|
# Own session => the pid in the file also names the process group to stop.
|
|
# (Bundled Python instead of setsid/nohup: minimal images lack util-linux.)
|
|
# Installed copy: install.sh's own NETX_PY may live where netx cannot enter (/root).
|
|
as_netx "${prefix}/current/python/bin/python3" -I "${prefix}/current/packaging/linux/netx_admin.py" spawn "${data}/logs/netxctl.log" "${pf}" "${ctl}" run
|
|
}
|
|
|
|
svc_stop() {
|
|
local prefix="$1" data="$2"
|
|
if svc_unit_installed; then
|
|
systemctl stop netx.service || true
|
|
elif svc_pid_alive "${data}"; then
|
|
local pid i
|
|
pid="$(cat "$(svc_pidfile "${data}")")"
|
|
kill -TERM "${pid}" 2>/dev/null || true
|
|
for ((i = 0; i < 90; i++)); do
|
|
kill -0 "${pid}" 2>/dev/null || break
|
|
sleep 1
|
|
done
|
|
if kill -0 "${pid}" 2>/dev/null; then
|
|
warn "$(t "进程未在 90 秒内退出,强制结束" "netx did not exit in 90s; killing")"
|
|
kill -KILL -- "-${pid}" 2>/dev/null || kill -KILL "${pid}" 2>/dev/null || true
|
|
fi
|
|
fi
|
|
rm -f "$(svc_pidfile "${data}")"
|
|
# A crashed supervisor can leave the bundled cluster running.
|
|
if [[ -x "${prefix}/current/pgsql/bin/pg_ctl" && -f "${data}/pgdata/PG_VERSION" ]]; then
|
|
PGBIN="${prefix}/current/pgsql/bin" PGDATA_DIR="${data}/pgdata" pg_stop
|
|
fi
|
|
return 0
|
|
}
|