imbajin commented on code in PR #3119:
URL: https://github.com/apache/hugegraph/pull/3119#discussion_r3666483105


##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -54,28 +199,109 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
 # ── Map env → properties file ─────────────────────────────────────────
 [[ -n "${HG_SERVER_BACKEND:-}"  ]] && set_prop "backend"  
"${HG_SERVER_BACKEND}"  "${GRAPH_CONF}"
 [[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers" 
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+if [[ -n "${HG_SERVER_INIT_STORE_ENABLED:-}" ]]; then
+    # Canonicalize before writing, so the property file only ever holds `true`
+    # or `false` and cannot be read differently by the shell and the server
+    if ! HG_SERVER_INIT_STORE_ENABLED=$(to_bool 
"${HG_SERVER_INIT_STORE_ENABLED}"); then
+        log "ERROR: HG_SERVER_INIT_STORE_ENABLED must be a boolean, got 
'${HG_SERVER_INIT_STORE_ENABLED}'"
+        exit 1
+    fi
+    set_prop "init_store.enabled" "${HG_SERVER_INIT_STORE_ENABLED}" 
"${REST_SERVER_CONF}"
+fi
 
 # ── Build wait-storage env ─────────────────────────────────────────────
 WAIT_ENV=()
 [[ -n "${HG_SERVER_BACKEND:-}"  ]] && 
WAIT_ENV+=("hugegraph.backend=${HG_SERVER_BACKEND}")
 [[ -n "${HG_SERVER_PD_PEERS:-}" ]] && 
WAIT_ENV+=("hugegraph.pd.peers=${HG_SERVER_PD_PEERS}")
 
-# ── Init store (once) ─────────────────────────────────────────────────
-if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
+wait_storage() {
     if (( ${#WAIT_ENV[@]} > 0 )); then
         env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
     else
         ./bin/wait-storage.sh
     fi
+}
+
+# ── Init store (once) ─────────────────────────────────────────────────
+# With `init_store.enabled=false` (distributed PD/HStore) init-store is a 
no-op:
+# storage owns the metadata and the admin account is created on server startup
+# from `auth.admin_pa`. A requested PASSWORD is therefore written to that
+# property rather than piped into init-store.sh, where it would be read and
+# discarded without creating the account.
+#
+# The value is read back from the config file rather than from the env var, so
+# that a rest-server.properties mounted with the property already set behaves
+# the same as `HG_SERVER_INIT_STORE_ENABLED` (the env mapping above has already
+# been applied, so env still wins).
+INIT_STORE_ENABLED=$(get_prop "init_store.enabled" "${REST_SERVER_CONF}")
+if [[ -n "${INIT_STORE_ENABLED}" ]]; then
+    if ! INIT_STORE_ENABLED=$(to_bool "${INIT_STORE_ENABLED}"); then
+        log "ERROR: init_store.enabled in ${REST_SERVER_CONF} must be a 
boolean," \
+            "got '${INIT_STORE_ENABLED}'"
+        exit 1
+    fi
+fi
+if [[ "${INIT_STORE_ENABLED:-true}" == "false" ]]; then
+    log "init-store disabled, skipping local backend/admin init"
+
+    # With init-store skipped, nothing creates the built-in admin account
+    # unless the server takes the PD metadata path, which it only does when
+    # `usePD=true`. Enabling auth without that combination starts a server
+    # that enforces authentication while no account exists, so refuse it here
+    # rather than fail every request later.
+    # Only the built-in authenticator relies on that account: any other
+    # implementation class keeps its identities outside HugeGraph, so requiring
+    # usePD for it would reject a deployment that works
+    AUTH_REQUESTED=""
+    [[ -n "${PASSWORD:-}" ]] && AUTH_REQUESTED=1

Review Comment:
   ‼️ Docker and Java still disagree on which authenticator needs the built-in 
admin. This branch treats any `PASSWORD` as requiring PD even when the 
configured authenticator is custom, while line 257 recognizes only the exact 
`StandardAuthenticator` name and misses subclasses; 
`InitStore.requiresBuiltinAdmin()` instead uses 
`StandardAuthenticator.class.isAssignableFrom()`, and its test explicitly 
covers a subclass. Thus custom auth plus an inherited `PASSWORD` is rejected 
unnecessarily, while a `StandardAuthenticator` subclass with no password can 
start without `usePD` and without any admin bootstrap. Please derive the 
effective authenticator first and share the Java type-aware decision with this 
entrypoint, then add regressions for custom auth plus `PASSWORD` and a built-in 
subclass.



##########
hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/cmd/InitStore.java:
##########
@@ -83,6 +121,61 @@ public static void main(String[] args) throws Exception {
         }
     }
 
+    /**
+     * Skipping means init-store does not create the built-in admin account,
+     * and the only other component that creates it is
+     * GraphManager.initAdminUserIfNeeded(), reached from loadMetaFromPD() and
+     * so gated on 'usePD'. Failing here rather than returning zero keeps
+     * tarball and init-job callers, which see only the exit status, from
+     * continuing into a server that enforces authentication with no account
+     * to authenticate against.
+     * <p>
+     * Remote auth is exempt: the auth manager is then an RPC client, and
+     * StandardAuthenticator only bootstraps an admin for a local one. So is
+     * any authenticator other than the built-in one, which keeps its
+     * identities outside HugeGraph and needs no such account.
+     */
+    private static void checkAdminBootstrapReachable(HugeConfig conf,
+                                                     String restConf) {
+        if (!requiresBuiltinAdmin(conf.get(ServerOptions.AUTHENTICATOR)) ||
+            conf.get(ServerOptions.USE_PD) ||

Review Comment:
   ‼️ `usePD=true` does not by itself guarantee that the authenticator's admin 
is created. `GraphManager.initAdminUserIfNeeded()` writes through 
`MetaManager`, but `StandardAuthenticator` authenticates against the configured 
auth graph; a non-HStore graph uses `StandardAuthManager`, whereas only an 
HStore graph uses the PD-backed `StandardAuthManagerV2`. Consequently 
`init_store.enabled=false + usePD=true + StandardAuthenticator` with a RocksDB 
or memory auth graph passes this guard while the real auth store still has no 
admin. Please gate this exemption on a PD-backed/HStore auth manager, or 
bootstrap through the authenticator's actual `AuthManager`, and cover 
`usePD=true` with a non-HStore auth graph.



##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -54,28 +199,109 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
 # ── Map env → properties file ─────────────────────────────────────────
 [[ -n "${HG_SERVER_BACKEND:-}"  ]] && set_prop "backend"  
"${HG_SERVER_BACKEND}"  "${GRAPH_CONF}"
 [[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers" 
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+if [[ -n "${HG_SERVER_INIT_STORE_ENABLED:-}" ]]; then
+    # Canonicalize before writing, so the property file only ever holds `true`
+    # or `false` and cannot be read differently by the shell and the server
+    if ! HG_SERVER_INIT_STORE_ENABLED=$(to_bool 
"${HG_SERVER_INIT_STORE_ENABLED}"); then
+        log "ERROR: HG_SERVER_INIT_STORE_ENABLED must be a boolean, got 
'${HG_SERVER_INIT_STORE_ENABLED}'"
+        exit 1
+    fi
+    set_prop "init_store.enabled" "${HG_SERVER_INIT_STORE_ENABLED}" 
"${REST_SERVER_CONF}"
+fi
 
 # ── Build wait-storage env ─────────────────────────────────────────────
 WAIT_ENV=()
 [[ -n "${HG_SERVER_BACKEND:-}"  ]] && 
WAIT_ENV+=("hugegraph.backend=${HG_SERVER_BACKEND}")
 [[ -n "${HG_SERVER_PD_PEERS:-}" ]] && 
WAIT_ENV+=("hugegraph.pd.peers=${HG_SERVER_PD_PEERS}")
 
-# ── Init store (once) ─────────────────────────────────────────────────
-if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
+wait_storage() {
     if (( ${#WAIT_ENV[@]} > 0 )); then
         env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
     else
         ./bin/wait-storage.sh
     fi
+}
+
+# ── Init store (once) ─────────────────────────────────────────────────
+# With `init_store.enabled=false` (distributed PD/HStore) init-store is a 
no-op:
+# storage owns the metadata and the admin account is created on server startup
+# from `auth.admin_pa`. A requested PASSWORD is therefore written to that
+# property rather than piped into init-store.sh, where it would be read and
+# discarded without creating the account.
+#
+# The value is read back from the config file rather than from the env var, so
+# that a rest-server.properties mounted with the property already set behaves
+# the same as `HG_SERVER_INIT_STORE_ENABLED` (the env mapping above has already
+# been applied, so env still wins).
+INIT_STORE_ENABLED=$(get_prop "init_store.enabled" "${REST_SERVER_CONF}")
+if [[ -n "${INIT_STORE_ENABLED}" ]]; then
+    if ! INIT_STORE_ENABLED=$(to_bool "${INIT_STORE_ENABLED}"); then
+        log "ERROR: init_store.enabled in ${REST_SERVER_CONF} must be a 
boolean," \
+            "got '${INIT_STORE_ENABLED}'"
+        exit 1
+    fi
+fi
+if [[ "${INIT_STORE_ENABLED:-true}" == "false" ]]; then
+    log "init-store disabled, skipping local backend/admin init"
+
+    # With init-store skipped, nothing creates the built-in admin account
+    # unless the server takes the PD metadata path, which it only does when
+    # `usePD=true`. Enabling auth without that combination starts a server
+    # that enforces authentication while no account exists, so refuse it here
+    # rather than fail every request later.
+    # Only the built-in authenticator relies on that account: any other
+    # implementation class keeps its identities outside HugeGraph, so requiring
+    # usePD for it would reject a deployment that works
+    AUTH_REQUESTED=""
+    [[ -n "${PASSWORD:-}" ]] && AUTH_REQUESTED=1
+    [[ "$(get_prop "auth.authenticator" "${REST_SERVER_CONF}")" == \
+       "${BUILTIN_AUTHENTICATOR}" ]] && AUTH_REQUESTED=1
+    # Remote auth delegates to another service and has no local admin to
+    # create, so it is exempt from the requirement below
+    [[ -n "$(get_prop "auth.remote_url" "${REST_SERVER_CONF}")" ]] && 
AUTH_REQUESTED=""
+    if [[ -n "${AUTH_REQUESTED}" ]]; then
+        USE_PD=$(to_bool "$(get_prop "usePD" "${REST_SERVER_CONF}")" 
2>/dev/null || echo "false")
+        if [[ "${USE_PD}" != "true" ]]; then
+            log "ERROR: auth is enabled and init_store.enabled=false, but 
usePD is not true."
+            log "ERROR: With init-store skipped the admin account is only 
created on the PD"
+            log "ERROR: metadata path, so this combination would start a 
server that nobody"
+            log "ERROR: can authenticate against."
+            log "ERROR: Set usePD=true in ${REST_SERVER_CONF}, or leave 
init-store enabled"
+            log "ERROR: so that it can create the admin account locally."
+            exit 1
+        fi
+    fi
+
+    # Still wait: the server needs the storage side reachable at startup even
+    # though nothing is initialized here
+    wait_storage
+
+    if [[ -n "${PASSWORD:-}" ]]; then
+        log "enabling auth mode, admin password applied via auth.admin_pa"
+        ensure_auth_enabled
+        # TODO: auth.admin_pa only applies when the admin account is first
+        # created, so changing PASSWORD on a later restart silently keeps the
+        # old one. It also leaves the password at rest in 
rest-server.properties,
+        # unlike the enabled path where it only travels over stdin.
+        set_prop "auth.admin_pa" "$(props_escape "${PASSWORD}")" 
"${REST_SERVER_CONF}"

Review Comment:
   ⚠️ This new persistence path writes the admin password into the shipped 
`rest-server.properties`, whose repository mode is `0644`. `set_prop()` 
preserves the destination mode; only its temporary file is protected by `umask 
077`, and the mode test first changes the fixture to `0600`, so it misses the 
default exposure. Please avoid storing the secret in this file or restrict the 
destination permissions before writing `auth.admin_pa`, and test the shipped 
default mode.



##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -54,28 +199,109 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
 # ── Map env → properties file ─────────────────────────────────────────
 [[ -n "${HG_SERVER_BACKEND:-}"  ]] && set_prop "backend"  
"${HG_SERVER_BACKEND}"  "${GRAPH_CONF}"
 [[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers" 
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+if [[ -n "${HG_SERVER_INIT_STORE_ENABLED:-}" ]]; then
+    # Canonicalize before writing, so the property file only ever holds `true`
+    # or `false` and cannot be read differently by the shell and the server
+    if ! HG_SERVER_INIT_STORE_ENABLED=$(to_bool 
"${HG_SERVER_INIT_STORE_ENABLED}"); then
+        log "ERROR: HG_SERVER_INIT_STORE_ENABLED must be a boolean, got 
'${HG_SERVER_INIT_STORE_ENABLED}'"
+        exit 1
+    fi
+    set_prop "init_store.enabled" "${HG_SERVER_INIT_STORE_ENABLED}" 
"${REST_SERVER_CONF}"
+fi
 
 # ── Build wait-storage env ─────────────────────────────────────────────
 WAIT_ENV=()
 [[ -n "${HG_SERVER_BACKEND:-}"  ]] && 
WAIT_ENV+=("hugegraph.backend=${HG_SERVER_BACKEND}")
 [[ -n "${HG_SERVER_PD_PEERS:-}" ]] && 
WAIT_ENV+=("hugegraph.pd.peers=${HG_SERVER_PD_PEERS}")
 
-# ── Init store (once) ─────────────────────────────────────────────────
-if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
+wait_storage() {
     if (( ${#WAIT_ENV[@]} > 0 )); then
         env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
     else
         ./bin/wait-storage.sh
     fi
+}
+
+# ── Init store (once) ─────────────────────────────────────────────────
+# With `init_store.enabled=false` (distributed PD/HStore) init-store is a 
no-op:
+# storage owns the metadata and the admin account is created on server startup
+# from `auth.admin_pa`. A requested PASSWORD is therefore written to that
+# property rather than piped into init-store.sh, where it would be read and
+# discarded without creating the account.
+#
+# The value is read back from the config file rather than from the env var, so
+# that a rest-server.properties mounted with the property already set behaves
+# the same as `HG_SERVER_INIT_STORE_ENABLED` (the env mapping above has already
+# been applied, so env still wins).
+INIT_STORE_ENABLED=$(get_prop "init_store.enabled" "${REST_SERVER_CONF}")
+if [[ -n "${INIT_STORE_ENABLED}" ]]; then
+    if ! INIT_STORE_ENABLED=$(to_bool "${INIT_STORE_ENABLED}"); then
+        log "ERROR: init_store.enabled in ${REST_SERVER_CONF} must be a 
boolean," \
+            "got '${INIT_STORE_ENABLED}'"
+        exit 1
+    fi
+fi
+if [[ "${INIT_STORE_ENABLED:-true}" == "false" ]]; then
+    log "init-store disabled, skipping local backend/admin init"
+
+    # With init-store skipped, nothing creates the built-in admin account
+    # unless the server takes the PD metadata path, which it only does when
+    # `usePD=true`. Enabling auth without that combination starts a server
+    # that enforces authentication while no account exists, so refuse it here
+    # rather than fail every request later.
+    # Only the built-in authenticator relies on that account: any other
+    # implementation class keeps its identities outside HugeGraph, so requiring
+    # usePD for it would reject a deployment that works
+    AUTH_REQUESTED=""
+    [[ -n "${PASSWORD:-}" ]] && AUTH_REQUESTED=1
+    [[ "$(get_prop "auth.authenticator" "${REST_SERVER_CONF}")" == \
+       "${BUILTIN_AUTHENTICATOR}" ]] && AUTH_REQUESTED=1
+    # Remote auth delegates to another service and has no local admin to
+    # create, so it is exempt from the requirement below
+    [[ -n "$(get_prop "auth.remote_url" "${REST_SERVER_CONF}")" ]] && 
AUTH_REQUESTED=""
+    if [[ -n "${AUTH_REQUESTED}" ]]; then
+        USE_PD=$(to_bool "$(get_prop "usePD" "${REST_SERVER_CONF}")" 
2>/dev/null || echo "false")
+        if [[ "${USE_PD}" != "true" ]]; then
+            log "ERROR: auth is enabled and init_store.enabled=false, but 
usePD is not true."
+            log "ERROR: With init-store skipped the admin account is only 
created on the PD"
+            log "ERROR: metadata path, so this combination would start a 
server that nobody"
+            log "ERROR: can authenticate against."
+            log "ERROR: Set usePD=true in ${REST_SERVER_CONF}, or leave 
init-store enabled"
+            log "ERROR: so that it can create the admin account locally."
+            exit 1
+        fi
+    fi
+
+    # Still wait: the server needs the storage side reachable at startup even
+    # though nothing is initialized here
+    wait_storage
+
+    if [[ -n "${PASSWORD:-}" ]]; then

Review Comment:
   ‼️ A mounted authenticator without `PASSWORD` never reaches 
`ensure_auth_enabled()`. The new custom-auth case sets only 
`auth.authenticator`, so the fixture retains no `authentication:` block in 
`gremlin-server.yaml` and leaves `hugegraph.properties` on `HugeFactory`, yet 
the test checks only that the process starts. REST can therefore require auth 
while Gremlin remains unauthenticated and the graph bypasses 
`HugeFactoryAuthProxy`; remote and built-in mounted configurations have the 
same gap. Please complete the Gremlin handler and auth proxy whenever an 
authenticator is configured, keeping only `auth.admin_pa` conditional on 
`PASSWORD`, and assert all generated configs for no-password cases.



##########
PR_DESCRIPTION.md:
##########
@@ -0,0 +1,152 @@
+<!-- Body below is shared by both PRs. Prepend per-repo before posting:

Review Comment:
   🧹 This file is an internal PR-body draft, including instructions to prepend 
different text before posting to two repositories, rather than product 
documentation. Please remove it from the source tree and keep this material in 
the GitHub PR body.



##########
hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh:
##########
@@ -0,0 +1,545 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Smoke tests for docker-entrypoint.sh init-store lifecycle.
+#
+# The entrypoint is run against a throwaway install tree whose ./bin scripts 
are
+# stubs recording their own invocation, so the tests assert on which scripts 
ran
+# and on the resulting config, without needing a JVM, a backend or Docker.
+#
+# Usage: hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh
+
+set -uo pipefail
+
+SELF_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ENTRYPOINT="${SELF_DIR}/../docker-entrypoint.sh"
+
+PASS=0
+FAIL=0
+
+fail() {
+    echo "    FAIL: $*"
+    FAIL=$((FAIL + 1))
+}
+
+ok() {
+    PASS=$((PASS + 1))
+}
+
+assert_ran() {
+    if grep -qxF "$1" "${INSTALL}/calls.log" 2>/dev/null; then
+        ok
+    else
+        fail "expected '$1' to run; calls were: $(tr '\n' ' ' < 
"${INSTALL}/calls.log")"
+    fi
+}
+
+assert_not_ran() {
+    if grep -qxF "$1" "${INSTALL}/calls.log" 2>/dev/null; then
+        fail "expected '$1' NOT to run"
+    else
+        ok
+    fi
+}
+
+assert_file() {
+    if [[ -f "${INSTALL}/$1" ]]; then ok; else fail "expected file '$1' to 
exist"; fi
+}
+
+assert_no_file() {
+    if [[ -f "${INSTALL}/$1" ]]; then fail "expected file '$1' NOT to exist"; 
else ok; fi
+}
+
+assert_prop() {
+    local expected="$1=$2"
+    if grep -qxF "${expected}" "${INSTALL}/conf/rest-server.properties" 
2>/dev/null; then
+        ok
+    else
+        fail "expected property '${expected}' in rest-server.properties"
+    fi
+}
+
+# A scalar option must end up defined exactly once, on any separator, or the
+# properties parser exposes it as a list and a scalar read of it fails
+assert_prop_defined_once() {
+    local n
+    n=$(grep -cE "^[[:space:]]*$1([[:space:]]*[=:]|[[:space:]])" \
+        "${INSTALL}/conf/rest-server.properties" 2>/dev/null || true)
+    if [[ "${n}" == "1" ]]; then ok; else fail "expected '$1' defined once, 
found ${n}"; fi
+}
+
+# Matches the separator set assert_prop_defined_once uses, so a `key:value` or
+# `key value` definition cannot pass as absent
+assert_no_prop_key() {
+    if grep -qE "^[[:space:]]*$1([[:space:]]*[=:]|[[:space:]])" \
+            "${INSTALL}/conf/rest-server.properties" 2>/dev/null; then
+        fail "expected no '$1' property"
+    else
+        ok
+    fi
+}
+
+# Auth is only fully enabled when all three configs agree: the REST properties,
+# the gremlin-server.yaml authentication block and the graph's auth proxy
+assert_auth_fully_enabled() {
+    local n
+    n=$(grep -cE '^[[:space:]]*authentication:' \
+        "${INSTALL}/conf/gremlin-server.yaml" 2>/dev/null || true)
+    if [[ "${n}" == "1" ]]; then
+        ok
+    else
+        fail "expected one gremlin-server.yaml authentication block, found 
${n}"
+    fi
+    if grep -qxF 
"gremlin.graph=org.apache.hugegraph.auth.HugeFactoryAuthProxy" \
+            "${INSTALL}/conf/graphs/hugegraph.properties" 2>/dev/null; then
+        ok
+    else
+        fail "expected hugegraph.properties to use HugeFactoryAuthProxy"
+    fi
+    assert_prop_defined_once "auth.authenticator"
+    assert_prop_defined_once "auth.graph_store"
+}
+
+# Build a throwaway install tree with stubbed bin scripts
+new_install() {
+    INSTALL=$(mktemp -d "${TMPDIR:-/tmp}/hg-entrypoint-test.XXXXXX")
+    mkdir -p "${INSTALL}/bin" "${INSTALL}/conf/graphs"
+
+    # Mirrors the shipped conf: the auth properties are present but commented
+    cat > "${INSTALL}/conf/rest-server.properties" <<'EOF'
+restserver.url=http://0.0.0.0:8080
+graphs=./conf/graphs
+#auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator
+#auth.admin_pa=pa
+EOF
+    cat > "${INSTALL}/conf/graphs/hugegraph.properties" <<'EOF'
+backend=rocksdb
+gremlin.graph=org.apache.hugegraph.HugeFactory
+EOF
+    # Shipped without an authentication block, which is what enable-auth.sh 
adds
+    echo "host: 0.0.0.0" > "${INSTALL}/conf/gremlin-server.yaml"
+
+    local script
+    for script in wait-storage start-hugegraph wait-partition; do
+        cat > "${INSTALL}/bin/${script}.sh" <<EOF
+#!/bin/bash
+echo "${script}.sh" >> "${INSTALL}/calls.log"
+EOF
+        chmod +x "${INSTALL}/bin/${script}.sh"
+    done
+
+    # Records whether a password was piped in, which is how the entrypoint
+    # passes a Docker PASSWORD to the admin bootstrap
+    cat > "${INSTALL}/bin/init-store.sh" <<EOF
+#!/bin/bash
+echo "init-store.sh" >> "${INSTALL}/calls.log"
+if [[ ! -t 0 ]]; then
+    stdin=\$(cat)
+    [[ -n "\${stdin}" ]] && echo "init-store.sh:stdin=\${stdin}" >> 
"${INSTALL}/calls.log"
+fi
+exit 0
+EOF
+    chmod +x "${INSTALL}/bin/init-store.sh"
+
+    # Mirrors bin/enable-auth.sh: appends the REST keys and the YAML
+    # authentication block, and switches the graph to the auth proxy
+    cat > "${INSTALL}/bin/enable-auth.sh" <<EOF
+#!/bin/bash
+echo "enable-auth.sh" >> "${INSTALL}/calls.log"
+{
+    echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator"
+    echo "auth.graph_store=hugegraph"
+} >> "${INSTALL}/conf/rest-server.properties"
+cat >> "${INSTALL}/conf/gremlin-server.yaml" <<'YAML'
+authentication: {
+  authenticator: org.apache.hugegraph.auth.StandardAuthenticator,
+  authenticationHandler: org.apache.hugegraph.auth.WsAndHttpBasicAuthHandler,
+  config: {tokens: conf/rest-server.properties}
+}
+YAML
+sed -i.bak 
's/gremlin.graph=org.apache.hugegraph.HugeFactory/gremlin.graph=org.apache.hugegraph.auth.HugeFactoryAuthProxy/g'
 \
+    "${INSTALL}/conf/graphs/hugegraph.properties"
+rm -f "${INSTALL}/conf/graphs/hugegraph.properties.bak"
+EOF
+    chmod +x "${INSTALL}/bin/enable-auth.sh"
+
+    : > "${INSTALL}/calls.log"
+}
+
+# Auth plus skipped init-store is only supported alongside the PD metadata
+# path, which is what actually creates the admin account in that mode
+enable_pd() {
+    echo "usePD=true" >> "${INSTALL}/conf/rest-server.properties"
+}
+
+# Run the entrypoint inside the throwaway tree. No ./bin/pid is ever written by
+# the stubs, so the entrypoint's tail-on-pid block is skipped and it returns.
+run_entrypoint() {
+    ( cd "${INSTALL}" && env "$@" bash "${ENTRYPOINT}" ) > 
"${INSTALL}/out.log" 2>&1
+    local rc=$?
+    if [[ ${rc} -ne 0 ]]; then
+        fail "entrypoint exited ${rc}; output: $(cat "${INSTALL}/out.log")"
+    fi
+    return 0
+}
+
+cleanup() { [[ -n "${INSTALL:-}" ]] && rm -rf "${INSTALL}"; }
+trap cleanup EXIT
+
+echo "==> default: no flag set, full init runs"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_ran "wait-storage.sh"
+assert_ran "init-store.sh"
+assert_not_ran "enable-auth.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> default + PASSWORD: auth enabled, password piped to init-store"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret
+assert_ran "enable-auth.sh"
+assert_ran "init-store.sh"
+assert_ran "init-store.sh:stdin=s3cret"
+assert_file "docker/init_complete"
+assert_auth_fully_enabled
+cleanup
+
+echo "==> skip via env: init-store never runs and no init flag is written"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "wait-storage.sh"
+assert_not_ran "init-store.sh"
+assert_prop "init_store.enabled" "false"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> skip + PASSWORD: password reaches auth.admin_pa, not init-store 
stdin"
+new_install
+enable_pd
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_ran "enable-auth.sh"
+assert_not_ran "init-store.sh"
+assert_not_ran "init-store.sh:stdin=s3cret"
+assert_prop "auth.admin_pa" "s3cret"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> skip via mounted property only: env var absent behaves the same"
+new_install
+enable_pd
+echo "init_store.enabled=false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret
+assert_not_ran "init-store.sh"
+assert_not_ran "init-store.sh:stdin=s3cret"
+assert_prop "auth.admin_pa" "s3cret"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> env wins over a conflicting mounted property"
+new_install
+echo "init_store.enabled=false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_ran "init-store.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> false then true: a restart with init enabled still initializes"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+: > "${INSTALL}/calls.log"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_ran "init-store.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> restart with init enabled: the flag file suppresses re-init"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_ran "init-store.sh"
+: > "${INSTALL}/calls.log"
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_not_ran "init-store.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> uppercase FALSE is honoured, matching the server's boolean parsing"
+new_install
+enable_pd
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=FALSE PASSWORD=s3cret
+assert_not_ran "init-store.sh"
+assert_not_ran "init-store.sh:stdin=s3cret"
+assert_prop "init_store.enabled" "false"
+assert_prop "auth.admin_pa" "s3cret"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> 'off' and 'no' are honoured too"
+for value in off no; do
+    new_install
+    run_entrypoint -u PASSWORD "HG_SERVER_INIT_STORE_ENABLED=${value}"
+    assert_not_ran "init-store.sh"
+    assert_no_file "docker/init_complete"
+    cleanup
+done
+
+echo "==> a non-boolean value fails fast instead of diverging"
+new_install
+if ( cd "${INSTALL}" && env -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=maybe \
+        bash "${ENTRYPOINT}" ) >/dev/null 2>&1; then
+    fail "expected a non-boolean HG_SERVER_INIT_STORE_ENABLED to fail"
+else
+    ok
+fi
+assert_not_ran "start-hugegraph.sh"
+cleanup
+
+echo "==> mounted property with a ':' separator is honoured"
+new_install
+echo "init_store.enabled:false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> password with backslashes survives the properties round trip"
+new_install
+enable_pd
+run_entrypoint 'PASSWORD=abc\def' HG_SERVER_INIT_STORE_ENABLED=false
+assert_not_ran "init-store.sh"
+# Written escaped, so the properties parser reads back the original `abc\def`
+assert_prop "auth.admin_pa" 'abc\\def'
+cleanup
+
+echo "==> skip + PASSWORD without usePD is refused, not silently started"
+new_install
+if ( cd "${INSTALL}" && env -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret \
+        HG_SERVER_INIT_STORE_ENABLED=false bash "${ENTRYPOINT}" ) >/dev/null 
2>&1; then
+    fail "expected auth + skip without usePD to be refused"
+else
+    ok
+fi
+# Refused before starting the server, and without leaving auth half-enabled
+assert_not_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> skip with auth already in a mounted config, no usePD, is refused too"
+new_install
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+if ( cd "${INSTALL}" && env -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false \
+        bash "${ENTRYPOINT}" ) >/dev/null 2>&1; then
+    fail "expected mounted auth + skip without usePD to be refused"
+else
+    ok
+fi
+assert_not_ran "start-hugegraph.sh"
+cleanup
+
+echo "==> skip without auth is unaffected by the usePD requirement"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> env override of a colon-form property leaves one canonical key"
+new_install
+echo "init_store.enabled:false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_prop_defined_once "init_store.enabled"
+assert_prop "init_store.enabled" "true"
+assert_ran "init-store.sh"
+cleanup
+
+echo "==> env override of a whitespace-form property leaves one canonical key"
+new_install
+echo "init_store.enabled false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_prop_defined_once "init_store.enabled"
+assert_prop "init_store.enabled" "true"
+assert_ran "init-store.sh"
+cleanup
+
+echo "==> PASSWORD override of a colon-form auth.admin_pa leaves one key"
+new_install
+enable_pd
+echo "auth.admin_pa:old" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_prop_defined_once "auth.admin_pa"
+assert_prop "auth.admin_pa" "s3cret"
+cleanup
+
+echo "==> commented-out defaults are not treated as definitions"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret
+# The shipped file ships '#auth.admin_pa=pa' commented; it must stay commented
+# and must not count as an existing definition
+if grep -qxF "#auth.admin_pa=pa" "${INSTALL}/conf/rest-server.properties"; then
+    ok
+else
+    fail "expected the commented '#auth.admin_pa=pa' line to be preserved"
+fi
+cleanup
+
+echo "==> mounted config that already enables auth is not duplicated"
+new_install
+enable_pd
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+echo "auth.graph_store=hugegraph" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_not_ran "enable-auth.sh"
+assert_prop_defined_once "auth.admin_pa"
+assert_prop "auth.admin_pa" "s3cret"
+# The mounted config carried only the REST keys, so the YAML block and the auth
+# proxy still have to be applied, or Gremlin would stay unauthenticated
+assert_auth_fully_enabled
+cleanup
+
+echo "==> mounted config with only auth.authenticator still authenticates 
Gremlin"
+new_install
+enable_pd
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_not_ran "enable-auth.sh"
+assert_auth_fully_enabled
+assert_prop "auth.graph_store" "hugegraph"
+cleanup
+
+echo "==> a gremlin-server.yaml without a trailing newline is still valid"
+new_install
+enable_pd
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+printf 'host: 0.0.0.0' > "${INSTALL}/conf/gremlin-server.yaml"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_auth_fully_enabled
+# The appended block must start on its own line, not glued onto 'host: 0.0.0.0'
+if grep -qxF "host: 0.0.0.0" "${INSTALL}/conf/gremlin-server.yaml"; then
+    ok
+else
+    fail "the last pre-existing line was absorbed by the appended block"
+fi
+cleanup
+
+echo "==> a pre-authenticated mount is completed, not duplicated"
+new_install
+enable_pd
+# Everything already in place, as after a restart with the conf dir mounted
+"${INSTALL}/bin/enable-auth.sh"
+: > "${INSTALL}/calls.log"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_not_ran "enable-auth.sh"
+assert_auth_fully_enabled
+cleanup
+
+echo "==> a custom authenticator is not held to the usePD requirement"
+new_install
+echo "auth.authenticator=org.example.auth.LdapAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+cleanup
+
+echo "==> surrounding whitespace is trimmed, inner whitespace is kept"
+new_install
+printf 'auth.authenticator = %s   \n' \
+    "org.apache.hugegraph.auth.StandardAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+# Trailing spaces must not stop the value matching the built-in class, or the
+# admin-account requirement below would be skipped for a config that needs it
+if ( cd "${INSTALL}" && env -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false \
+        bash "${ENTRYPOINT}" ) >/dev/null 2>&1; then
+    fail "expected a padded auth.authenticator to still be recognized"
+else
+    ok
+fi
+cleanup
+
+echo "==> a value containing spaces survives the round trip"
+new_install
+enable_pd
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false 'PASSWORD=two words'
+assert_prop "auth.admin_pa" "two words"
+assert_prop_defined_once "auth.admin_pa"
+cleanup
+
+echo "==> remote auth is exempt from the usePD requirement"
+new_install
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+    >> "${INSTALL}/conf/rest-server.properties"
+echo "auth.remote_url=127.0.0.1:8899" >> 
"${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+cleanup
+
+echo "==> set_prop preserves the config file's inode and mode"
+new_install
+chmod 600 "${INSTALL}/conf/rest-server.properties"
+before_inode=$(ls -i "${INSTALL}/conf/rest-server.properties" | awk '{print 
$1}')
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+after_inode=$(ls -i "${INSTALL}/conf/rest-server.properties" | awk '{print 
$1}')
+after_mode=$(stat -c '%a' "${INSTALL}/conf/rest-server.properties" 2>/dev/null 
\
+             || stat -f '%Lp' "${INSTALL}/conf/rest-server.properties")
+# Rewriting in place matters: a single-file bind mount cannot be replaced by
+# rename, and a rename would drop the mode protecting auth.admin_pa
+if [[ "${before_inode}" == "${after_inode}" ]]; then ok; else fail "inode 
changed"; fi
+if [[ "${after_mode}" == "600" ]]; then ok; else fail "mode became 
${after_mode}, expected 600"; fi
+assert_prop "init_store.enabled" "false"
+cleanup
+
+echo "==> no scratch file is left behind"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+if compgen -G "${INSTALL}/conf/rest-server.properties.tmp*" >/dev/null; then
+    fail "a set_prop scratch file was left behind"
+else
+    ok
+fi
+cleanup
+
+echo "==> the entrypoint's auth block has not drifted from enable-auth.sh"
+# The entrypoint reapplies what bin/enable-auth.sh would have done when it
+# cannot run the script itself, so the two must name the same classes and keys
+ENABLE_AUTH="${SELF_DIR}/../../src/assembly/static/bin/enable-auth.sh"
+if [[ -f "${ENABLE_AUTH}" ]]; then
+    for token in "authentication: {" "WsAndHttpBasicAuthHandler" \
+                 "tokens: conf/rest-server.properties" "auth.graph_store" \
+                 "HugeFactoryAuthProxy"; do
+        if grep -qF "${token}" "${ENABLE_AUTH}" && \

Review Comment:
   ⚠️ This advertised drift check is one-directional: if `enable-auth.sh` 
removes or replaces one of these fixed tokens, the first `grep` becomes false 
and the test reports success even while the entrypoint retains the stale value. 
Please compare normalized auth blocks bidirectionally, generate both from one 
source, or otherwise assert equality of the relevant authenticator, handler, 
token config, graph-store, and proxy values.



##########
.github/workflows/docker-build-ci.yml:
##########
@@ -26,8 +26,19 @@ on:
     paths:
       - '**/Dockerfile*'
       - '.dockerignore'
+      - 'hugegraph-server/hugegraph-dist/docker/**'

Review Comment:
   ⚠️ This is the only workflow running `test-docker-entrypoint.sh`, but its PR 
path filter omits 
`hugegraph-server/hugegraph-dist/src/assembly/static/bin/enable-auth.sh`. The 
new smoke test explicitly treats that script as the source that the 
entrypoint's inlined auth configuration must track, so a PR changing only the 
source script skips the guard. Please add `enable-auth.sh` or its containing 
bin directory to `pull_request.paths`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to