feat(photoprism): Add configurable originals_path via UCI/LuCI

- Add originals_path option to UCI config (default: /srv/photoprism/originals)
- Add set_config RPC method to update originals_path from LuCI
- Add Storage Settings section to LuCI dashboard
- Update LXC config to use configurable ORIGINALS_PATH
- Update get_stats to scan originals_path instead of data_path/originals
- Lyrion media_path already configurable via Settings page

Both services now support external mount points:
- PhotoPrism: /mnt/PHOTO for photos
- Lyrion: /mnt/MUSIC for music

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-06 11:34:09 +01:00
parent 7bcd09b81d
commit 363e2af9d6
4 changed files with 85 additions and 8 deletions

View File

@ -59,6 +59,19 @@ var callEmancipate = rpc.declare({
expect: {}
});
var callGetConfig = rpc.declare({
object: 'luci.photoprism',
method: 'get_config',
expect: {}
});
var callSetConfig = rpc.declare({
object: 'luci.photoprism',
method: 'set_config',
params: ['originals_path'],
expect: {}
});
return view.extend({
css: `
:root {
@ -225,11 +238,13 @@ return view.extend({
status: null,
stats: null,
config: null,
load: function() {
return Promise.all([
callStatus(),
callGetStats()
callGetStats(),
callGetConfig()
]);
},
@ -237,6 +252,7 @@ return view.extend({
var self = this;
this.status = data[0] || {};
this.stats = data[1] || {};
this.config = data[2] || {};
var container = E('div', { 'class': 'kiss-container' }, [
E('style', {}, this.css),
@ -245,9 +261,10 @@ return view.extend({
]);
poll.add(function() {
return Promise.all([callStatus(), callGetStats()]).then(function(results) {
return Promise.all([callStatus(), callGetStats(), callGetConfig()]).then(function(results) {
self.status = results[0] || {};
self.stats = results[1] || {};
self.config = results[2] || {};
self.updateView();
});
}, 10);
@ -376,6 +393,41 @@ return view.extend({
])
]),
// Settings
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'Storage Settings'),
E('div', { 'class': 'kiss-input-group' }, [
E('label', { 'style': 'color: var(--kiss-muted); margin-right: 10px;' }, 'Photos Path:'),
E('input', {
'class': 'kiss-input',
'type': 'text',
'id': 'originals-path',
'value': self.config.originals_path || '/srv/photoprism/originals',
'placeholder': '/mnt/PHOTO'
}),
E('button', {
'class': 'kiss-btn kiss-btn-secondary',
'click': function() {
var path = document.getElementById('originals-path').value;
if (!path) {
ui.addNotification(null, E('p', {}, 'Please enter a path'), 'warning');
return;
}
this.disabled = true;
this.textContent = 'Saving...';
var btn = this;
callSetConfig(path).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'Path updated. Restart PhotoPrism to apply.'), 'success');
}
btn.disabled = false;
btn.textContent = 'Save';
});
}
}, 'Save')
])
]),
// Emancipate
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'Public Exposure'),

View File

@ -17,6 +17,7 @@ method_get_config() {
json_add_boolean "enabled" "$([ "$(uci -q get ${CONFIG}.main.enabled)" = "1" ] && echo true || echo false)"
json_add_string "data_path" "$(uci -q get ${CONFIG}.main.data_path || echo '/srv/photoprism')"
json_add_string "originals_path" "$(uci -q get ${CONFIG}.main.originals_path || echo '/srv/photoprism/originals')"
json_add_string "http_port" "$(uci -q get ${CONFIG}.main.http_port || echo '2342')"
json_add_string "memory_limit" "$(uci -q get ${CONFIG}.main.memory_limit || echo '2G')"
json_add_string "admin_user" "$(uci -q get ${CONFIG}.admin.username || echo 'admin')"
@ -28,18 +29,33 @@ method_get_config() {
json_dump
}
# Method: set_config
method_set_config() {
local originals_path="$1"
if [ -n "$originals_path" ]; then
uci set ${CONFIG}.main.originals_path="$originals_path"
uci commit ${CONFIG}
fi
json_init
json_add_boolean "success" true
json_dump
}
# Method: get_stats
method_get_stats() {
local data_path=$(uci -q get ${CONFIG}.main.data_path || echo '/srv/photoprism')
local originals_path=$(uci -q get ${CONFIG}.main.originals_path || echo '/srv/photoprism/originals')
local originals_size="0"
local storage_size="0"
local photo_count=0
local video_count=0
if [ -d "${data_path}/originals" ]; then
originals_size=$(du -sh "${data_path}/originals" 2>/dev/null | cut -f1 || echo "0")
photo_count=$(find "${data_path}/originals" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.heic" -o -iname "*.raw" -o -iname "*.dng" \) 2>/dev/null | wc -l || echo 0)
video_count=$(find "${data_path}/originals" -type f \( -iname "*.mp4" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mkv" \) 2>/dev/null | wc -l || echo 0)
if [ -d "${originals_path}" ]; then
originals_size=$(du -sh "${originals_path}" 2>/dev/null | cut -f1 || echo "0")
photo_count=$(find "${originals_path}" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.heic" -o -iname "*.raw" -o -iname "*.dng" \) 2>/dev/null | wc -l || echo 0)
video_count=$(find "${originals_path}" -type f \( -iname "*.mp4" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mkv" \) 2>/dev/null | wc -l || echo 0)
fi
if [ -d "${data_path}/storage" ]; then
@ -162,6 +178,7 @@ case "$1" in
echo '{
"status": {},
"get_config": {},
"set_config": {"originals_path": "string"},
"get_stats": {},
"start": {},
"stop": {},
@ -178,6 +195,11 @@ case "$1" in
case "$2" in
status) method_status ;;
get_config) method_get_config ;;
set_config)
read -r input
originals_path=$(echo "$input" | jsonfilter -e '@.originals_path' 2>/dev/null)
method_set_config "$originals_path"
;;
get_stats) method_get_stats ;;
start) method_start ;;
stop) method_stop ;;

View File

@ -22,7 +22,8 @@
"uninstall",
"index",
"import",
"emancipate"
"emancipate",
"set_config"
]
},
"uci": ["photoprism"]

View File

@ -36,6 +36,7 @@ uci_set() { uci set "${CONFIG}.$1=$2" && uci commit "$CONFIG"; }
defaults() {
ENABLED=$(uci_get main.enabled 0)
DATA_PATH=$(uci_get main.data_path /srv/photoprism)
ORIGINALS_PATH=$(uci_get main.originals_path /srv/photoprism/originals)
HTTP_PORT=$(uci_get main.http_port 2342)
MEMORY_LIMIT=$(uci_get main.memory_limit 2G)
TIMEZONE=$(uci_get main.timezone Europe/Paris)
@ -123,7 +124,7 @@ lxc.net.0.type = none
lxc.mount.auto = proc:mixed sys:ro
# Bind mounts for data persistence
lxc.mount.entry = ${DATA_PATH}/originals opt/photoprism/originals none bind,create=dir 0 0
lxc.mount.entry = ${ORIGINALS_PATH} opt/photoprism/originals none bind,create=dir 0 0
lxc.mount.entry = ${DATA_PATH}/storage opt/photoprism/storage none bind,create=dir 0 0
lxc.mount.entry = ${DATA_PATH}/import opt/photoprism/import none bind,create=dir 0 0
@ -439,6 +440,7 @@ cmd_status() {
"videos": $videos,
"storage_used": "$storage_used",
"data_path": "$DATA_PATH",
"originals_path": "$ORIGINALS_PATH",
"domain": "$DOMAIN",
"admin_user": "$ADMIN_USER",
"face_recognition": $([ "$FACE_RECOGNITION" = "1" ] && echo "true" || echo "false"),