commit b27126269546093439cf1b93f593f8f1cc31cd42
parent 751272017c9e579f763b8c07d63f3f617449ec92
Author: Pablo Murad <pblmrd@gmail.com>
Date: Fri, 1 May 2026 12:12:16 -0300
metadata
Diffstat:
4 files changed, 77 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
@@ -91,7 +91,7 @@ The API returns **503** only when the in-memory track pool is **empty** (`trackC
Check `metadataTsv`, `metadataExists`, `metadataSizeBytes`, `trackCount`, `tracksReady`, and `hint`.
-2. **Align `METADATA_TSV`** with the real file (e.g. `/opt/mymusics/data/metadata.tsv`). Wrong paths such as `/opt/data/metadata.tsv` look “almost right” but **fail** if the file lives under the app directory. Alternatively **remove** `METADATA_TSV` from `.env` / PM2 so the app uses the default `data/metadata.tsv` next to the project.
+2. **Align `METADATA_TSV`** with the real file (e.g. `/opt/mymusics/data/metadata.tsv`). Wrong paths such as `/opt/data/metadata.tsv` look “almost right” but **fail** if the file lives under the app directory. Alternatively **remove** `METADATA_TSV` from `.env` / PM2 so the app uses the default `data/metadata.tsv` next to the project. If the env path is missing but **`data/metadata.tsv` exists inside the app**, the server **falls back** to that file automatically and logs a warning (you should still fix `.env` to avoid confusion).
3. **Restart the process** after editing env so variables reload:
diff --git a/server/index.ts b/server/index.ts
@@ -25,9 +25,34 @@ function resolvePath(p: string): string {
const PORT = resolveApiPort(process.env);
/** Resolved path to data/metadata.tsv next to the app (independent of METADATA_TSV env). */
const BUNDLED_METADATA_TSV = path.join(__dirname, "..", "data", "metadata.tsv");
-const METADATA_TSV = process.env.METADATA_TSV
- ? resolvePath(process.env.METADATA_TSV.trim())
- : BUNDLED_METADATA_TSV;
+
+/** If METADATA_TSV points to a missing file but the bundled copy exists, use bundled (VPS typo self-heal). */
+function resolveEffectiveMetadataTsv(): {
+ path: string;
+ envRequested: string | null;
+ usedFallback: boolean;
+} {
+ const raw = process.env.METADATA_TSV?.trim();
+ if (!raw) {
+ return { path: BUNDLED_METADATA_TSV, envRequested: null, usedFallback: false };
+ }
+ const resolved = resolvePath(raw);
+ if (fs.existsSync(resolved)) {
+ return { path: resolved, envRequested: resolved, usedFallback: false };
+ }
+ if (fs.existsSync(BUNDLED_METADATA_TSV)) {
+ console.warn(
+ `MyMusics: METADATA_TSV not found at ${resolved}; using bundled ${BUNDLED_METADATA_TSV}`,
+ );
+ return { path: BUNDLED_METADATA_TSV, envRequested: resolved, usedFallback: true };
+ }
+ return { path: resolved, envRequested: resolved, usedFallback: false };
+}
+
+const _meta = resolveEffectiveMetadataTsv();
+const METADATA_TSV = _meta.path;
+const METADATA_ENV_REQUESTED = _meta.envRequested;
+const METADATA_USED_FALLBACK = _meta.usedFallback;
function hintForMetadataNotFound(message: string): string {
if (!message.includes("not found")) return message;
@@ -91,7 +116,9 @@ function randomTrack(): TrackMeta | null {
async function main() {
console.info(`MyMusics: cwd=${process.cwd()}`);
- console.info(`MyMusics: METADATA_TSV resolved to ${METADATA_TSV}`);
+ console.info(
+ `MyMusics: metadata file ${METADATA_TSV}${METADATA_USED_FALLBACK ? ` (fallback; env had ${METADATA_ENV_REQUESTED})` : ""}`,
+ );
try {
rebuildPool();
} catch (e) {
@@ -116,6 +143,9 @@ async function main() {
trackCount: pool.length,
tracksReady: pool.length > 0,
metadataTsv: METADATA_TSV,
+ ...(METADATA_ENV_REQUESTED && METADATA_ENV_REQUESTED !== METADATA_TSV
+ ? { metadataEnvRequested: METADATA_ENV_REQUESTED, metadataUsedFallback: METADATA_USED_FALLBACK }
+ : {}),
metadataExists: fs.existsSync(METADATA_TSV),
metadataSizeBytes,
cwd: process.cwd(),
diff --git a/src/App.css b/src/App.css
@@ -199,6 +199,31 @@
color: var(--sky);
}
+.health-info {
+ margin-bottom: 1.25rem;
+ padding: 0.85rem 1rem;
+ border-radius: 12px;
+ border: 1px solid color-mix(in srgb, var(--sky) 40%, transparent);
+ background: color-mix(in srgb, var(--sky) 8%, var(--panel));
+}
+
+.health-info strong {
+ display: block;
+ margin-bottom: 0.35rem;
+ color: var(--sky);
+ font-size: 0.85rem;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+}
+
+.health-info p {
+ margin: 0;
+ font-size: 0.85rem;
+ line-height: 1.45;
+ color: var(--muted);
+ word-break: break-word;
+}
+
.footer {
margin-top: 2.5rem;
text-align: center;
diff --git a/src/App.tsx b/src/App.tsx
@@ -18,6 +18,8 @@ type HealthBody = {
tracksReady?: boolean;
hint?: string;
metadataTsv?: string;
+ metadataEnvRequested?: string;
+ metadataUsedFallback?: boolean;
metadataExists?: boolean;
trackCount?: number;
};
@@ -29,6 +31,7 @@ export default function App() {
const [history, setHistory] = useState<TrackInfo[]>([]);
const [autoPlay, setAutoPlay] = useState(true);
const [healthWarn, setHealthWarn] = useState<string | null>(null);
+ const [healthInfo, setHealthInfo] = useState<string | null>(null);
const playUrl = useCallback((url: string) => {
const a = audioRef.current;
@@ -75,6 +78,7 @@ export default function App() {
const res = await fetch("/api/health");
const h = (await res.json()) as HealthBody;
if (!h.tracksReady) {
+ setHealthInfo(null);
const parts = [
h.hint,
h.metadataTsv && `Path: ${h.metadataTsv}`,
@@ -88,6 +92,13 @@ export default function App() {
);
} else {
setHealthWarn(null);
+ if (h.metadataUsedFallback && h.metadataEnvRequested && h.metadataTsv) {
+ setHealthInfo(
+ `METADATA_TSV was not found at ${h.metadataEnvRequested}. Using bundled file at ${h.metadataTsv}. Remove or fix METADATA_TSV in .env / PM2.`,
+ );
+ } else {
+ setHealthInfo(null);
+ }
}
} catch {
setHealthWarn(null);
@@ -117,6 +128,12 @@ export default function App() {
</p>
</div>
) : null}
+ {healthInfo ? (
+ <div className="health-info" role="status">
+ <strong>Metadata path</strong>
+ <p>{healthInfo}</p>
+ </div>
+ ) : null}
<header className="header">
<img
className="logo"