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]

Reply via email to