Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions Docs/LinuxDeploymentAndUpdates.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,17 @@ conflicts, auto-staging stops with a warning and `/settings/updates` shows a
git-style conflict editor. Resolve and save the JSON there, or choose **Force
release defaults** to stage the release file without local appsettings values.

Activation is explicit. The UI copies the staged payload into
Activation is explicit and requires the worker to be running under Bootstrap.
The UI copies the staged payload into
`ManagedRuntime/WebService/<version>`, writes the active-release pointer to that
managed worker, updates the install-directory `appsettings.json` from the
resolved staged file, and clears old staged payloads. Bootstrap copies that
install-directory file into the managed worker before launch, observes the
pointer change, drains the old worker, starts the managed worker on the same
public port, and leaves managed Magnetar servers running. After a successful
cutover, Bootstrap prunes inactive managed web-release directories.
public port, and leaves managed Magnetar servers running. The browser polls
`/api/health` until the activated UI version is serving, then reloads the
Updates page. After a successful cutover, Bootstrap prunes inactive managed
web-release directories.

This intentionally accepts a short web/agent disconnect. `Quasar.Agent`
reconnects, and managed Magnetar processes stay alive because Quasar launches
Expand Down
16 changes: 8 additions & 8 deletions Docs/Reference/data/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,8 @@
"path": "Quasar.Bootstrap/Program.cs",
"name": "Program.cs",
"ext": ".cs",
"size": 95399,
"sha256": "e6606b7369b3f9f4ab529ddd1774620981d407fffecd09ec70679e133e4a5db7",
"size": 96453,
"sha256": "5330cde918b0c67442fe79125f0dcc95eff7337c4afbf1dee9513aaffa06b2dd",
"module": "Quasar.Bootstrap",
"tier": 1,
"status": "pending"
Expand Down Expand Up @@ -934,8 +934,8 @@
"path": "Quasar/Components/Pages/Updates.razor",
"name": "Updates.razor",
"ext": ".razor",
"size": 27689,
"sha256": "0769fe53ceff39972594110e53a22ca0f1bf4c090ede2cdb4301a99a5b5d30b2",
"size": 28978,
"sha256": "a13e47bceb35ff341d772b65a555595b7e6bce56c100a87b15823e36c64f448f",
"module": "Quasar.Components",
"tier": 2,
"status": "pending"
Expand Down Expand Up @@ -1964,8 +1964,8 @@
"path": "Quasar/Services/Updates/QuasarUpdateService.cs",
"name": "QuasarUpdateService.cs",
"ext": ".cs",
"size": 47252,
"sha256": "895348b11a244ed83a70f0158a875db25323723abfc7e0da86056b62ed5c544c",
"size": 47464,
"sha256": "473266643d585fd75ad67294a9a727f3aba348393ce15cbc3f0e7f54f837d2eb",
"module": "Quasar.Services.Core",
"tier": 2,
"status": "pending"
Expand Down Expand Up @@ -2134,8 +2134,8 @@
"path": "Quasar/wwwroot/quasar-configs.js",
"name": "quasar-configs.js",
"ext": ".js",
"size": 9808,
"sha256": "b0a29210fe1f8988e65a765d631069f2eb6b5e980cc99f8559ff3c82f73ffeac",
"size": 10978,
"sha256": "7f1eca665c40ce0655fbff3bac49d88f64fd73ae5cbe8fca7c67b24f00a6b0bc",
"module": "Quasar.Host",
"tier": 3,
"status": "pending"
Expand Down
2 changes: 1 addition & 1 deletion Docs/Reference/files/Quasar.Bootstrap/Program.cs.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Entry point and core logic for the Quasar launcher. It implements three CLI comm
### `BootstrapDataDirectoryMigration` (static)
- Runs once at process start before `MagnetarPaths` is used.
- Treats a blank `QUASAR_DATA_DIR`, a legacy default data root (`~/.config/Quasar` / `%APPDATA%\Quasar`), or an install-root value as the default path policy.
- Uses `AppContext.BaseDirectory` as the target data root, recursively copies legacy default root contents into it, rewrites migrated `Updates/active-release.json` file/working-directory paths from the old root to the new root, removes copied legacy files/directories when possible, then sets `QUASAR_DATA_DIR` for the current process and child worker.
- Uses `AppContext.BaseDirectory` as the target data root, recursively copies legacy default root contents into it, rewrites migrated `Updates/active-release.json` file, argument, and working-directory paths from the old root to the new root, removes copied legacy files/directories when possible, then sets `QUASAR_DATA_DIR` for the current process and child worker.
- Refuses the migration and falls back to the legacy root if the launcher install root is inside the legacy root, preventing recursive self-copy.
- Leaves custom `QUASAR_DATA_DIR` values untouched.

Expand Down
6 changes: 3 additions & 3 deletions Docs/Reference/files/Quasar/Components/Pages/Updates.razor.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Authorization: `QuasarPolicyNames.CanManageSecurity`
- `ManagedRuntimeWarmupService` — managed Magnetar/DS snapshot plus manual update checks
- `ISnackbar` — user feedback for update actions
- `IDialogService` — confirmation dialogs before enabling prerelease updates or forcing Bootstrap activation
- `IJSRuntime` — starts browser-side health polling before a forced Bootstrap restart drops the circuit
- `IJSRuntime` — starts browser-side health polling before UI-worker activation or a forced Bootstrap restart drops the circuit

**Key members**

Expand All @@ -31,8 +31,8 @@ Authorization: `QuasarPolicyNames.CanManageSecurity`
| `CheckDedicatedServerNowAsync()` | Runs an immediate managed DS check through `ManagedRuntimeWarmupService.CheckDedicatedServerNowAsync()`. |
| `HandleSelectedWebVersionChanged(...)` | Selects the UI release to stage/install from the discovered list, including older rollback targets. |
| `StageAsync()` | Downloads and stages the selected Quasar UI release unless it is already current or staged; if appsettings rollover conflicts, loads the conflict text and warns instead of reporting success. |
| `ActivateAsync()` | Requests staged UI activation; the update service promotes the staged payload into the managed active-release directory and writes the active-release pointer. Older staged releases are allowed for rollback. |
| `ForceActivateBootstrapAsync()` | Confirms, starts browser health polling, then asks `QuasarUpdateService` to write the Bootstrap update request file so the launcher activates the detected update immediately. Disabled when no Bootstrap update is detected or the worker is not launcher-managed. |
| `ActivateAsync()` | Starts browser health polling for the selected target version, then requests staged UI activation; disabled outside Bootstrap because no launcher is watching the active-release pointer. Older staged releases are allowed for rollback. |
| `ForceActivateBootstrapAsync()` | Confirms, starts browser health polling that waits for a restart gap, then asks `QuasarUpdateService` to write the Bootstrap update request file so the launcher activates the detected update immediately. Disabled when no Bootstrap update is detected or the worker is not launcher-managed. |
| `HandleIncludePrereleaseChanged(bool)` | Confirms before enabling prerelease updates, persists the stream setting through `QuasarUpdateService`, refreshes the release list, and shows a strong warning while prereleases are enabled. |
| `HandleAutoStageWebUpdatesChanged(bool)` | Persists whether release checks should automatically download/stage a newer UI release or only queue releases for manual staging. |
| `LoadAppSettingsConflictAsync()` / `SaveAppSettingsResolutionAsync()` / `ForceReleaseAppSettingsAsync()` | Reads the staged conflict file, saves a manually resolved JSON file, or force-restages release defaults after confirmation. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Namespace: `Quasar.Services.Updates`
| `CheckNowAsync(ct)` | Checks the configured GitHub releases endpoint, builds all selectable non-draft UI releases containing the configured asset, finds only newer Bootstrap candidates, updates the selected UI version, and auto-stages a newer UI version only when auto-stage mode is enabled. |
| `StageWebUpdateAsync(ct)` / `StageWebUpdateAsync(forceAppSettingsOverride, ct)` | Downloads the selected web asset, resolves/verifies its SHA-256 checksum from `SHA256SUMS`, extracts it into `Updates/Staged/<version>`, validates required web layout files, resolves staged `appsettings.json`, and marks it staged. Current-version staging is rejected; older selected releases can be staged for rollback. |
| `ReadAppSettingsConflictTextAsync(ct)` / `ResolveAppSettingsConflictAsync(text, ct)` | Supports the Updates page conflict editor by reading the staged conflict file, validating the resolved JSON, persisting it, and marking the release staged once conflict markers are gone. |
| `ActivateStagedWebUpdateAsync(ct)` | Copies the staged payload into `ManagedRuntime/WebService/<version>`, syncs the resolved active `appsettings.json` back to the install directory when `QUASAR_INSTALL_DIR` is known, writes `QuasarActiveReleasePointer` to the active-release path so Bootstrap can swap workers, and clears old staged payloads. Staged older UI releases are valid rollback targets. |
| `ActivateStagedWebUpdateAsync(ct)` | Requires a launcher-managed worker, copies the staged payload into `ManagedRuntime/WebService/<version>`, syncs the resolved active `appsettings.json` back to the install directory when `QUASAR_INSTALL_DIR` is known, writes `QuasarActiveReleasePointer` to the active-release path so Bootstrap can swap workers, and clears old staged payloads. Staged older UI releases are valid rollback targets. |
| `RequestBootstrapUpdateActivationAsync(ct)` | Validates that a newer Bootstrap candidate exists and the worker is launcher-managed, then writes `Updates/bootstrap-update-request.json` with the detected version/asset for Bootstrap to consume and publishes an activating status message. |
| `ExecuteAsync(stoppingToken)` | Runs an initial delayed check and repeats every configured interval while enabled. |
| `GetReleasesAsync(ct)` / `BuildCandidates(...)` | Calls GitHub releases API (`per_page=100`), ignores drafts, optionally includes prereleases, and maps matching release assets into UI/Bootstrap candidates. |
Expand All @@ -46,4 +46,4 @@ Private nested DTOs `GitHubRelease` and `GitHubAsset` model the small subset of

## Notes

UI-worker activation stays explicit from the Updates page on both Linux and Windows. Auto-stage mode only downloads/stages the newer selected UI release; manual mode queues releases until the operator stages one. Launcher updates are reported in the UI; normally Bootstrap installs them automatically from the platform asset (`quasar-installer-linux.tar.gz` on Linux, `quasar-installer-windows.zip` on Windows), but a launcher-managed worker can request immediate activation by writing `Updates/bootstrap-update-request.json` with the detected target version and asset. Bootstrap consumes that request and runs its normal verified self-update path for that requested release, restarting on Linux via systemd exit-75 or on Windows by spawning a detached replacement launcher. Staged UI payloads are rejected before activation when core Blazor/MudBlazor/app static assets are missing or `appsettings.json` has unresolved conflict markers; older staged UI payloads are allowed so the operator can roll back the worker. Active UI releases live outside `Updates/Staged`, so the Updates folder only contains transient staged payloads plus the active pointer, the Bootstrap update request file, and the stored `appsettings.json` merge base. The prerelease switch affects the running worker immediately; forced Bootstrap requests carry the detected candidate, while Bootstrap reads the persisted data-directory override for periodic checks after its next restart.
UI-worker activation stays explicit from the Updates page on both Linux and Windows and is rejected when the worker is not running under Bootstrap. Auto-stage mode only downloads/stages the newer selected UI release; manual mode queues releases until the operator stages one. Launcher updates are reported in the UI; normally Bootstrap installs them automatically from the platform asset (`quasar-installer-linux.tar.gz` on Linux, `quasar-installer-windows.zip` on Windows), but a launcher-managed worker can request immediate activation by writing `Updates/bootstrap-update-request.json` with the detected target version and asset. Bootstrap consumes that request and runs its normal verified self-update path for that requested release, restarting on Linux via systemd exit-75 or on Windows by spawning a detached replacement launcher. Staged UI payloads are rejected before activation when core Blazor/MudBlazor/app static assets are missing or `appsettings.json` has unresolved conflict markers; older staged UI payloads are allowed so the operator can roll back the worker. Active UI releases live outside `Updates/Staged`, so the Updates folder only contains transient staged payloads plus the active pointer, the Bootstrap update request file, and the stored `appsettings.json` merge base. The prerelease switch affects the running worker immediately; forced Bootstrap requests carry the detected candidate, while Bootstrap reads the persisted data-directory override for periodic checks after its next restart.
2 changes: 1 addition & 1 deletion Docs/Reference/files/Quasar/wwwroot/quasar-configs.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Small JavaScript interop module registered as `window.quasarConfigs`. Provides u
| `getScrollEdgeState(id, threshold)` | `(string, number?) → object` | Returns `{ nearTop, nearBottom }` booleans for scroll containers; retained for simple edge checks |
| `attachRolloverLog(id, dotNetRef, options)` | `(string, DotNetObjectReference, object) → void` | Attaches browser-side scroll, click, and `Ctrl`/`Alt` + `PageUp`/`PageDown`/`Home`/`End` listeners for the server-log viewer; calls .NET only when a 250-line window move or start/end jump is needed |
| `detachRolloverLog(id)` | `(string) → void` | Removes listeners installed by `attachRolloverLog` |
| `reloadWhenHealthy(targetUrl, options)` | `(string, object?) → void` | Used during a Quasar worker restart (the Blazor circuit drops): after an initial delay, polls the anonymous `/api/health` endpoint at `pollIntervalMs` (default 1 s) and navigates to `targetUrl` once it responds `ok`; falls back to a plain reload after `maxWaitMs` (default 120 s) |
| `reloadWhenHealthy(targetUrl, options)` | `(string, object?) → void` | Used during a Quasar worker restart (the Blazor circuit drops): after an initial delay, polls the anonymous `/api/health` endpoint at `pollIntervalMs` (default 1 s) and navigates to `targetUrl` once it responds `ok`, optionally waiting for `expectedVersion` or for an unhealthy gap via `requireUnhealthy`; falls back to a plain reload after `maxWaitMs` (default 120 s) |

## Dependencies
- Called by Blazor components via `IJSRuntime` (specific callers not determinable from this file alone)
Expand Down
10 changes: 6 additions & 4 deletions Docs/WindowsDeploymentAndUpdates.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,12 @@ default and lists selectable `quasar-web-win-x64.zip` releases on
`AutoStageWebUpdates` enabled, a newer web asset is downloaded and staged
automatically after its `SHA256SUMS` entry is verified; with it disabled, releases
remain queued until the operator stages the selected version. Activation is
explicit; the UI copies the staged payload into
`<install-root>\ManagedRuntime\WebService\<version>`, clears stale staged
payloads, and Bootstrap drains the old worker, starts the managed `Quasar.exe` on
the same port, and leaves managed Magnetar servers running.
explicit and requires the worker to be running under Bootstrap; the UI copies the
staged payload into `<install-root>\ManagedRuntime\WebService\<version>`, clears
stale staged payloads, and Bootstrap drains the old worker, starts the managed
`Quasar.exe` on the same port, and leaves managed Magnetar servers running. The
browser polls `/api/health` until the activated UI version is serving, then
reloads the Updates page.

Staging also resolves `appsettings.json`. Quasar uses the stored release base in
the data directory (`<install-root>\Updates\appsettings.base.json` by default) as the
Expand Down
27 changes: 25 additions & 2 deletions Quasar.Bootstrap/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,10 @@ private static void TryRewriteMigratedActiveReleasePointer(string legacyRoot, st

var fileName = RewriteMigratedPath(pointer.FileName, legacyRoot, targetRoot);
var workingDirectory = RewriteMigratedPath(pointer.WorkingDirectory, legacyRoot, targetRoot);
var arguments = RewriteMigratedArguments(pointer.Arguments, legacyRoot, targetRoot);
if (string.Equals(fileName, pointer.FileName, StringComparison.Ordinal) &&
string.Equals(workingDirectory, pointer.WorkingDirectory, StringComparison.Ordinal))
string.Equals(workingDirectory, pointer.WorkingDirectory, StringComparison.Ordinal) &&
string.Equals(arguments, pointer.Arguments, StringComparison.Ordinal))
{
return;
}
Expand All @@ -668,7 +670,7 @@ private static void TryRewriteMigratedActiveReleasePointer(string legacyRoot, st
{
Version = pointer.Version,
FileName = fileName,
Arguments = pointer.Arguments,
Arguments = arguments,
WorkingDirectory = workingDirectory,
ActivatedAtUtc = pointer.ActivatedAtUtc,
};
Expand All @@ -694,6 +696,27 @@ private static string RewriteMigratedPath(string value, string legacyRoot, strin
return Path.Combine(targetRoot, relativePath);
}

private static string RewriteMigratedArguments(string value, string legacyRoot, string targetRoot)
{
if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(legacyRoot))
return value;

var normalizedLegacyRoot = NormalizeDirectory(legacyRoot);
var normalizedTargetRoot = NormalizeDirectory(targetRoot);
var comparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
var rewritten = value.Replace(normalizedLegacyRoot, normalizedTargetRoot, comparison);

if (OperatingSystem.IsWindows())
{
rewritten = rewritten.Replace(
normalizedLegacyRoot.Replace('\\', '/'),
normalizedTargetRoot.Replace('\\', '/'),
StringComparison.OrdinalIgnoreCase);
}

return rewritten;
}

private static void MergeDirectoryContents(string sourceDirectory, string destinationDirectory)
{
Directory.CreateDirectory(destinationDirectory);
Expand Down
Loading
Loading