Repository navigation
[BUG] Standalone mode is unusable on Windows: fileKey() is always null and the owner-only ACL omits the rights needed to repair itself #4400
Description
Is there an existing issue for this?
- I have searched the existing issues
Current Behavior
Standalone mode (org.apache.hertzbeat.startup.HertzBeatApplication) cannot run on Windows 11. Two defects exist in the setup-file security layer, and both of them are hit during a normal start.
Symptom A — fatal failure before Spring starts. The process dies immediately with a message whose cause is discarded:
Exception in thread "main" org.apache.hertzbeat.startup.runtime.StandaloneDeploymentOwnerException: Standalone deployment ownership is unavailable
at org.apache.hertzbeat.startup.runtime.StandaloneDeploymentOwnerException.unavailable(StandaloneDeploymentOwnerException.java:18)
at org.apache.hertzbeat.startup.runtime.StandaloneDeploymentOwner.acquire(StandaloneDeploymentOwner.java:78)
at org.apache.hertzbeat.startup.runtime.HertzBeatStartupCoordinator.acquireDeploymentOwner(HertzBeatStartupCoordinator.java:327)
at org.apache.hertzbeat.startup.runtime.HertzBeatStartupCoordinator.start(HertzBeatStartupCoordinator.java:105)
at org.apache.hertzbeat.startup.HertzBeatApplication.main(HertzBeatApplication.java:74)
Symptom B — silent degradation into an unrecoverable recovery_required state. After making the identity check non-fatal, the process starts but is permanently stuck in RECOVERY mode. GET /api/setup/status returns:
<StatusResponse>
<phase>recovery_required</phase>
<access>local</access>
<applyMode>managed_write</applyMode>
<writableManagedConfig>true</writableManagedConfig>
<errorCode>config_recovery_required</errorCode>
<managementDatabase><kind>h2</kind><configured>false</configured><source>built_in_default</source></managementDatabase>
</StatusResponse>There is nothing to recover: data/config/ contains only lock files at this point.
.factory-reset.lock
.metadata-migration-operations.lock
.setup-transition-intent.lock
.standalone-deployment-owner.lock
The web UI shows only a read-only, button-less "The configuration needs to be recovered. The current server state has been preserved; follow the server recovery instructions." panel, and every product route is redirected back to /setup. No configuration property can escape this state — see the reproduction step 5.
Expected Behavior
- Standalone mode should start on Windows. The surrounding pre-flight checks clearly intend Windows to be supported:
StandaloneFileStorePolicyexplicitly allowsntfs, and root resolution, secure file creation and owner-only ACL enforcement all succeed before the failure. - If Windows is genuinely out of scope, startup should fail fast with a clear, actionable message instead of an exception whose cause is discarded.
- A failed lock/identity check must not be silently reported as "a configuration recovery is required". The current behaviour sends users hunting for a recovery procedure that does not exist, and no configuration property can bypass it.
Steps To Reproduce
- On Windows 11 (NTFS), build:
mvnw.cmd -DskipTests clean install - Run
org.apache.hertzbeat.startup.HertzBeatApplication(IDE run configuration or the packaged distribution) with the project root as the working directory - Observe the immediate
StandaloneDeploymentOwnerExceptionfrom Symptom A. Note thatdata/config/.standalone-deployment-owner.lockis created successfully before the failure, so steps 1–2 ofacquire()pass and only the file-identity check fails - (Optional, to reach Symptom B) Make the identity check non-fatal, then start again: the process now starts but
GET /api/setup/statusreportsphase=recovery_required, and the UI redirects every route to a read-only/setuppage - (Optional, to confirm there is no escape hatch) Start with
--hertzbeat.startup.mode=normal(or-Dhertzbeat.startup.mode=normal, orHERTZBEAT_STARTUP_MODE=normal). The property has no effect, because the migration preflight branch ofHertzBeatStartupCoordinator.startupPlan()is taken and never calls the installation probe
Environment
HertzBeat version(s): 2.0-SNAPSHOT (branch 2.0.0, commit 40e7485be "Stabilize migration recovery timing test")
OS: Windows 11 (NTFS)
JDK: Eclipse Temurin 26.0.2.1 (used for the reproduction; the fileKey() limitation is a Windows platform behaviour, not JDK-version-specific)
Entry point: org.apache.hertzbeat.startup.HertzBeatApplication (standalone)
Working directory: project root (installation root); data/config/ is created there
Database: never reached — the failure happens before the Spring context startsDebug logs
1. BasicFileAttributes.fileKey() is always null on Windows. Minimal probe on the affected machine:
JVM = 26.0.2.1
OS = Windows 11
store = NTFS
rootKey = null
lockKey = null
2. The exception cause is discarded — StandaloneDeploymentOwner.acquire(...) replaces the real IOException with StandaloneDeploymentOwnerException.unavailable(). There is no initCause and no log line, so the actual reason ("identity is unavailable") is not visible to the user or to a log collector. The same static factory is also used for the genuinely different "another process holds the lock" case (FileLock == null), so the message cannot distinguish a lost race from a platform limitation.
3. data/config/ after the failed start (lock file created, then startup aborted):
.factory-reset.lock
.metadata-migration-operations.lock
.setup-transition-intent.lock
.standalone-deployment-owner.lock
4. The migration-preflight failure is swallowed with no diagnostics. Once the identity check is non-fatal, the lock handshake in SecureSetupFileLock fails instead, and the exception disappears entirely:
SecureSetupFileLock.execute(...)→initializeAndValidate()throwsIOExceptionFileMigrationOperationStore.locked(...)converts anyIOExceptionintoMigrationOperationStoreException(CONFIG_RECOVERY_REQUIRED)ManagedMigrationStartupRecoverySession.selectOnce()catches it and returnsGATED_RECOVERYwithout loggingHertzBeatStartupCoordinator.startupPlan()mapsGATED_RECOVERYtoStartupDecision.recovery()SetupRuntimeStateFactory.phase()returnsRECOVERY_REQUIREDformode == RECOVERY
StartupFailureReporter is not invoked on this path, and even when it is, it logs only the exception class name, never the message or cause.
5. Existing test evidence that the startup property is bypassed. HertzBeatStartupMigrationPreflightFlowTest passes --hertzbeat.startup.mode=normal while the preflight returns GATED_RECOVERY and asserts that the probe is never called:
coordinator.start(new String[] {
"--" + SetupInstallationPaths.ROOT_PROPERTY + "=" + installationRoot,
"--" + StartupModePropertyProbe.PROPERTY_NAME + "=" + RuntimeMode.NORMAL.value()
});
assertEquals(RuntimeMode.RECOVERY, coordinator.mode());
...
assertEquals(0, probes.get());6. Only clean workaround found: delete the data/ directory (four lock files plus a half-initialised H2 file) and restart.
Anything else?
Affected files
| File | Defect |
|---|---|
hertzbeat-startup/.../runtime/StandaloneDeploymentOwner.java |
Strict fileKey() identity check always fails on Windows; the resulting exception hides its cause; the immediate reopen of the just-created lock file has no retry against Windows AV/indexer deny-share handles; a stale lock file is never repaired |
hertzbeat-manager/.../setup/security/SecureSetupFileLock.java |
Same fileKey() defect (thrown instead of tolerated); the same reopen-without-retry pattern; no DACL re-enforcement for an existing lock file; failures are swallowed upstream into recovery_required |
hertzbeat-manager/.../setup/security/OwnerOnlyFilePermissions.java |
The written ACL_PERMISSIONS set omits WRITE_ACL and the extended-attribute rights, so the ACL cannot be re-applied to the very file it protects (self-inflicted lockout) |
Root cause 1 — fileKey() is unusable on Windows
// StandaloneDeploymentOwner
private static Object fileKey(Path path) throws IOException {
Object key = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).fileKey();
if (key == null) {
throw new IOException("Standalone deployment owner identity is unavailable");
}
return key;
}
// SecureSetupFileLock
private Object readPathFileKey() throws IOException {
BasicFileAttributes attributes = Files.readAttributes(
lockFile, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
if (attributes.fileKey() == null) {
throw new IOException("Secure setup-file lock identity is unavailable");
}
return attributes.fileKey();
}BasicFileAttributes.fileKey() returns null on Windows — a long-standing JDK platform behaviour (no file identity key is provided by the Windows provider). Every caller that relies on it therefore fails closed on this platform.
Root cause 2 — the ACL written at creation forbids repairing that same ACL
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTo do
Activity
[bugfix] Compatible with Windows native file locking and permission h…
xdn-code commentedon Oct 4, 2026
Hi @StreamPlume, thank you for the detailed report, and for tracing the failure to
fileKey()and the owner-only ACL.I would like to take this on, if that is all right. I will work from the current
2.0.0branch and:fileKey()inStandaloneDeploymentOwnerandSecureSetupFileLockIOExceptioncause instead of discarding itWRITE_ACLsoOwnerOnlyFilePermissionscan re-apply the ACLI will add unit tests and verify standalone startup on Windows before opening the PR. @Duansg, please let me know if this overlaps with anything already in progress.
[bugfix] Make standalone startup work on Windows NTFS (#4400) (#4419)