Skip to content

Commit 257fa49

Browse files
committed
Add DecentDB migration guide and enhance health checks
- Introduced detailed DecentDB usage and migration documentation to guide users through resolving unsupported file-format errors. - Updated Admin dashboard to display specific DecentDB health issues, linking to the migration guide for easier resolution. - Improved `Doctor` service and dashboard logic to surface health check results for incompatible DecentDB files. - Enhanced test coverage for health checks and migration scenarios.
1 parent 00b55f4 commit 257fa49

32 files changed

Lines changed: 760 additions & 27 deletions

.github/workflows/dotnet.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ jobs:
7474
Jwt__Audience: "test"
7575
security__secretKey: "testsecretkeytestsecretkeytestsecretkey"
7676
QuartzDisabled: "true"
77-
run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings coverage.runsettings --results-directory coverage/results
77+
# Parallel solution-level VSTest with coverage can return a non-zero exit after successful test summaries.
78+
run: dotnet test --no-build -m:1 --verbosity normal --collect:"XPlat Code Coverage" --settings coverage.runsettings --results-directory coverage/results
7879
- name: Generate coverage report
7980
run: |
8081
dotnet tool install -g dotnet-reportgenerator-globaltool

docs/_data/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
url: "hardware"
2121
- title: "Backup & Recovery"
2222
url: "backup"
23+
- title: "DecentDB Usage & Migration"
24+
url: "decentdb"
2325

2426
- title: Core Concepts
2527
links:

docs/pages/changelog.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323

2424
## [Unreleased]
2525

26+
### Added
27+
28+
- Added public DecentDB usage and migration documentation covering Melodee's
29+
generated search databases and rebuild steps for unsupported file-format
30+
errors.
31+
32+
### Fixed
33+
34+
- Admin dashboard and login health warnings now open-check MusicBrainz and
35+
ArtistSearch DecentDB files and link to the migration guide when unsupported
36+
DecentDB file-format versions are detected.
37+
2638
## [2.1.3] - 2026-06-15
2739

2840
### Added

docs/pages/cli/doctor.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ mcli doctor [OPTIONS]
2929
- Validates required connection strings exist
3030
2. **Database connectivity**
3131
- Postgres (`DefaultConnection`)
32-
- MusicBrainz SQLite (`MusicBrainzConnection`)
33-
- ArtistSearchEngine SQLite (`ArtistSearchEngineConnection`)
32+
- MusicBrainz DecentDB (`MusicBrainzConnection`)
33+
- ArtistSearchEngine DecentDB (`ArtistSearchEngineConnection`)
3434
3. **Library paths**
3535
- Ensures each configured library path exists
3636
- Optionally validates write access with `--write-test`
@@ -60,8 +60,8 @@ mcli doctor [OPTIONS]
6060
```
6161
✓ Configuration
6262
✓ Database: Postgres
63-
✓ Database: MusicBrainz (SQLite)
64-
✓ Database: ArtistSearchEngine (SQLite)
63+
✓ Database: MusicBrainz (DecentDB)
64+
✓ Database: ArtistSearchEngine (DecentDB)
6565
✓ Libraries
6666
6767
All checks passed.

docs/pages/decentdb.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
title: DecentDB Usage & Migration
3+
description: How Melodee uses DecentDB, how to diagnose compatibility issues, and how to rebuild generated DecentDB search databases.
4+
permalink: /decentdb/
5+
tags:
6+
- decentdb
7+
- migration
8+
- troubleshooting
9+
---
10+
11+
## Overview
12+
13+
Melodee uses DecentDB for local, generated search databases. These files are
14+
separate from the primary Melodee PostgreSQL database and can be rebuilt from
15+
source data when necessary.
16+
17+
DecentDB is currently used for:
18+
19+
- **MusicBrainz search data**: the local MusicBrainz artist, alias, relation,
20+
and album lookup database.
21+
- **Artist search cache**: the local artist search repository used to speed up
22+
artist matching and enrichment.
23+
24+
Melodee does not store user accounts, playlists, play history, ratings, or
25+
library metadata in these DecentDB files. Those records live in the primary
26+
PostgreSQL database.
27+
28+
## Configuration
29+
30+
The DecentDB databases are configured with these connection strings:
31+
32+
| Connection string | Purpose |
33+
|-------------------|---------|
34+
| `MusicBrainzConnection` | Local MusicBrainz lookup database |
35+
| `ArtistSearchEngineConnection` | Local artist search cache database |
36+
37+
In container deployments, the same values can be supplied with environment
38+
variables:
39+
40+
```bash
41+
ConnectionStrings__MusicBrainzConnection="Data Source=/app/storage/_search-engines/musicbrainz/musicbrainz.ddb"
42+
ConnectionStrings__ArtistSearchEngineConnection="Data Source=/app/storage/_search-engines/artistSearchEngine.ddb"
43+
```
44+
45+
Use your configured paths as the source of truth. Older installations may use
46+
`.db` filenames while newer examples use `.ddb`; the connection string path is
47+
what matters.
48+
49+
## Doctor Compatibility Checks
50+
51+
Doctor opens both DecentDB files with the current Melodee DecentDB provider. If
52+
the file was created by a newer or incompatible DecentDB engine, Doctor reports
53+
an issue similar to:
54+
55+
```text
56+
MusicBrainz DecentDB database uses a file format that is not supported by the current DecentDB provider.
57+
Provider error: unsupported DecentDB file format version 11
58+
```
59+
60+
This means the current Melodee process cannot safely read that generated search
61+
database. Search and enrichment may continue in degraded mode, but the affected
62+
database should be rebuilt or the Melodee/DecentDB package version should be
63+
updated.
64+
65+
## Migration Strategy
66+
67+
For Melodee's generated DecentDB files, migration usually means rebuilding the
68+
affected generated database with the current Melodee version. This is safer than
69+
trying to manually edit a DecentDB file created by an incompatible provider.
70+
71+
Use this order:
72+
73+
1. Upgrade Melodee to the intended version.
74+
2. Stop Melodee so no process is writing to the DecentDB files.
75+
3. Back up the affected `.ddb` file and any companion WAL or shared-memory
76+
files.
77+
4. Move the incompatible generated database out of the active path.
78+
5. Start Melodee.
79+
6. Rebuild the affected database from Melodee.
80+
7. Run Doctor again and confirm the DecentDB checks pass.
81+
82+
## Backup Before Rebuild
83+
84+
Back up the main database and generated DecentDB files before changing anything.
85+
The example below preserves common DecentDB companion file names.
86+
87+
```bash
88+
#!/usr/bin/env bash
89+
set -euo pipefail
90+
91+
backup_root="$HOME/melodee-decentdb-backup-$(date +%Y%m%d-%H%M%S)"
92+
mkdir -p "$backup_root"
93+
94+
musicbrainz_db="/path/to/search-engine-storage/musicbrainz/musicbrainz.ddb"
95+
artist_search_db="/path/to/search-engine-storage/artistSearchEngine.ddb"
96+
97+
backup_decentdb_file() {
98+
local db_path="$1"
99+
local db_name
100+
db_name="$(basename "$db_path")"
101+
102+
for suffix in "" ".wal" "-wal" ".shm" "-shm"; do
103+
if [ -f "${db_path}${suffix}" ]; then
104+
cp -a "${db_path}${suffix}" "$backup_root/${db_name}${suffix}"
105+
fi
106+
done
107+
}
108+
109+
backup_decentdb_file "$musicbrainz_db"
110+
backup_decentdb_file "$artist_search_db"
111+
112+
echo "Backed up DecentDB files to $backup_root"
113+
```
114+
115+
Also back up PostgreSQL before a Melodee upgrade. See
116+
[Backup & Recovery](/backup/) for full backup guidance.
117+
118+
## Example: Rebuild Incompatible DecentDB Files
119+
120+
This example renames incompatible generated DecentDB files so Melodee can create
121+
fresh files with the current provider.
122+
123+
```bash
124+
#!/usr/bin/env bash
125+
set -euo pipefail
126+
127+
stamp="$(date +%Y%m%d-%H%M%S)"
128+
129+
musicbrainz_db="/path/to/search-engine-storage/musicbrainz/musicbrainz.ddb"
130+
artist_search_db="/path/to/search-engine-storage/artistSearchEngine.ddb"
131+
132+
move_decentdb_file_aside() {
133+
local db_path="$1"
134+
135+
for suffix in "" ".wal" "-wal" ".shm" "-shm"; do
136+
if [ -f "${db_path}${suffix}" ]; then
137+
mv "${db_path}${suffix}" "${db_path}${suffix}.unsupported-$stamp"
138+
fi
139+
done
140+
}
141+
142+
# Stop Melodee before moving active database files.
143+
# podman compose down
144+
# docker compose down
145+
146+
move_decentdb_file_aside "$musicbrainz_db"
147+
move_decentdb_file_aside "$artist_search_db"
148+
149+
# Start Melodee again.
150+
# podman compose up -d
151+
# docker compose up -d
152+
```
153+
154+
After the files are moved aside, rebuild the generated databases.
155+
156+
### MusicBrainz
157+
158+
Use one of these options:
159+
160+
- In the web UI, go to **Admin > Doctor** and use **Generate MusicBrainz
161+
Database**.
162+
- In the web UI, go to **Admin > Jobs** and run
163+
`MusicBrainzUpdateDatabaseJob`.
164+
- From the CLI, run:
165+
166+
```bash
167+
./mcli job musicbrainz-update
168+
```
169+
170+
MusicBrainz rebuilds can take a long time because Melodee downloads and imports
171+
the MusicBrainz dump into a local DecentDB file.
172+
173+
### Artist Search Cache
174+
175+
Use one of these options:
176+
177+
- In the web UI, go to **Admin > Jobs** and run
178+
`ArtistSearchEngineRepositoryHousekeepingJob`.
179+
- From the CLI, run:
180+
181+
```bash
182+
./mcli job artistsearchengine-refresh
183+
```
184+
185+
The artist search cache is generated from Melodee's library data and configured
186+
artist search providers. It can be rebuilt after the incompatible file is moved
187+
aside.
188+
189+
## Verify The Migration
190+
191+
Run Doctor after rebuilding:
192+
193+
```bash
194+
./mcli doctor --verbose
195+
```
196+
197+
Or open **Admin > Doctor** in the web UI.
198+
199+
The following checks should pass:
200+
201+
- `MusicBrainzDatabase`
202+
- `ArtistSearchEngineDatabase`
203+
204+
If Doctor still reports an unsupported DecentDB file format after rebuilding,
205+
confirm that:
206+
207+
- Melodee is running the version you expect.
208+
- The active connection strings point to the rebuilt files.
209+
- No old `.wal`, `-wal`, `.shm`, or `-shm` companion files remain beside the
210+
rebuilt database.
211+
- The app container or service was restarted after the rebuild.
212+
213+
## What Not To Delete
214+
215+
Do not delete the primary PostgreSQL database when resolving DecentDB search
216+
cache compatibility issues. The unsupported DecentDB file-format warning applies
217+
to generated local search databases, not the primary Melodee database.
218+

src/Melodee.Blazor/Components/Pages/Admin/Dashboard.razor

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,35 @@
5454
</RadzenColumn>
5555
</RadzenRow>
5656

57+
@if (!_isCheckingHealth && _systemHealthIssues.Length > 0)
58+
{
59+
<RadzenAlert AlertStyle="AlertStyle.Danger" Variant="Variant.Outlined" Icon="error">
60+
<RadzenStack Gap="0.5rem">
61+
<RadzenText TextStyle="TextStyle.Subtitle2" Style="margin: 0;">@L("Dashboard.IssuesDetected")</RadzenText>
62+
@foreach (var issue in _systemHealthIssues)
63+
{
64+
<RadzenText TextStyle="TextStyle.Body2" Style="margin: 0; overflow-wrap: anywhere;">
65+
@FormatHealthIssue(issue)
66+
</RadzenText>
67+
}
68+
@if (DashboardHealthWarningEvaluator.HasUnsupportedDecentDbIssue(_systemHealthIssues))
69+
{
70+
<a href="@DashboardHealthWarningEvaluator.DecentDbMigrationGuideUrl"
71+
target="_blank"
72+
rel="noopener noreferrer"
73+
style="font-weight: 600;">
74+
@L("Dashboard.DecentDbMigrationGuide")
75+
</a>
76+
}
77+
<RadzenButton Text="@L("Navigation.Doctor")"
78+
Icon="health_and_safety"
79+
Click="@(() => NavigationManager.NavigateTo("/admin/doctor"))"
80+
Size="ButtonSize.Small"
81+
ButtonStyle="ButtonStyle.Danger"/>
82+
</RadzenStack>
83+
</RadzenAlert>
84+
}
85+
5786
<RadzenRow Gap="1rem">
5887
@* Quick Links *@
5988
<RadzenColumn Size="12" SizeMD="6">
@@ -502,6 +531,7 @@
502531
private string _melodeeVersion = "---";
503532
private string _apiVersion = "---";
504533
private string _osDescription = "---";
534+
private Melodee.Common.Services.Doctor.DoctorCheckResult[] _systemHealthIssues = [];
505535
private bool _systemHealthy = true;
506536
private bool _isCheckingHealth = true;
507537

@@ -648,10 +678,19 @@
648678
try
649679
{
650680
var results = await DoctorService.RunAllChecksAsync();
651-
_systemHealthy = results.Checks.All(c => c.Success);
681+
_systemHealthIssues = results.Checks.Where(c => !c.Success).Take(5).ToArray();
682+
_systemHealthy = _systemHealthIssues.Length == 0;
652683
}
653684
catch
654685
{
686+
_systemHealthIssues =
687+
[
688+
new Melodee.Common.Services.Doctor.DoctorCheckResult(
689+
"Doctor",
690+
false,
691+
"Doctor checks failed before returning results",
692+
TimeSpan.Zero)
693+
];
655694
_systemHealthy = false;
656695
}
657696
finally
@@ -661,6 +700,43 @@
661700
}
662701
}
663702

703+
private string FormatHealthIssue(Melodee.Common.Services.Doctor.DoctorCheckResult issue)
704+
{
705+
var name = LocalizeHealthIssueName(issue.Name);
706+
return string.IsNullOrWhiteSpace(issue.Details)
707+
? name
708+
: $"{name}: {issue.Details}";
709+
}
710+
711+
private string LocalizeHealthIssueName(string checkName)
712+
{
713+
return checkName switch
714+
{
715+
"Configuration" => L("AdminDoctor.Check.Configuration"),
716+
"PostgresDatabase" => L("AdminDoctor.Check.PostgresDatabase"),
717+
"MusicBrainzDatabase" => L("AdminDoctor.Check.MusicBrainzDatabase"),
718+
"ArtistSearchEngineDatabase" => L("AdminDoctor.Check.ArtistSearchEngineDatabase"),
719+
"LibraryPaths" => L("AdminDoctor.Check.LibraryPaths"),
720+
"SerilogLogging" => L("AdminDoctor.Check.SerilogLogging"),
721+
"ConfigurableServices" => L("AdminDoctor.Check.ConfigurableServices"),
722+
"DiskSpace" => L("AdminDoctor.Check.DiskSpace"),
723+
"LibraryPathOverlap" => L("AdminDoctor.Check.LibraryPathOverlap"),
724+
"SearchEngineApiKeys" => L("AdminDoctor.Check.SearchEngineApiKeys"),
725+
"SmtpConfiguration" => L("AdminDoctor.Check.SmtpConfiguration"),
726+
"JwtTokenStrength" => L("AdminDoctor.Check.JwtTokenStrength"),
727+
"HttpsSecurity" => L("AdminDoctor.Check.HttpsSecurity"),
728+
"AdminPassword" => L("AdminDoctor.Check.AdminPassword"),
729+
"Scheduler" => L("AdminDoctor.Check.Scheduler"),
730+
"FFmpeg" => L("AdminDoctor.Check.FFmpeg"),
731+
"Memory" => L("AdminDoctor.Check.Memory"),
732+
"TempDirectory" => L("AdminDoctor.Check.TempDirectory"),
733+
"DatabaseLatency" => L("AdminDoctor.Check.DatabaseLatency"),
734+
"JukeboxConfiguration" => L("AdminDoctor.Check.JukeboxConfiguration"),
735+
"PodcastConfiguration" => L("AdminDoctor.Check.PodcastConfiguration"),
736+
_ => checkName
737+
};
738+
}
739+
664740
private void ClearCacheClicked()
665741
{
666742
CacheManager.Clear();

0 commit comments

Comments
 (0)