PHP shims: derive paths from __DIR__ (PHP-FPM runs as caddy user)

BirdNET-Pi's installer runs PHP-FPM as the caddy user, so getenv('HOME')
resolves to /var/lib/caddy - not the install user's home. Every path
constructed from getenv('HOME') was broken on first install.

PHP resolves __DIR__ through the install_services.sh symlink to the
realpath ($HOME/BirdNET-Pi/avian/api), so dirname(__DIR__, 2) = the
BirdNET-Pi install root and dirname(__DIR__, 3) = the user's home.
Works under any username because nothing is hardcoded.

Also fixed birdnet-api.php's DB_PATH which walked one level too far
(used , 3) - the BirdNET-Pi root is , 2).
This commit is contained in:
Twarner491
2026-05-28 11:16:38 -07:00
parent 7999483b7a
commit 9ea3249f9d
4 changed files with 15 additions and 19 deletions
+8 -12
View File
@@ -18,18 +18,14 @@ declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: public, max-age=30'); header('Cache-Control: public, max-age=30');
// SCRIPT_FILENAME on the Pi resolves through the symlink to // PHP resolves __DIR__ through symlinks to the realpath. This script
// $HOME/BirdNET-Pi/avian/api/birdnet-api.php - walk three dirs up to // lives at $HOME/BirdNET-Pi/avian/api/birdnet-api.php (served via the
// reach the install root, then point at scripts/birds.db. Lets a Pi // ${EXTRACTED}/avian symlink). dirname(..., 2) walks to the BirdNET-Pi
// installed under any username (the BirdNET-Pi installer uses $USER, // install root. Works under any username because we never bake the
// not a fixed name) work without editing this file. // home directory in. getenv('HOME') would resolve to /var/lib/caddy
$DB_PATH = dirname(__DIR__, 3) . '/scripts/birds.db'; // under PHP-FPM (BirdNET-Pi runs it as the caddy user), so it can't
// Fallback if the symlink layout ever changes - keeps the most common // be relied on.
// install path working even if SCRIPT_FILENAME oddities trip __DIR__. $DB_PATH = dirname(__DIR__, 2) . '/scripts/birds.db';
if (!file_exists($DB_PATH)) {
$alt = getenv('HOME') . '/BirdNET-Pi/scripts/birds.db';
if (file_exists($alt)) $DB_PATH = $alt;
}
if (!file_exists($DB_PATH)) { if (!file_exists($DB_PATH)) {
http_response_code(503); http_response_code(503);
+1 -1
View File
@@ -54,7 +54,7 @@ if (is_file($cutout) && filesize($cutout) > 1024) {
} }
// 3. Dynamic cache from a previous Wikipedia + rembg run. // 3. Dynamic cache from a previous Wikipedia + rembg run.
$cacheDir = getenv('HOME') . '/BirdSongs/Extracted/cutouts'; $cacheDir = dirname(__DIR__, 3) . '/BirdSongs/Extracted/cutouts';
$cachePath = "$cacheDir/$slug.png"; $cachePath = "$cacheDir/$slug.png";
if (is_file($cachePath) && filesize($cachePath) > 1024) { if (is_file($cachePath) && filesize($cachePath) > 1024) {
serve_png($cachePath); serve_png($cachePath);
+3 -3
View File
@@ -32,7 +32,7 @@ if ($sci !== '' && !preg_match('/^[A-Za-z]{2,40}(?:[ ][a-z]{2,40}){1,3}$/', $sci
exit; exit;
} }
$BY_DATE = getenv('HOME') . '/BirdSongs/Extracted/By_Date'; $BY_DATE = dirname(__DIR__, 3) . '/BirdSongs/Extracted/By_Date';
// ---- Direct-by-file lookup ---- // ---- Direct-by-file lookup ----
// Used by the atlas detail modal to play any past recording. // Used by the atlas detail modal to play any past recording.
@@ -96,7 +96,7 @@ if ($file !== '') {
// ---- Resolve scientific name → common name (with underscores) ---- // ---- Resolve scientific name → common name (with underscores) ----
function resolve_common(string $sci): ?string { function resolve_common(string $sci): ?string {
// Try birds.json first (preferred - has clean sci/com pairs). // Try birds.json first (preferred - has clean sci/com pairs).
foreach ([getenv('HOME') . '/BirdNET-Pi/scripts/birds.json'] as $f) { foreach ([dirname(__DIR__, 3) . '/BirdNET-Pi/scripts/birds.json'] as $f) {
if (is_readable($f)) { if (is_readable($f)) {
$list = json_decode((string)file_get_contents($f), true); $list = json_decode((string)file_get_contents($f), true);
if (is_array($list)) { if (is_array($list)) {
@@ -112,7 +112,7 @@ function resolve_common(string $sci): ?string {
} }
} }
// Fallback: labels.txt has "<sci>_<com>" or "<sci>, <com>" per line. // Fallback: labels.txt has "<sci>_<com>" or "<sci>, <com>" per line.
$labels = getenv('HOME') . '/BirdNET-Pi/model/labels.txt'; $labels = dirname(__DIR__, 3) . '/BirdNET-Pi/model/labels.txt';
if (is_readable($labels)) { if (is_readable($labels)) {
foreach (file($labels, FILE_IGNORE_NEW_LINES) as $line) { foreach (file($labels, FILE_IGNORE_NEW_LINES) as $line) {
if (strpos($line, '_') !== false) { if (strpos($line, '_') !== false) {
+3 -3
View File
@@ -31,7 +31,7 @@ if ($sci !== '' && !preg_match('/^[A-Za-z]{2,40}(?:[ ][a-z]{2,40}){1,3}$/', $sci
exit; exit;
} }
$BY_DATE = getenv('HOME') . '/BirdSongs/Extracted/By_Date'; $BY_DATE = dirname(__DIR__, 3) . '/BirdSongs/Extracted/By_Date';
// ---- Direct-by-file lookup ---- // ---- Direct-by-file lookup ----
// BirdNET-Pi writes <base>.mp3 and <base>.png next to each other under // BirdNET-Pi writes <base>.mp3 and <base>.png next to each other under
@@ -93,7 +93,7 @@ if ($file !== '') {
} }
function resolve_common(string $sci): ?string { function resolve_common(string $sci): ?string {
$f = getenv('HOME') . '/BirdNET-Pi/scripts/birds.json'; $f = dirname(__DIR__, 3) . '/BirdNET-Pi/scripts/birds.json';
if (is_readable($f)) { if (is_readable($f)) {
$list = json_decode((string)file_get_contents($f), true); $list = json_decode((string)file_get_contents($f), true);
if (is_array($list)) { if (is_array($list)) {
@@ -107,7 +107,7 @@ function resolve_common(string $sci): ?string {
} }
} }
} }
$labels = getenv('HOME') . '/BirdNET-Pi/model/labels.txt'; $labels = dirname(__DIR__, 3) . '/BirdNET-Pi/model/labels.txt';
if (is_readable($labels)) { if (is_readable($labels)) {
foreach (file($labels, FILE_IGNORE_NEW_LINES) as $line) { foreach (file($labels, FILE_IGNORE_NEW_LINES) as $line) {
if (strpos($line, '_') !== false) { if (strpos($line, '_') !== false) {