From ac379269e754eac860f82d62a5dcca7aeef8c34d Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 6 Nov 2026 13:08:07 +0800 Subject: [PATCH] fix: centralize service preparation for startup and recovery --- README.md | 11 ++++ jmsctl.sh | 25 ++++--- scripts/gists/ansible-executor.sh | 7 +- .../gists/postgresql-upgrade-standalone.sh | 4 +- scripts/gists/service.sh | 66 +++++++++++++++---- 5 files changed, 88 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index c01a13a3..1739032f 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,17 @@ JDMC artifact 的升级脚本完成数据、配置和 systemd 服务迁移。安 命令行使用 `jdmc` 作为 JDMC 服务目标。 `--skip-jdmc` 仅供 JDMC 发起 JumpServer 重启时避免停止自身,不是组件开关。 +组件的目录、权限和密钥初始化统一由安装器负责: + +```bash +./jmsctl.sh prepare chen video-worker +./jmsctl.sh prepare --containers jms_chen jms_video-worker +``` + +不指定服务时准备所有已启用服务。`--containers` 必须指定已停止的容器,按容器实际使用的 +镜像和运行用户准备,适用于 JDMC 主备及组件补丁后的启动。此命令不启动或重建业务容器, +不改写配置或执行数据库操作。JDMC 在两端维护升级及拉起应用前调用它,继续负责主备启停。 + ## 离线镜像清单 `scripts/gists/image.sh` 是离线镜像选择和重标记规则的唯一来源。调用 diff --git a/jmsctl.sh b/jmsctl.sh index 93986de3..2eb38870 100755 --- a/jmsctl.sh +++ b/jmsctl.sh @@ -70,6 +70,8 @@ function usage() { echo " uninstall $(gettext 'Uninstall JumpServer')" echo echo "More Commands: " + echo " prepare [service ...] Prepare service files and permissions without starting services" + echo " prepare --containers Prepare stopped containers using their installed images" echo " load_image $(gettext 'Loading docker image')" echo " backup_db $(gettext 'Backup database')" echo " backup_no_audit $(gettext 'Backup database without audit data')" @@ -103,9 +105,7 @@ function start() { configure_jdmc || return 1 gen_safe_config >/dev/null stop_disabled_video_worker || return 1 - prepare_chen_volume || return 1 - prepare_video_worker_volume || return 1 - prepare_ansible_executor || return 1 + prepare_services_for_start || return 1 EXE=$(get_docker_compose_cmd_line) if [[ "$(get_config_or_env ANSIBLE_EXECUTOR_ENABLED)" != "0" ]]; then ${EXE} up -d --wait --wait-timeout 60 ansible-executor || return 1 @@ -173,9 +173,7 @@ function restart() { echo -e "\n" if [[ -n "${target}" && "${target}" != "ignore_db" ]]; then - if [[ "${target}" == "chen" || "${target}" == "jms_chen" ]]; then - prepare_chen_volume || return 1 - fi + prepare_services_for_start "${target}" || return 1 ${EXE} up -d "${target}" || return 1 remove_stopped_openbao_init_container return @@ -215,7 +213,7 @@ function video-worker() { log_error "video-worker is disabled by VIDEO_WORKER_ENABLED=0" return 1 fi - prepare_video_worker_volume && ${EXE} up -d + prepare_services_for_start video-worker && ${EXE} up -d ;; stop) ${EXE} down -v ;; restart) @@ -223,7 +221,7 @@ function video-worker() { log_error "video-worker is disabled by VIDEO_WORKER_ENABLED=0" return 1 fi - ${EXE} down -v && prepare_video_worker_volume && ${EXE} up -d + ${EXE} down -v && prepare_services_for_start video-worker && ${EXE} up -d ;; status) ${EXE} ps ;; *) @@ -255,6 +253,17 @@ function check_os() { function main() { check_os || return 3 + # Preparation also runs on an HA standby. Do not render Compose, rewrite + # configuration, or enter any service/database lifecycle action here. + if [[ "${action}" == prepare ]]; then + [[ -f "${CONFIG_FILE}" ]] || { + log_error "Configuration file not found: ${CONFIG_FILE}" + return 3 + } + prepare_services_for_start "${args[@]:1}" + return $? + fi + if [[ "${action}" == "help" || "${action}" == "h" || "${action}" == "-h" || "${action}" == "--help" ]]; then echo "" elif [[ "${action}" == "install" || "${action}" == "config" || "${action}" == "reconfig" || "${action}" == "uninstall" ]]; then diff --git a/scripts/gists/ansible-executor.sh b/scripts/gists/ansible-executor.sh index 86f65d60..6d65c68d 100644 --- a/scripts/gists/ansible-executor.sh +++ b/scripts/gists/ansible-executor.sh @@ -17,8 +17,11 @@ function prepare_ansible_executor() { fi credential_dir="${CONFIG_DIR}/ansible-executor" - tag=$(get_ansible_executor_tag) || return 1 - image="$(get_image_namespace)/ansible-executor:${tag}" + image=${1:-} + if [[ -z "${image}" ]]; then + tag=$(get_ansible_executor_tag) || return 1 + image="$(get_image_namespace)/ansible-executor:${tag}" + fi mkdir -p "${credential_dir}" || return 1 chmod 700 "${credential_dir}" || return 1 # Use the shipped SSH tools; the host needs no additional key-generation package. diff --git a/scripts/gists/postgresql-upgrade-standalone.sh b/scripts/gists/postgresql-upgrade-standalone.sh index 59dde452..5594eb1c 100644 --- a/scripts/gists/postgresql-upgrade-standalone.sh +++ b/scripts/gists/postgresql-upgrade-standalone.sh @@ -88,12 +88,10 @@ complete_internal_postgresql_upgrade() { # Called only after application schema and JDMC upgrades have succeeded. while read -r container running policy retries service; do [[ "${running}" == true ]] || continue - if [[ "${service}" == chen ]]; then - prepare_chen_volume || return 1 - fi services+=("${service}") done <"${PG18_WORK}/writers" if (( ${#services[@]} )); then + prepare_services_for_start "${services[@]}" || return 1 cmd=$(get_docker_compose_cmd_line) || return 1 ${cmd} up -d "${services[@]}" || return 1 fi diff --git a/scripts/gists/service.sh b/scripts/gists/service.sh index 30aff64f..c4a04a80 100644 --- a/scripts/gists/service.sh +++ b/scripts/gists/service.sh @@ -328,18 +328,57 @@ function stop_disabled_video_worker() { fi } +function prepare_services_for_start() { + local service enabled container image owner running metadata extra from_containers=0 + if [[ "${1:-}" == --containers ]]; then + from_containers=1 + shift + (( $# > 0 )) || { + log_error 'prepare --containers requires at least one container' + return 1 + } + fi + local -a selected=("$@") + if (( $# == 0 )); then + enabled=$(get_enabled_services) || return 1 + read -r -a selected <<<"${enabled}" + fi + for service in "${selected[@]}"; do + image='' owner='' + if [[ "${from_containers}" == 1 ]]; then + container=${service} + [[ "${container}" =~ ^/?jms_[A-Za-z0-9_.-]+$ ]] || return 1 + metadata=$(docker inspect -f '{{.State.Running}}|{{.Image}}|{{index .Config.Labels "com.docker.compose.service"}}|{{.Config.User}}' "${container}") || return 1 + IFS='|' read -r running image service owner extra <<<"${metadata}" + [[ "${running}" == false && "${image}" =~ ^sha256:[a-f0-9]{64}$ && \ + "${service}" =~ ^[A-Za-z0-9_.-]+$ && -z "${extra}" ]] || { + log_error "Cannot prepare a running or invalid service container: ${container}" + return 1 + } + fi + case "${service#jms_}" in + chen) prepare_chen_volume "${image}" "${owner}" || return 1 ;; + video-worker) prepare_video_worker_volume "${image}" "${owner}" || return 1 ;; + ansible-executor|ansible_executor) prepare_ansible_executor "${image}" || return 1 ;; + esac + done +} + function prepare_chen_volume() { [[ "$(get_config_or_env CHEN_ENABLED)" != "0" ]] || return 0 local image owner data_dir folder - image="$(get_image_namespace)/chen:${VERSION}" - owner=$(docker image inspect -f '{{.Config.User}}' "${image}") || { - log_error "Unable to inspect Chen image user: ${image}" - return 1 - } + image=${1:-"$(get_image_namespace)/chen:${VERSION}"} + owner=${2:-} + if [[ -z "${owner}" ]]; then + owner=$(docker image inspect -f '{{.Config.User}}' "${image}") || { + log_error "Unable to inspect Chen image user: ${image}" + return 1 + } + fi # Legacy Java images run as root and need no ownership migration. case "${owner}" in ''|root|root:root|0|0:0) return 0 ;; esac if [[ ! "${owner}" =~ ^[0-9]+:[0-9]+$ ]]; then - owner=$(docker run --rm --entrypoint /bin/sh "${image}" -c \ + owner=$(docker run --rm --network none --user "${owner}" --entrypoint /bin/sh "${image}" -c \ 'printf "%s:%s\n" "$(id -u)" "$(id -g)"') || return 1 fi if [[ ! "${owner}" =~ ^[0-9]+:[0-9]+$ ]]; then @@ -370,15 +409,18 @@ function prepare_video_worker_volume() { data_dir="${volume_dir}/video-worker/data" mkdir -p "${data_dir}" || return 1 - image="$(get_image_namespace)/video-worker:${VERSION}" - user_spec=$(docker image inspect -f '{{.Config.User}}' "${image}") || { - log_error "Unable to inspect video-worker image user: ${image}" - return 1 - } + image=${1:-"$(get_image_namespace)/video-worker:${VERSION}"} + user_spec=${2:-} + if [[ -z "${user_spec}" ]]; then + user_spec=$(docker image inspect -f '{{.Config.User}}' "${image}") || { + log_error "Unable to inspect video-worker image user: ${image}" + return 1 + } + fi if [[ "${user_spec}" =~ ^[0-9]+:[0-9]+$ ]]; then owner=${user_spec} else - owner=$(docker run --rm --entrypoint /bin/sh "${image}" -c \ + owner=$(docker run --rm --network none --user "${user_spec:-0}" --entrypoint /bin/sh "${image}" -c \ 'printf "%s:%s\n" "$(id -u)" "$(id -g)"') || { log_error "Unable to resolve video-worker image user: ${image}" return 1