Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a7b81ce
fix(hugegraph-dist): skip init-store when graph.load_from_local_confi…
bitflicker64 Jul 26, 2026
69ac25e
fix(hugegraph-dist): gate init-store on a dedicated init_store.enable…
bitflicker64 Jul 26, 2026
e698202
style(hugegraph-dist): avoid em dashes in InitStore comment
bitflicker64 Jul 26, 2026
a62f6ff
fix(hugegraph-dist): refuse auth with init-store skipped unless usePD…
bitflicker64 Jul 27, 2026
3cb4fda
fix(hugegraph-dist): make set_prop match the separators get_prop accepts
bitflicker64 Jul 27, 2026
99d7de8
fix(hugegraph-dist): fail non-zero on unusable skip config, rewrite c…
bitflicker64 Jul 28, 2026
0dd5a7a
fix(hugegraph-dist): keep auth enablement complete and limit the admi…
bitflicker64 Jul 28, 2026
090763a
fix(hugegraph-dist): self-review follow-ups on the init-store skip path
bitflicker64 Jul 28, 2026
4c9be3a
Merge remote-tracking branch 'hugegraph/master' into fix/no-init
bitflicker64 Jul 28, 2026
95bfe45
fix(hugegraph-dist): harden skipped init authentication
bitflicker64 Jul 29, 2026
4b298bf
fix(hugegraph-dist): fail closed on auth config errors
bitflicker64 Jul 29, 2026
906a3ef
fix(hugegraph-api): always close init auth graph
bitflicker64 Jul 29, 2026
1434969
fix(hugegraph-dist): address init-store review findings
bitflicker64 Jul 30, 2026
3e505a8
fix(hugegraph-dist): fail fast on init-store override writes
bitflicker64 Jul 30, 2026
da85e46
fix(server): harden auth bootstrap upgrades
imbajin Jul 31, 2026
ba6b7e7
refactor(hugegraph-dist): narrow init-store gate to what #3118 asks for
bitflicker64 Aug 1, 2026
5268091
style(hugegraph-dist): address review follow-ups on the init-store gate
bitflicker64 Aug 1, 2026
9707feb
fix(hugegraph-dist): fail closed on the default admin password and re…
bitflicker64 Aug 2, 2026
ff1325a
docs(docker): correct the init_complete description after the marker …
bitflicker64 Aug 2, 2026
edf07d0
fix(server): validate the disabled init path on every startup
bitflicker64 Aug 2, 2026
d45ce1e
fix(dist): harden init marker and config mapping
imbajin Aug 5, 2026
78a2b5c
chore: resolve Docker CI workflow conflict
imbajin Aug 5, 2026
8390c65
fix: harden bootstrap config handling
imbajin Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/docker-build-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ on:
paths:
- '**/Dockerfile*'
- '.dockerignore'
- 'hugegraph-server/hugegraph-dist/docker/**'
- 'hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh'

jobs:
Expand Down Expand Up @@ -53,6 +54,10 @@ jobs:
echo "Healthcheck: $HC"
[[ "$HC" != "null" ]] || { echo "ERROR: HEALTHCHECK missing in ${{ matrix.dockerfile }}"; exit 1; }

- name: Test server entrypoint property mapping
if: matrix.dockerfile == 'hugegraph-server/Dockerfile'
run: bash hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh

# The startup preflight needs a socket-table tool, and the base image
# ships none of its own. Without one every start reports "unknown" and
# a duplicate start is no longer refused, so assert the image can
Expand Down
27 changes: 26 additions & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,32 @@ Configuration is injected via environment variables. The old `docker/configs/app
| `HG_SERVER_BACKEND` | Yes | — | `backend` in `hugegraph.properties` | Storage backend (e.g. `hstore`) |
| `HG_SERVER_PD_PEERS` | Yes | — | `pd.peers` | PD cluster addresses (e.g. `pd0:8686,pd1:8686,pd2:8686`) |
| `STORE_REST` | No | — | Used by `wait-partition.sh` | Store REST endpoint for partition verification (e.g. `store0:8520`) |
| `PASSWORD` | No | — | Enables auth mode | Optional authentication password |
| `PASSWORD` | No | — | Enables auth mode | Optional authentication password; ignored when `HG_SERVER_INIT_STORE_ENABLED` is `false` (see below) |
| `HG_SERVER_INIT_STORE_ENABLED` | No | `true` | `init_store.enabled` in `rest-server.properties` | Set `false` in PD/HStore deployments so init-store skips local backend and admin initialization |

> **The built-in authenticator with `HG_SERVER_INIT_STORE_ENABLED=false`
> requires `usePD=true` and an HStore-backed `auth.graph_store`, unless
> `auth.remote_url` delegates auth elsewhere.** With init-store skipped, the
> server creates the built-in admin in PD metadata, and only an HStore auth
> graph uses the PD-backed auth manager that can read that account. init-store
> exits non-zero when the combination is unusable, rather than leaving a server
> nobody can log in to. A custom `auth.authenticator` is exempt because it
> manages its own identities.
>
> `docker/init_complete` is written by init-store itself, and only after it has
> initialized. A skipped run therefore records nothing, whether it was disabled
> by the variable or by the property in a mounted `rest-server.properties`, so a
> later re-enable is still able to initialize. The marker only short-circuits
> re-initialization: init-store runs on every container start, and a disabled
> one performs the fail-closed check above first, so a marker left by an
> earlier release or an earlier enabled run cannot bypass it.
>
> **`PASSWORD` does not reach that path.** init-store reads it from standard
> input, and a disabled one returns before doing so. The admin is instead
> created from `auth.admin_pa`, whose `pa` default is public, so init-store
> refuses to skip unless it is explicitly set to a non-empty value in a mounted
> `rest-server.properties`. It applies only when the account is first created,
> so changing it later does not rotate an existing password.

**Deprecated aliases** (still work but log a warning):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,17 @@ public class ServerOptions extends OptionHolder {
"./conf/graphs"
);

public static final ConfigOption<Boolean> INIT_STORE_ENABLED =
new ConfigOption<>(
"init_store.enabled",
"Whether init-store initializes the local backend stores " +
"and the built-in admin account. Set false in distributed " +
"deployments (PD/HStore) where the storage side already " +
"owns the metadata.",
disallowEmpty(),
true
);

public static final ConfigOption<Boolean> SERVER_START_IGNORE_SINGLE_GRAPH_ERROR =
new ConfigOption<>(
"server.start_ignore_single_graph_error",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,10 @@ private void loadMetaFromPD() {

this.initMetaManager(conf);
this.initK8sManagerIfNeeded(conf);
this.initAdminUserIfNeeded(conf.get(ServerOptions.ADMIN_PA));
if (shouldBootstrapAdmin(this.authenticator,
conf.get(ServerOptions.AUTH_REMOTE_URL))) {
this.initAdminUserIfNeeded(conf.get(ServerOptions.ADMIN_PA));
}

this.createDefaultGraphSpaceIfNeeded(conf);

Expand All @@ -368,6 +371,19 @@ private void loadMetaFromPD() {
this.listenMetaChanges();
}

private static boolean shouldBootstrapAdmin(HugeAuthenticator authenticator,
String remoteUrl) {
return authenticator instanceof StandardAuthenticator &&
remoteUrl.isEmpty();
}

/**
* Creates the built-in admin account in PD metadata. With init-store
* disabled this is the only bootstrap that admin gets, and init-store's
* fail-closed check assumes it works, so only the already-exists case is
* benign; any other failure aborts startup instead of leaving the server
* without a usable administrator.
*/
public void initAdminUserIfNeeded(String password) {
HugeUser user = new HugeUser("admin");
user.nickname("超级管理员");
Expand All @@ -380,10 +396,29 @@ public void initAdminUserIfNeeded(String password) {
user.create(new Date());
user.avatar("/image.png");
try {
this.metaManager.createUser(user);
try {
this.metaManager.createUser(user);
} catch (Exception e) {
// Judged by re-reading rather than by matching the message:
// benign only if the admin actually exists, from an earlier
// startup or from a concurrent server that won the race
HugeUser existing;
try {
existing = this.metaManager.findUser(user.name());
} catch (Exception probe) {
e.addSuppressed(probe);
throw e;
}
if (existing == null) {
throw e;
}
LOG.info("The built-in admin user already exists, " +
"skip creating it");
}
this.metaManager.initDefaultGraphSpace();
} catch (Exception e) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

‼️ This turns every usePD=true startup into a fail-fast admin bootstrap. loadMetaFromPD() calls initAdminUserIfNeeded() unconditionally at line 358, including auth-disabled, remote-auth, and custom-auth configurations that InitStore.checkAdminBootstrapReachable() explicitly exempts; before this change the same create/default-graph failures were logged and startup continued. A PD write or permission failure in those deployments now prevents startup for an unrelated built-in admin. Please make fatal propagation conditional on the local built-in-auth/disabled-init case, or separate default graph-space initialization from admin bootstrap, and add caller-level tests for no-auth, remote, and custom authenticators.

LOG.info(e.getMessage());
throw new HugeException("Failed to init the built-in admin " +
"user or the default graph space", e);
}
}

Expand Down
59 changes: 51 additions & 8 deletions hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,23 @@ set -euo pipefail
DOCKER_FOLDER="./docker"
INIT_FLAG_FILE="init_complete"
GRAPH_CONF="./conf/graphs/hugegraph.properties"
REST_SERVER_CONF="./conf/rest-server.properties"

mkdir -p "${DOCKER_FOLDER}"

log() { echo "[hugegraph-server-entrypoint] $*"; }

set_prop() {
local key="$1" val="$2" file="$3"
local esc_key esc_val
local esc_key esc_val key_re

esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
esc_val=$(printf '%s' "$val" | sed -e 's/[&|\\]/\\&/g')
esc_val=$(printf '%s' "$val" | sed -e 's/[&|\\~]/\\&/g')
key_re="^[[:space:]]*${esc_key}([[:space:]]*[:=]|[[:space:]]+|[[:space:]]*$)"

if grep -qE "^[[:space:]]*${esc_key}[[:space:]]*=" "${file}"; then
sed -ri "s|^([[:space:]]*${esc_key}[[:space:]]*=).*|\\1${esc_val}|" "${file}"
if grep -qE "${key_re}" "${file}"; then
sed -ri "0,/${key_re}/!{/${key_re}/d;}" "${file}"
sed -ri "0,/${key_re}/s~${key_re}.*~${key}=${esc_val}~" "${file}"
else
printf '%s=%s\n' "$key" "$val" >> "${file}"
fi
Expand All @@ -55,13 +58,39 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
[[ -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}"

# Normalized once here and reused by the init-flag guard below. The accepted
# spellings are the ones HugeConfig accepts, case-insensitive: commons-lang 2.x
# BooleanUtils, reached through commons-configuration 1.x PropertyConverter.
# That set excludes 0 and 1, which commons-lang3 would have taken. Anything
# outside it is rejected now rather than touching the init flag for a value the
# server is going to refuse anyway.
INIT_STORE_ENABLED=$(printf '%s' "${HG_SERVER_INIT_STORE_ENABLED:-}" |
tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
case "${INIT_STORE_ENABLED}" in
"" | y | t | yes | on | true | n | f | no | off | false) ;;
*) log "ERROR: invalid HG_SERVER_INIT_STORE_ENABLED" \
"'${HG_SERVER_INIT_STORE_ENABLED}'"
exit 1 ;;
esac
[[ -n "${INIT_STORE_ENABLED}" ]] && \
set_prop "init_store.enabled" "${INIT_STORE_ENABLED}" "${REST_SERVER_CONF}"

# ── 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
# ── Init store ────────────────────────────────────────────────────────
# init-store owns the marker: it skips re-initialization when the marker is
# present and writes it only after it has actually initialized. Deciding here
# would mean guessing from the environment variable, which says nothing about
# a config mounted with the property already set. Absolute, so the in-Java
# existence check agrees with the guard below no matter where init-store.sh
# leaves its working directory.
INIT_MARKER_PATH="$(cd "${DOCKER_FOLDER}" && pwd)/${INIT_FLAG_FILE}"
export HG_SERVER_INIT_COMPLETE_MARKER="${INIT_MARKER_PATH}"

if [[ ! -f "${INIT_MARKER_PATH}" ]]; then
if (( ${#WAIT_ENV[@]} > 0 )); then
env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
else
Expand All @@ -74,11 +103,25 @@ if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
else
log "init hugegraph with auth mode"
./bin/enable-auth.sh
# init-store reads the password from stdin, and a disabled one returns
# before it gets there, so say plainly that PASSWORD is being dropped
case "${INIT_STORE_ENABLED}" in
n | f | no | off | false)
log "WARN: PASSWORD is ignored while init-store is disabled;" \
"the admin is created on the PD startup path from" \
"'auth.admin_pa', which defaults to the public value 'pa'" ;;
esac
echo "${PASSWORD}" | ./bin/init-store.sh
fi
touch "${DOCKER_FOLDER}/${INIT_FLAG_FILE}"
else
log "HugeGraph initialization already done. Skipping re-init..."
log "HugeGraph initialization already done. Revalidating the config..."
# The marker skips re-initialization inside init-store, not init-store
# itself: a disabled one must pass its fail-closed check on every startup,
# because the marker may predate this configuration or this release and
# says nothing about whether the admin the current config relies on is
# reachable. An enabled one returns at the marker, before it touches the
# backend or reads stdin, so neither wait-storage nor PASSWORD is needed.
./bin/init-store.sh
fi

./bin/start-hugegraph.sh -j "${JAVA_OPTS:-}" -t 120
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/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.

set -euo pipefail

entrypoint="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/docker-entrypoint.sh"
test_dir="$(mktemp -d)"
trap 'rm -rf "${test_dir}"' EXIT

eval "$(awk '
/^set_prop\(\) \{/ { capture = 1 }
capture { print }
capture && /^\}$/ { exit }
' "${entrypoint}")"

assert_replaced() {
local separator="$1"
local file="${test_dir}/config-${separator// /space}"

printf 'init_store.enabled%sfalse\n' "${separator}" > "${file}"
set_prop "init_store.enabled" "true" "${file}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 The added test executes GNU-only sed -ri through set_prop; on macOS/BSD sed it aborts before any assertion (sed: ... invalid command code ...). The previous GNU-sed guard is absent in this head. Use a portable edit command or declare and enforce a Linux-only test contract so the new regression suite is runnable on supported development hosts.

[[ "$(grep -Ec '^init_store\.enabled=true$' "${file}")" -eq 1 ]]
}

assert_line_count() {
local expected="$1" pattern="$2" file="$3"
local actual

actual=$(grep -Ec "${pattern}" "${file}")
if [[ "${actual}" -ne "${expected}" ]]; then
echo "expected ${expected} matching lines, got ${actual}" >&2
return 1
fi
}

assert_replaced "="
assert_replaced ": "
assert_replaced " "

duplicate_file="${test_dir}/config-duplicates"
printf '%s\n' \
'init_store.enabled=false' \
'init_store.enabled: false' \
'init_store.enabled false' \
'init_store.enabled' \
'unrelated=true' > "${duplicate_file}"
set_prop "init_store.enabled" "true" "${duplicate_file}"
assert_line_count 1 \
'^[[:space:]]*init_store\.enabled([[:space:]]*[:=]|[[:space:]]+|[[:space:]]*$)' \
"${duplicate_file}"
assert_line_count 1 '^init_store\.enabled=true$' "${duplicate_file}"
grep -q '^unrelated=true$' "${duplicate_file}"
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,9 @@ CP="$CP":$(find -L "${LIB}" -name '*.jar' \! -name 'hugegraph*' | sort | tr '\n'
CP="$CP":$(find -L "${PLUGINS}" -name '*.jar' | sort | tr '\n' ':')
$JAVA -cp $CP ${DEFAULT_JAVA_OPTIONS} \
org.apache.hugegraph.cmd.InitStore "${CONF}"/rest-server.properties
INIT_STORE_STATUS=$?
if [[ ${INIT_STORE_STATUS} -ne 0 ]]; then
exit "${INIT_STORE_STATUS}"
fi

echo "Initialization finished."
Loading
Loading