StreamPlume opened a new issue, #4400:
URL: https://github.com/apache/hertzbeat/issues/4400
### Is there an existing issue for this?
- [x] 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:
```xml
<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: `StandaloneFileStorePolicy` explicitly
allows `ntfs`, 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
1. On Windows 11 (NTFS), build: `mvnw.cmd -DskipTests clean install`
2. Run `org.apache.hertzbeat.startup.HertzBeatApplication` (IDE run
configuration or the packaged distribution) with the project root as the
working directory
3. Observe the immediate `StandaloneDeploymentOwnerException` from Symptom
A. Note that `data/config/.standalone-deployment-owner.lock` **is** created
successfully before the failure, so steps 1–2 of `acquire()` pass and only the
file-identity check fails
4. (Optional, to reach Symptom B) Make the identity check non-fatal, then
start again: the process now starts but `GET /api/setup/status` reports
`phase=recovery_required`, and the UI redirects every route to a read-only
`/setup` page
5. (Optional, to confirm there is no escape hatch) Start with
`--hertzbeat.startup.mode=normal` (or `-Dhertzbeat.startup.mode=normal`, or
`HERTZBEAT_STARTUP_MODE=normal`). The property has no effect, because the
migration preflight branch of `HertzBeatStartupCoordinator.startupPlan()` is
taken and never calls the installation probe
### Environment
```markdown
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
starts
```
### Debug 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()` throws
`IOException`
- `FileMigrationOperationStore.locked(...)` converts any `IOException` into
`MigrationOperationStoreException(CONFIG_RECOVERY_REQUIRED)`
- `ManagedMigrationStartupRecoverySession.selectOnce()` catches it and
returns `GATED_RECOVERY` **without logging**
- `HertzBeatStartupCoordinator.startupPlan()` maps `GATED_RECOVERY` to
`StartupDecision.recovery()`
- `SetupRuntimeStateFactory.phase()` returns `RECOVERY_REQUIRED` for `mode
== 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:
```java
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
```java
// 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
--
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]