fix(core): boot embedded Postgres under non-admin user on elevated Windows#2117
Conversation
…ndows GitHub windows-latest and "Run as administrator" launches hold an elevated admin token; PostgreSQL refuses to start as that user. On elevation, initdb still runs as the launcher, but the server process is started as a dedicated non-admin local account (Start-Process -Credential) so CI smoke and elevated desktop Local mode can open the cluster. Also raise real-process vitest timeouts on Windows so the 15s package default does not kill start() while the non-admin postmaster is still coming up.
|
Warning Review limit reached
Next review available in: 15 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a Windows elevated-admin path that starts embedded PostgreSQL under a dedicated non-admin account, integrates handle-based lifecycle shutdown and maintenance SQL provisioning, and adds elevation tests, Windows-specific integration timeouts, and release documentation. ChangesWindows elevated embedded PostgreSQL
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Fusion
participant EmbeddedPostgresLifecycle
participant WindowsAdminStartup
participant PowerShell
participant PostgreSQL
Fusion->>EmbeddedPostgresLifecycle: start embedded database
EmbeddedPostgresLifecycle->>WindowsAdminStartup: detect elevation and start server
WindowsAdminStartup->>PowerShell: Start-Process -Credential
PowerShell->>PostgreSQL: launch postgres.exe as fusion-pg
PostgreSQL-->>WindowsAdminStartup: readiness log and TCP availability
WindowsAdminStartup-->>EmbeddedPostgresLifecycle: return server handle
EmbeddedPostgresLifecycle->>EmbeddedPostgresLifecycle: provision database via maintenance SQL
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR enables embedded PostgreSQL startup when Fusion runs elevated on Windows. The main changes are:
Confidence Score: 4/5This is close, but the localized Windows failure should be fixed before merging.
packages/core/src/postgres/embedded-windows-admin.ts Important Files Changed
Reviews (7): Last reviewed commit: "fix(core): raise Windows embedded PG rea..." | Re-trigger Greptile |
After the elevated non-admin server reported ready, ensureDatabase still called embedded-postgres.createDatabase(), which requires this.process from .its own start() and never ran under the non-admin path. Connection helpers also used localhost (IPv6-first on Windows). - Ensure DB via postgres.js on 127.0.0.1 with a 10s connect timeout - Prefer 127.0.0.1 in connection URLs on Windows - TCP-probe 127.0.0.1 after the ready log before returning from non-admin start
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
packages/core/src/postgres/embedded-windows-admin.ts (1)
81-98: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPassword has a fixed prefix/suffix, weakening effective entropy.
generatePassword()always appends the literalFx9!prefix and#kPsuffix around the randomized middle, so 7 of the ~29 characters are constant/known across every generated password for this local account. Consider guaranteeing the four complexity classes by interleaving/shuffling required characters into random positions instead of a fixed affix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/postgres/embedded-windows-admin.ts` around lines 81 - 98, Update generatePassword() to remove the fixed `Fx9!` prefix and `#kP` suffix. Generate the full password from randomized characters, explicitly include at least one uppercase letter, lowercase letter, digit, and symbol, then shuffle those required characters into random positions before returning a 29-character password.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/postgres/embedded-lifecycle.ts`:
- Around line 1048-1070: Make the elevated-Windows startup path cancellable
during readiness polling. Update startServerAsNonAdminUser and its caller in
startInternal so the wrapper process/PID is tracked before awaiting readiness
and is terminated by the existing timeout/failure cleanup path; ensure late
readiness cannot register a server after startup has already failed.
In `@packages/core/src/postgres/embedded-windows-admin.ts`:
- Around line 405-413: Update the startup return path around readPostgresPid so
a missing postmaster.pid is surfaced explicitly instead of assigning wrapperPid
to postgresPid. Preserve the documented contract that postgresPid identifies the
actual PostgreSQL server, either by failing startup through the existing onError
flow or by making the field nullable and handling that state at callers; do not
silently substitute wrapperPid.
- Around line 109-127: Remove the generated password from process arguments at
both affected sites in embedded-windows-admin.ts: lines 109-127, update the net
user creation and reset calls to use the interactive password form and provide
the password through spawnSync input; lines 320-339, update the launch.ps1 flow
to read the password from stdin instead of a -Password parameter and pass it
through spawnSync input. Preserve the existing account and launcher behavior
without exposing the secret in command-line arguments.
- Around line 262-286: Update the launch.bat command construction around args,
argStr, and the generated pgExe invocation to safely handle arbitrary executable
arguments containing cmd.exe-sensitive characters such as quotes and percent
signs. Either reject invalid shell-sensitive values before writing the script or
apply correct cmd.exe escaping, while preserving valid PostgreSQL flags and
paths.
---
Nitpick comments:
In `@packages/core/src/postgres/embedded-windows-admin.ts`:
- Around line 81-98: Update generatePassword() to remove the fixed `Fx9!` prefix
and `#kP` suffix. Generate the full password from randomized characters,
explicitly include at least one uppercase letter, lowercase letter, digit, and
symbol, then shuffle those required characters into random positions before
returning a 29-character password.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2f70e71e-2c06-4aa9-9c8a-93f8395d39e0
📒 Files selected for processing (4)
.changeset/win-embedded-pg-admin.mdpackages/core/src/__tests__/postgres/embedded-lifecycle.test.tspackages/core/src/postgres/embedded-lifecycle.tspackages/core/src/postgres/embedded-windows-admin.ts
Reuse starts failed on elevated Windows because wrapper.log was appended across launches; a prior taskkill left exit=1 in the log and the readiness poll treated that as an immediate failure before the new postmaster came up.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/core/src/postgres/embedded-lifecycle.ts (1)
1065-1073: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy liftDangling promise leaves orphaned PostgreSQL processes on timeout.
This is a duplicate of a previously raised issue. The elevated Windows startup path still leaves a running PostgreSQL process behind if
start()times out first. BecausestartServerAsNonAdminUser()only exposes a stop handle after it completes readiness polling, the outer timeout cleanup path cannot kill the wrapper while it is still waiting. When the helper later achieves readiness,startInternal()resumes on an already-failed lifecycle instance, overwritesthis.nonAdminHandle, and registers a live server that nobody will stop.Track the wrapper process PID before awaiting readiness, or pass an
AbortSignalto make the helper cancellable during startup.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/postgres/embedded-lifecycle.ts` around lines 1065 - 1073, Update the elevated Windows startup flow around startServerAsNonAdminUser and startInternal so the wrapper process can be terminated when start() times out before readiness completes. Expose and track the wrapper PID immediately, or make the helper accept and honor an AbortSignal; ensure timeout cleanup cancels/stops it and prevent late readiness from assigning nonAdminHandle or registering a server on the failed lifecycle.
🧹 Nitpick comments (1)
packages/core/src/postgres/embedded-lifecycle.ts (1)
1125-1125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnnecessary
awaiton synchronous function call.
openMaintenanceSql()synchronously returns thepostgrestagged template function; it does not return a Promise. Theawaitkeyword at these call sites is unnecessary and can be removed for clarity.
packages/core/src/postgres/embedded-lifecycle.ts#L1125-L1125: removeawaitfromconst sql = await this.openMaintenanceSql();.packages/core/src/postgres/embedded-lifecycle.ts#L1137-L1137: removeawaitfromconst sql = await this.openMaintenanceSql();.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/postgres/embedded-lifecycle.ts` at line 1125, Remove the unnecessary await from both calls assigning const sql via openMaintenanceSql() at packages/core/src/postgres/embedded-lifecycle.ts lines 1125 and 1137, preserving the synchronous postgres tagged-template function usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/core/src/postgres/embedded-lifecycle.ts`:
- Around line 1065-1073: Update the elevated Windows startup flow around
startServerAsNonAdminUser and startInternal so the wrapper process can be
terminated when start() times out before readiness completes. Expose and track
the wrapper PID immediately, or make the helper accept and honor an AbortSignal;
ensure timeout cleanup cancels/stops it and prevent late readiness from
assigning nonAdminHandle or registering a server on the failed lifecycle.
---
Nitpick comments:
In `@packages/core/src/postgres/embedded-lifecycle.ts`:
- Line 1125: Remove the unnecessary await from both calls assigning const sql
via openMaintenanceSql() at packages/core/src/postgres/embedded-lifecycle.ts
lines 1125 and 1137, preserving the synchronous postgres tagged-template
function usage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2bfda35d-b4b5-4e23-84cd-41d29b56b90c
📒 Files selected for processing (2)
packages/core/src/postgres/embedded-lifecycle.tspackages/core/src/postgres/embedded-windows-admin.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/core/src/postgres/embedded-windows-admin.ts
tsc targets ES2022; Promise.withResolvers needs ES2024 and failed the Windows desktop workspace build after the elevated non-admin smoke passed.
Merge origin/main (AbortSignal start cancellation) with the elevated Windows non-admin path. Address review feedback: - Publish stop handle as soon as the wrapper PID is known (onLaunched) and honor AbortSignal during readiness so outer start() timeouts kill orphans - settleCancelledStart stops non-admin handles before embedded-postgres.stop - Truncate/UTF-8 bat+ps1; strip Administrators from fusion-pg; sanitize flags - Prefer 127.0.0.1 maintenance connects without unnecessary await
- Bundle postgres.js via static import so CLI ensureDatabase works (boot-smoke failed with Cannot find module 'postgres' after lazy require) - Skip elevated non-admin boot when tests inject an EmbeddedPostgres ctor so delayed start cancellation coverage runs on Windows CI - Fail closed if fusion-pg cannot be demoted from Administrators - Honor startTimeoutMs <= 0 as unbounded on the non-admin readiness poll
Double-start cases (reuse log, VAL-CONN-006) need two elevated non-admin boots. A 180s vitest wall left the second start racing the budget, timing out mid- readiness and leaving EBUSY orphans on the data dir. Allow 6 minutes on win32.
Review feedback follow-upResolved the three open inline threads in
Earlier review-body notes that were already on the branch:
Validation: |
…ndows (#2117) ## Summary Windows embedded Postgres verification (CI `windows-latest` and elevated desktop) fails because PostgreSQL refuses to run under an administrative token: > Execution of PostgreSQL by a user with administrative permissions is not permitted. GitHub Actions runners execute as `runneradmin` elevated, so the existing `test:embedded-postgres` smoke (and any elevated Local-mode desktop launch) cannot start the server. ### Fix - When `isWindowsElevatedAdmin()` is true, **initdb / clients stay as the launcher**, but the **postgres server** is started as a dedicated non-admin local user (`fusion-pg`) via PowerShell `Start-Process -Credential`. - Readiness waits on the postgres log line `database system is ready to accept connections` with a lightweight poll (no per-iteration `tasklist`). - Real-process vitest cases use a **180s** timeout on Windows (package default is 15s, which killed healthy boots mid-start). - Builds on top of the packaged-desktop asar materialization work already on main (#2106). ## Test plan - [x] `pnpm --filter @fusion/core test:embedded-postgres` on macOS (33/33) - [ ] `desktop-windows.yml` on `feature/win-pg-verify`: - [ ] Smoke embedded Postgres on Windows - [ ] Build + package Windows EXE - [ ] Verify app.asar assets - [ ] Optional: download portable EXE and manual Local mode smoke on a Windows host <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved embedded PostgreSQL startup on Windows when Fusion runs with elevated administrator privileges. - When elevated, the embedded database now boots under a dedicated non-administrator local account, with more reliable readiness detection, logging, and shutdown cleanup. - Enhanced database provisioning and now prefers `127.0.0.1` for Windows connection addressing. - **Tests** - Added coverage for Windows elevation detection without starting embedded PostgreSQL. - Increased platform-dependent timeouts for embedded real-process tests to avoid premature failures on Windows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary Verification of the Fusion Linux AppImage + embedded Postgres packaging surface (follow-on to #2106 Mac packaging and #2117 Windows PG work). ### What we found 1. **Published `v0.60.0` AppImages are broken for Local/embedded Postgres** (pre-#2106): - No `embedded-postgres` / `@embedded-postgres/*` in `app.asar` or `app.asar.unpacked` - `package.json` `main` is still `dist/main.js` (no `main-bootstrap.cjs`) - No `omp-runtime` packaged - `asar.unpacked` only has incidental natives (pi-tui, esbuild, node-pty) 2. **Current main (post-#2106) packaging config is correct** (verified via mac `--dir` pack on this host): - `main` → `dist/main-bootstrap.cjs` - Full `asarUnpack` of `embedded-postgres` + `@embedded-postgres/**` - Native bins present under `app.asar.unpacked` - `omp-runtime` dist present in asar 3. **Linux arm64 native PG binary smoke** (`@embedded-postgres/linux-arm64` 15.18) in Docker: initdb → start → create DB → persist across restart → **OK** (requires postinstall soname symlinks from `hydrate-symlinks.js` / `pg-symlinks.json`). 4. **Host blocker:** this machine is macOS arm64 — cannot produce or execute a Linux AppImage end-to-end. Linux packaging must run on `ubuntu-latest` CI. ### Fix in this PR Release jobs only checked that `*.AppImage` files existed — which is how v0.60.0 shipped empty of Postgres. Add: - `scripts/verify-desktop-linux-pg-packaging.mjs` — inspects `linux-*-unpacked` trees for: - `app.asar.unpacked` embedded-postgres + `@embedded-postgres/linux-*` bins - `dist/main-bootstrap.cjs` + `package.json` main - `omp-runtime` presence - Wire into `release.yml` + `test-release.yml` after AppImage artifact checks - Unit test lock in `release-workflow.test.ts` ## Test plan - [x] `pnpm --filter @fusion/core test:embedded-postgres` (33/33 with 60s timeout; default 15s flaked under load) - [x] Desktop packaging unit tests (`electron-builder-config`, `build-bundling`, `release-workflow`) - [x] Inspected published `Fusion-0.60.0-linux-arm64.AppImage` (checksum OK; PG packaging absent) - [x] Post-#2106 `electron-builder --mac --dir`: asar.unpacked has PG + bootstrap + omp - [x] Docker linux-arm64 native binary lifecycle smoke - [ ] CI `build-desktop-linux` on this PR (runs the new verifier against real linux-unpacked) ## Gaps remaining (not fixed here) | Gap | Notes | |-----|-------| | Full AppImage launch + `/api/health` on Linux | Needs Linux host/CI with display or headless Electron | | No post-#2106 published AppImage yet | Next release will include packaging fixes; this PR stops empty AppImages | | README still says `linux-x64.AppImage` | Actual name is `linux-x86_64.AppImage` (workflow already correct) | | Windows packaged Local | Tracked by #2117 / verify-desktop | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Added verification for Linux AppImage packaging to ensure embedded Postgres binaries, platform files, symlinks, and runtime assets are included correctly. * Confirmed packaged application metadata points to the expected startup entry point. * Improved detection of invalid, missing, or incorrectly formatted packaging artifacts. * **Tests** * Added coverage for architecture-specific binaries, exact ASAR paths, executable files, and symlink metadata. * Verified packaging checks run after Linux desktop artifacts are created. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
Windows embedded Postgres verification (CI
windows-latestand elevated desktop) fails because PostgreSQL refuses to run under an administrative token:GitHub Actions runners execute as
runneradminelevated, so the existingtest:embedded-postgressmoke (and any elevated Local-mode desktop launch) cannot start the server.Fix
isWindowsElevatedAdmin()is true, initdb / clients stay as the launcher, but the postgres server is started as a dedicated non-admin local user (fusion-pg) via PowerShellStart-Process -Credential.database system is ready to accept connectionswith a lightweight poll (no per-iterationtasklist).Test plan
pnpm --filter @fusion/core test:embedded-postgreson macOS (33/33)desktop-windows.ymlonfeature/win-pg-verify:Summary by CodeRabbit
Bug Fixes
127.0.0.1for Windows connection addressing.Tests