diff --git a/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json b/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json
index 05767e49..3b130f2a 100644
--- a/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json
+++ b/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json
@@ -3,8 +3,7 @@
"title": "DPI Dual-Stream",
"order": 45,
"action": {
- "type": "firstchildview",
- "recurse": true
+ "type": "firstchild"
},
"depends": {
"acl": ["luci-app-dpi-dual"],
diff --git a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js
index b849cb6c..9b0b8ae8 100644
--- a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js
+++ b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js
@@ -3,271 +3,176 @@
'require ui';
'require rpc';
'require poll';
-'require secubox/kiss-theme';
-var callStatus = rpc.declare({ object: 'luci.lyrion', method: 'status', expect: {} });
-var callLibraryStats = rpc.declare({ object: 'luci.lyrion', method: 'get_library_stats', expect: {} });
-var callInstall = rpc.declare({ object: 'luci.lyrion', method: 'install', expect: {} });
-var callStart = rpc.declare({ object: 'luci.lyrion', method: 'start', expect: {} });
-var callStop = rpc.declare({ object: 'luci.lyrion', method: 'stop', expect: {} });
-var callRestart = rpc.declare({ object: 'luci.lyrion', method: 'restart', expect: {} });
-var callRescan = rpc.declare({ object: 'luci.lyrion', method: 'rescan', expect: {} });
+var callStatus = rpc.declare({ object: 'luci.lyrion', method: 'status' });
+var callInstall = rpc.declare({ object: 'luci.lyrion', method: 'install' });
+var callStart = rpc.declare({ object: 'luci.lyrion', method: 'start' });
+var callStop = rpc.declare({ object: 'luci.lyrion', method: 'stop' });
+var callRescan = rpc.declare({ object: 'luci.lyrion', method: 'rescan' });
return view.extend({
- pollActive: true,
- libraryStats: null,
-
load: function() {
- return Promise.all([callStatus(), callLibraryStats()]);
+ return callStatus().catch(function() { return {}; });
},
startPolling: function() {
var self = this;
- this.pollActive = true;
- poll.add(L.bind(function() {
- if (!this.pollActive) return Promise.resolve();
- return Promise.all([callStatus(), callLibraryStats()]).then(L.bind(function(results) {
- this.updateStatus(results[0]);
- this.updateLibraryStats(results[1]);
- }, this));
- }, this), 3);
+ poll.add(function() {
+ return callStatus().then(function(s) {
+ self.updateUI(s);
+ });
+ }, 5);
},
- updateStatus: function(status) {
- var badge = document.getElementById('lyrion-status-badge');
+ updateUI: function(s) {
+ var badge = document.getElementById('status-badge');
if (badge) {
- badge.innerHTML = '';
- badge.appendChild(KissTheme.badge(status.running ? 'RUNNING' : 'STOPPED', status.running ? 'green' : 'red'));
- }
- },
-
- updateLibraryStats: function(stats) {
- if (!stats) return;
- this.libraryStats = stats;
-
- var statsEl = document.getElementById('lyrion-stats');
- if (statsEl) {
- statsEl.innerHTML = '';
- this.renderStats(stats).forEach(function(el) { statsEl.appendChild(el); });
+ badge.className = 'cbi-value-field';
+ badge.innerHTML = s.running
+ ? '● Running'
+ : '● Stopped';
}
- var scanEl = document.getElementById('lyrion-scan');
- if (scanEl) {
- scanEl.innerHTML = '';
- scanEl.appendChild(this.renderScanStatus(stats));
+ var stats = document.getElementById('library-stats');
+ if (stats && s.songs !== undefined) {
+ stats.textContent = s.songs + ' songs, ' + s.albums + ' albums, ' + s.artists + ' artists';
}
- },
- formatNumber: function(n) {
- if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
- if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
- return n.toString();
- },
-
- renderStats: function(stats) {
- var c = KissTheme.colors;
- return [
- KissTheme.stat(this.formatNumber(stats.songs || 0), 'Songs', c.purple),
- KissTheme.stat(this.formatNumber(stats.albums || 0), 'Albums', c.blue),
- KissTheme.stat(this.formatNumber(stats.artists || 0), 'Artists', c.green),
- KissTheme.stat(this.formatNumber(stats.genres || 0), 'Genres', c.orange)
- ];
- },
-
- renderScanStatus: function(stats) {
- if (stats.scanning) {
- var pct = stats.scan_total > 0 ? Math.round((stats.scan_progress / stats.scan_total) * 100) : 0;
- return E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
- E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
- E('span', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
- E('span', { 'class': 'spinning', 'style': 'font-size: 14px;' }, '⏳'),
- E('span', { 'style': 'font-weight: 600;' }, 'Scanning...')
- ]),
- E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' },
- (stats.scan_phase || 'Processing') + ' (' + stats.scan_progress + '/' + stats.scan_total + ')')
- ]),
- E('div', { 'style': 'height: 8px; background: var(--kiss-bg); border-radius: 4px; overflow: hidden;' }, [
- E('div', { 'style': 'height: 100%; width: ' + pct + '%; background: linear-gradient(90deg, var(--kiss-purple), var(--kiss-blue)); border-radius: 4px; transition: width 0.3s;' })
- ])
- ]);
- } else {
- return E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
- E('span', { 'style': 'display: flex; align-items: center; gap: 8px; color: var(--kiss-green);' }, [
- E('span', {}, '✓'),
- E('span', { 'style': 'font-weight: 600;' }, 'Library Ready')
- ]),
- E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'DB: ' + (stats.db_size || '0'))
- ]);
- }
- },
-
- renderControls: function(status) {
- return E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap;' }, [
- E('button', {
- 'class': 'kiss-btn kiss-btn-green',
- 'click': ui.createHandlerFn(this, 'handleStart'),
- 'disabled': status.running
- }, 'Start'),
- E('button', {
- 'class': 'kiss-btn kiss-btn-red',
- 'click': ui.createHandlerFn(this, 'handleStop'),
- 'disabled': !status.running
- }, 'Stop'),
- E('button', {
- 'class': 'kiss-btn',
- 'click': ui.createHandlerFn(this, 'handleRestart'),
- 'disabled': !status.running
- }, 'Restart'),
- E('button', {
- 'class': 'kiss-btn kiss-btn-blue',
- 'click': ui.createHandlerFn(this, 'handleRescan'),
- 'disabled': !status.running
- }, 'Rescan Library')
- ]);
- },
-
- renderServiceInfo: function(status) {
- var checks = [
- { label: 'Runtime', value: status.detected_runtime || 'auto' },
- { label: 'Port', value: status.port || '9000' },
- { label: 'Memory', value: status.memory_limit || '256M' },
- { label: 'Media Path', value: status.media_path || '/srv/media' }
- ];
-
- return E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' },
- checks.map(function(c) {
- return E('div', { 'style': 'display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--kiss-line);' }, [
- E('span', { 'style': 'color: var(--kiss-muted);' }, c.label),
- E('span', { 'style': 'font-family: monospace;' }, c.value)
- ]);
- })
- );
+ ['btn-start', 'btn-rescan'].forEach(function(id) {
+ var el = document.getElementById(id);
+ if (el) el.disabled = s.running;
+ });
+ var stopBtn = document.getElementById('btn-stop');
+ if (stopBtn) stopBtn.disabled = !s.running;
},
handleInstall: function() {
- var self = this;
- ui.showModal('Installing Lyrion', [
- E('p', { 'class': 'spinning' }, 'Installing Lyrion Music Server. This may take several minutes...')
+ ui.showModal(_('Installing'), [
+ E('p', { 'class': 'spinning' }, _('Installing Lyrion Music Server...'))
]);
callInstall().then(function(r) {
ui.hideModal();
if (r.success) {
- ui.addNotification(null, E('p', r.message || 'Installation started'));
- self.startPolling();
- window.location.reload();
+ ui.addNotification(null, E('p', _('Installation started')));
+ setTimeout(function() { location.reload(); }, 3000);
} else {
- ui.addNotification(null, E('p', 'Failed: ' + (r.error || 'Unknown error')), 'error');
+ ui.addNotification(null, E('p', r.error || _('Installation failed')), 'error');
}
});
},
handleStart: function() {
- ui.showModal('Starting...', [E('p', { 'class': 'spinning' }, 'Starting Lyrion...')]);
- callStart().then(function(r) {
- ui.hideModal();
- if (r.success) ui.addNotification(null, E('p', 'Lyrion started'));
+ callStart().then(function() {
+ ui.addNotification(null, E('p', _('Lyrion started')));
});
},
handleStop: function() {
- ui.showModal('Stopping...', [E('p', { 'class': 'spinning' }, 'Stopping Lyrion...')]);
- callStop().then(function(r) {
- ui.hideModal();
- if (r.success) ui.addNotification(null, E('p', 'Lyrion stopped'));
- });
- },
-
- handleRestart: function() {
- ui.showModal('Restarting...', [E('p', { 'class': 'spinning' }, 'Restarting Lyrion...')]);
- callRestart().then(function(r) {
- ui.hideModal();
- if (r.success) ui.addNotification(null, E('p', 'Lyrion restarted'));
+ callStop().then(function() {
+ ui.addNotification(null, E('p', _('Lyrion stopped')));
});
},
handleRescan: function() {
- callRescan().then(function(r) {
- if (r.success) ui.addNotification(null, E('p', 'Library rescan started'));
+ callRescan().then(function() {
+ ui.addNotification(null, E('p', _('Library rescan started')));
});
},
- render: function(data) {
- var status = data[0] || {};
- var stats = data[1] || {};
- this.libraryStats = stats;
+ render: function(status) {
+ var s = status || {};
- // Not installed view
- if (!status.installed) {
- var notInstalledContent = [
- E('div', { 'style': 'margin-bottom: 24px;' }, [
- E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [
- E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Lyrion Music Server'),
- KissTheme.badge('NOT INSTALLED', 'red')
- ]),
- E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'Self-hosted music streaming with Squeezebox compatibility')
- ]),
-
- KissTheme.card('Install', E('div', { 'style': 'text-align: center; padding: 40px;' }, [
- E('div', { 'style': 'font-size: 4rem; margin-bottom: 16px;' }, '🎵'),
- E('h3', { 'style': 'margin: 0 0 8px 0;' }, 'Lyrion Music Server'),
- E('p', { 'style': 'color: var(--kiss-muted); margin: 0 0 20px 0;' }, 'Self-hosted music streaming with Squeezebox compatibility.'),
- E('button', {
- 'class': 'kiss-btn kiss-btn-green',
- 'click': ui.createHandlerFn(this, 'handleInstall'),
- 'disabled': status.detected_runtime === 'none'
- }, 'Install Lyrion')
- ]))
- ];
-
- return KissTheme.wrap(notInstalledContent, 'admin/services/lyrion/overview');
+ // Not installed
+ if (!s.installed) {
+ return E('div', { 'class': 'cbi-map' }, [
+ E('h2', {}, _('Lyrion Music Server')),
+ E('div', { 'class': 'cbi-section' }, [
+ E('div', { 'style': 'text-align:center;padding:40px' }, [
+ E('p', { 'style': 'font-size:48px;margin:0' }, '🎵'),
+ E('h3', {}, _('Lyrion Music Server')),
+ E('p', {}, _('Self-hosted music streaming with Squeezebox compatibility.')),
+ E('button', {
+ 'class': 'cbi-button cbi-button-positive',
+ 'click': ui.createHandlerFn(this, 'handleInstall')
+ }, _('Install Lyrion'))
+ ])
+ ])
+ ]);
}
- // Installed view
+ // Installed
this.startPolling();
- var content = [
- // Header
- E('div', { 'style': 'margin-bottom: 24px;' }, [
- E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [
- E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Lyrion Music Server'),
- E('span', { 'id': 'lyrion-status-badge' }, [
- KissTheme.badge(status.running ? 'RUNNING' : 'STOPPED', status.running ? 'green' : 'red')
- ])
- ]),
- E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'Self-hosted music streaming')
+ return E('div', { 'class': 'cbi-map' }, [
+ E('h2', {}, [
+ '🎵 ',
+ _('Lyrion Music Server'),
+ ' ',
+ E('span', { 'id': 'status-badge', 'style': 'font-size:14px' },
+ s.running
+ ? E('span', { 'style': 'color:#4caf50;font-weight:600' }, '● Running')
+ : E('span', { 'style': 'color:#f44336;font-weight:600' }, '● Stopped')
+ )
]),
- // Stats
- E('div', { 'class': 'kiss-grid kiss-grid-4', 'id': 'lyrion-stats', 'style': 'margin: 20px 0;' }, this.renderStats(stats)),
-
- // Scan progress
- KissTheme.card('Library Status', E('div', { 'id': 'lyrion-scan' }, [this.renderScanStatus(stats)])),
-
- // Two-column layout
- E('div', { 'class': 'kiss-grid kiss-grid-2' }, [
- KissTheme.card('Service Info', this.renderServiceInfo(status)),
- KissTheme.card('Controls', this.renderControls(status))
- ]),
-
- // Web UI link
- status.running && status.web_accessible ? KissTheme.card('Web Interface',
- E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [
- E('div', { 'style': 'font-size: 2rem;' }, '🌐'),
- E('div', { 'style': 'flex: 1;' }, [
- E('div', { 'style': 'font-weight: 600;' }, 'Lyrion Web Interface'),
- E('div', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-purple);' }, status.web_url)
- ]),
- E('a', {
- 'href': status.web_url,
- 'target': '_blank',
- 'class': 'kiss-btn kiss-btn-blue',
- 'style': 'text-decoration: none;'
- }, 'Open')
+ E('div', { 'class': 'cbi-section' }, [
+ E('h3', {}, _('Controls')),
+ E('div', { 'class': 'cbi-value' }, [
+ E('button', {
+ 'id': 'btn-start',
+ 'class': 'cbi-button cbi-button-positive',
+ 'click': ui.createHandlerFn(this, 'handleStart'),
+ 'disabled': s.running,
+ 'style': 'margin-right:8px'
+ }, _('Start')),
+ E('button', {
+ 'id': 'btn-stop',
+ 'class': 'cbi-button cbi-button-negative',
+ 'click': ui.createHandlerFn(this, 'handleStop'),
+ 'disabled': !s.running,
+ 'style': 'margin-right:8px'
+ }, _('Stop')),
+ E('button', {
+ 'id': 'btn-rescan',
+ 'class': 'cbi-button',
+ 'click': ui.createHandlerFn(this, 'handleRescan'),
+ 'disabled': !s.running
+ }, _('Rescan Library'))
])
- ) : ''
- ];
+ ]),
- return KissTheme.wrap(content, 'admin/services/lyrion/overview');
+ E('div', { 'class': 'cbi-section' }, [
+ E('h3', {}, _('Service Info')),
+ E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr' }, [
+ E('td', { 'class': 'td', 'style': 'width:150px' }, _('Port')),
+ E('td', { 'class': 'td' }, String(s.port || 9000))
+ ]),
+ E('tr', { 'class': 'tr' }, [
+ E('td', { 'class': 'td' }, _('Runtime')),
+ E('td', { 'class': 'td' }, s.detected_runtime || 'auto')
+ ]),
+ E('tr', { 'class': 'tr' }, [
+ E('td', { 'class': 'td' }, _('Media Path')),
+ E('td', { 'class': 'td' }, s.media_path || '/srv/media')
+ ]),
+ E('tr', { 'class': 'tr' }, [
+ E('td', { 'class': 'td' }, _('Library')),
+ E('td', { 'class': 'td', 'id': 'library-stats' },
+ (s.songs || 0) + ' songs, ' + (s.albums || 0) + ' albums, ' + (s.artists || 0) + ' artists')
+ ])
+ ])
+ ]),
+
+ s.running && s.web_accessible ? E('div', { 'class': 'cbi-section' }, [
+ E('h3', {}, _('Web Interface')),
+ E('a', {
+ 'href': s.web_url || ('http://192.168.255.1:' + (s.port || 9000)),
+ 'target': '_blank',
+ 'class': 'cbi-button cbi-button-action'
+ }, _('Open Lyrion Web UI'))
+ ]) : ''
+ ]);
},
handleSaveApply: null,
diff --git a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js
index d190641c..2c953c07 100644
--- a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js
+++ b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js
@@ -2,7 +2,6 @@
'require view';
'require form';
'require uci';
-'require secubox/kiss-theme';
return view.extend({
load: function() {
@@ -12,67 +11,28 @@ return view.extend({
render: function() {
var m, s, o;
- m = new form.Map('lyrion', _('Lyrion Settings'),
- _('Configure Lyrion Music Server settings. Changes require service restart to take effect.'));
+ m = new form.Map('lyrion', _('Lyrion Settings'));
- s = m.section(form.TypedSection, 'lyrion', _('General Settings'));
+ s = m.section(form.TypedSection, 'lyrion');
s.anonymous = true;
s.addremove = false;
- o = s.option(form.Flag, 'enabled', _('Enabled'),
- _('Enable Lyrion Music Server'));
+ o = s.option(form.Flag, 'enabled', _('Enabled'));
o.default = '0';
- o.rmempty = false;
- o = s.option(form.ListValue, 'runtime', _('Container Runtime'),
- _('Select the container runtime to use'));
- o.value('auto', _('Auto-detect (LXC preferred)'));
- o.value('lxc', _('LXC Container'));
- o.value('docker', _('Docker'));
- o.default = 'auto';
-
- o = s.option(form.Value, 'port', _('Web UI Port'),
- _('Port for the Lyrion web interface'));
+ o = s.option(form.Value, 'port', _('Web UI Port'));
o.datatype = 'port';
o.default = '9000';
- o.placeholder = '9000';
- o = s.option(form.Value, 'data_path', _('Data Path'),
- _('Path to store Lyrion configuration and cache'));
- o.default = '/srv/lyrion';
- o.placeholder = '/srv/lyrion';
-
- o = s.option(form.Value, 'media_path', _('Media Path'),
- _('Path to your music library'));
+ o = s.option(form.Value, 'media_path', _('Media Path'));
o.default = '/srv/media';
- o.placeholder = '/srv/media';
- o = s.option(form.Value, 'memory_limit', _('Memory Limit'),
- _('Maximum memory for the container (e.g., 256M, 512M, 1G)'));
+ o = s.option(form.Value, 'data_path', _('Data Path'));
+ o.default = '/srv/lyrion';
+
+ o = s.option(form.Value, 'memory_limit', _('Memory Limit'));
o.default = '256M';
- o.placeholder = '256M';
- o = s.option(form.Value, 'timezone', _('Timezone'),
- _('Timezone for the container'));
- o.default = 'UTC';
- o.placeholder = 'UTC';
-
- o = s.option(form.Flag, 'wan_access', _('WAN Access'),
- _('Also open Lyrion ports on the WAN interface (remote access)'));
- o.default = '0';
- o.rmempty = false;
-
- o = s.option(form.Value, 'image', _('Docker Image'),
- _('Docker image to use (only for Docker runtime)'));
- o.default = 'ghcr.io/lms-community/lyrionmusicserver:stable';
- o.depends('runtime', 'docker');
-
- return m.render().then(function(node) {
- return KissTheme.wrap(node, 'admin/secubox/services/lyrion/settings');
- });
- },
-
- handleSaveApply: null,
- handleSave: null,
- handleReset: null
+ return m.render();
+ }
});
diff --git a/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion b/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion
index 874bfc27..24449bc6 100755
--- a/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion
+++ b/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion
@@ -1,300 +1,109 @@
#!/bin/sh
-# RPCD backend for Lyrion Music Server LuCI app
+# Lyrion Music Server RPCD handler
. /lib/functions.sh
CONFIG="lyrion"
-json_init() { echo "{"; }
-json_close() { echo "}"; }
-json_add_string() { echo "\"$1\": \"$2\""; }
-json_add_int() { echo "\"$1\": $2"; }
-json_add_bool() { [ "$2" = "1" ] && echo "\"$1\": true" || echo "\"$1\": false"; }
-
uci_get() { uci -q get ${CONFIG}.main.$1; }
-uci_set() { uci set ${CONFIG}.main.$1="$2" && uci commit ${CONFIG}; }
-# Get service status
get_status() {
- local enabled=$(uci_get enabled)
- local runtime=$(uci_get runtime)
local port=$(uci_get port)
local data_path=$(uci_get data_path)
local media_path=$(uci_get media_path)
- local memory_limit=$(uci_get memory_limit)
- local image=$(uci_get image)
+ port=${port:-9000}
- # Check if service is running
+ # Check running state
local running=0
- local container_status="stopped"
-
- if command -v lxc-info >/dev/null 2>&1; then
- if lxc-info -n lyrion -s 2>/dev/null | grep -q "RUNNING"; then
- running=1
- container_status="running"
- fi
- elif command -v docker >/dev/null 2>&1; then
- if docker ps --filter "name=secbx-lyrion" --format "{{.Names}}" 2>/dev/null | grep -q "secbx-lyrion"; then
- running=1
- container_status="running"
- fi
+ if command -v lxc-info >/dev/null 2>&1 && lxc-info -n lyrion -s 2>/dev/null | grep -q "RUNNING"; then
+ running=1
+ elif command -v docker >/dev/null 2>&1 && docker ps --filter "name=secbx-lyrion" -q 2>/dev/null | grep -q .; then
+ running=1
fi
- # Check if installed (LXC rootfs or Docker image exists)
+ # Check installed
local installed=0
- if [ -d "/srv/lxc/lyrion/rootfs" ] && [ -f "/srv/lxc/lyrion/rootfs/opt/lyrion/slimserver.pl" ]; then
- installed=1
- elif command -v docker >/dev/null 2>&1 && docker images --format "{{.Repository}}" 2>/dev/null | grep -q "lyrionmusicserver"; then
- installed=1
- fi
+ [ -d "/srv/lxc/lyrion/rootfs" ] && installed=1
+ [ "$installed" = "0" ] && command -v docker >/dev/null 2>&1 && docker images 2>/dev/null | grep -q lyrionmusicserver && installed=1
# Detect runtime
- local detected_runtime="none"
- if command -v lxc-start >/dev/null 2>&1; then
- detected_runtime="lxc"
- elif command -v docker >/dev/null 2>&1; then
- detected_runtime="docker"
- fi
+ local runtime="none"
+ command -v lxc-start >/dev/null 2>&1 && runtime="lxc"
+ [ "$runtime" = "none" ] && command -v docker >/dev/null 2>&1 && runtime="docker"
- # Check web UI accessibility
+ # Check web access
local web_accessible=0
+ [ "$running" = "1" ] && wget -q -O /dev/null --timeout=2 "http://127.0.0.1:${port}/" 2>/dev/null && web_accessible=1
+
+ # Get library stats if running
+ local songs=0 albums=0 artists=0
if [ "$running" = "1" ]; then
- wget -q -O /dev/null --timeout=2 "http://127.0.0.1:${port:-9000}/" 2>/dev/null && web_accessible=1
+ local resp=$(curl -s --max-time 2 "http://127.0.0.1:${port}/jsonrpc.js" \
+ -H "Content-Type: application/json" \
+ -d '{"id":1,"method":"slim.request","params":["",["serverstatus",0,0]]}' 2>/dev/null)
+ if [ -n "$resp" ]; then
+ songs=$(echo "$resp" | jsonfilter -e '@.result["info total songs"]' 2>/dev/null || echo 0)
+ albums=$(echo "$resp" | jsonfilter -e '@.result["info total albums"]' 2>/dev/null || echo 0)
+ artists=$(echo "$resp" | jsonfilter -e '@.result["info total artists"]' 2>/dev/null || echo 0)
+ fi
fi
cat </dev/null)
- local port=$(echo "$input" | jsonfilter -e '@.port' 2>/dev/null)
- local data_path=$(echo "$input" | jsonfilter -e '@.data_path' 2>/dev/null)
- local media_path=$(echo "$input" | jsonfilter -e '@.media_path' 2>/dev/null)
- local memory_limit=$(echo "$input" | jsonfilter -e '@.memory_limit' 2>/dev/null)
- local timezone=$(echo "$input" | jsonfilter -e '@.timezone' 2>/dev/null)
-
- [ -n "$runtime" ] && uci_set runtime "$runtime"
- [ -n "$port" ] && uci_set port "$port"
- [ -n "$data_path" ] && uci_set data_path "$data_path"
- [ -n "$media_path" ] && uci_set media_path "$media_path"
- [ -n "$memory_limit" ] && uci_set memory_limit "$memory_limit"
- [ -n "$timezone" ] && uci_set timezone "$timezone"
-
- echo '{"success": true}'
-}
-
-# Install Lyrion
do_install() {
if command -v lyrionctl >/dev/null 2>&1; then
lyrionctl install >/tmp/lyrion-install.log 2>&1 &
- echo '{"success": true, "message": "Installation started in background"}'
+ echo '{"success":true}'
else
- echo '{"success": false, "error": "lyrionctl not found"}'
+ echo '{"success":false,"error":"lyrionctl not found"}'
fi
}
-# Start service
do_start() {
- if [ -x /etc/init.d/lyrion ]; then
- /etc/init.d/lyrion start >/dev/null 2>&1
- uci_set enabled '1'
- echo '{"success": true}'
- else
- echo '{"success": false, "error": "Service not installed"}'
- fi
+ [ -x /etc/init.d/lyrion ] && /etc/init.d/lyrion start >/dev/null 2>&1
+ echo '{"success":true}'
}
-# Stop service
do_stop() {
- if [ -x /etc/init.d/lyrion ]; then
- /etc/init.d/lyrion stop >/dev/null 2>&1
- echo '{"success": true}'
- else
- echo '{"success": false, "error": "Service not installed"}'
- fi
+ [ -x /etc/init.d/lyrion ] && /etc/init.d/lyrion stop >/dev/null 2>&1
+ echo '{"success":true}'
}
-# Restart service
-do_restart() {
- if [ -x /etc/init.d/lyrion ]; then
- /etc/init.d/lyrion restart >/dev/null 2>&1
- echo '{"success": true}'
- else
- echo '{"success": false, "error": "Service not installed"}'
- fi
-}
-
-# Update container
-do_update() {
- if command -v lyrionctl >/dev/null 2>&1; then
- lyrionctl update >/tmp/lyrion-update.log 2>&1 &
- echo '{"success": true, "message": "Update started in background"}'
- else
- echo '{"success": false, "error": "lyrionctl not found"}'
- fi
-}
-
-# Get logs
-get_logs() {
- local lines=50
- local log_content=""
-
- if [ -f /srv/lxc/lyrion/rootfs/var/log/lyrion/server.log ]; then
- log_content=$(tail -n $lines /srv/lxc/lyrion/rootfs/var/log/lyrion/server.log 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|')
- elif [ -f /tmp/lyrion-install.log ]; then
- log_content=$(tail -n $lines /tmp/lyrion-install.log 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|')
- fi
-
- echo "{\"logs\": \"$log_content\"}"
-}
-
-# Get library stats from Lyrion API
-get_library_stats() {
- local port=$(uci_get port)
- port=${port:-9000}
-
- # Query Lyrion JSON-RPC API for server status
- local response=$(curl -s --max-time 3 "http://127.0.0.1:${port}/jsonrpc.js" \
- -H "Content-Type: application/json" \
- -d '{"id":1,"method":"slim.request","params":["", ["serverstatus", 0, 0]]}' 2>/dev/null)
-
- if [ -z "$response" ]; then
- echo '{"songs":0,"albums":0,"artists":0,"genres":0,"scanning":false,"scan_progress":0,"scan_total":0,"scan_phase":""}'
- return
- fi
-
- # Parse response using jsonfilter
- local songs=$(echo "$response" | jsonfilter -e '@.result["info total songs"]' 2>/dev/null || echo 0)
- local albums=$(echo "$response" | jsonfilter -e '@.result["info total albums"]' 2>/dev/null || echo 0)
- local artists=$(echo "$response" | jsonfilter -e '@.result["info total artists"]' 2>/dev/null || echo 0)
- local genres=$(echo "$response" | jsonfilter -e '@.result["info total genres"]' 2>/dev/null || echo 0)
- local rescan=$(echo "$response" | jsonfilter -e '@.result.rescan' 2>/dev/null || echo 0)
- local progress_done=$(echo "$response" | jsonfilter -e '@.result.progressdone' 2>/dev/null || echo 0)
- local progress_total=$(echo "$response" | jsonfilter -e '@.result.progresstotal' 2>/dev/null || echo 0)
- local progress_name=$(echo "$response" | jsonfilter -e '@.result.progressname' 2>/dev/null || echo "")
-
- # Get database size
- local db_size="0"
- if [ -f /srv/lyrion/cache/library.db ]; then
- db_size=$(ls -lh /srv/lyrion/cache/library.db 2>/dev/null | awk '{print $5}')
- fi
-
- local scanning="false"
- [ "$rescan" = "1" ] && scanning="true"
-
- cat </dev/null 2>&1
-
- echo '{"success": true, "message": "Rescan started"}'
+ -d '{"id":1,"method":"slim.request","params":["",["rescan","full"]]}' >/dev/null 2>&1
+ echo '{"success":true}'
}
-# RPCD list method
-list_methods() {
- cat <<'EOF'
-{
- "status": {},
- "get_config": {},
- "save_config": {"runtime": "string", "port": "string", "data_path": "string", "media_path": "string", "memory_limit": "string", "timezone": "string"},
- "install": {},
- "start": {},
- "stop": {},
- "restart": {},
- "update": {},
- "logs": {},
- "get_library_stats": {},
- "rescan": {}
-}
-EOF
-}
-
-# Main entry point
case "$1" in
list)
- list_methods
+ echo '{"status":{},"install":{},"start":{},"stop":{},"rescan":{}}'
;;
call)
case "$2" in
- status) get_status ;;
- get_config) get_config ;;
- save_config) save_config ;;
- install) do_install ;;
- start) do_start ;;
- stop) do_stop ;;
- restart) do_restart ;;
- update) do_update ;;
- logs) get_logs ;;
- get_library_stats) get_library_stats ;;
- rescan) do_rescan ;;
- *) echo '{"error": "Unknown method"}' ;;
+ status) get_status ;;
+ install) do_install ;;
+ start) do_start ;;
+ stop) do_stop ;;
+ rescan) do_rescan ;;
+ *) echo '{"error":"Unknown method"}' ;;
esac
;;
- *)
- echo '{"error": "Unknown command"}'
- ;;
esac
diff --git a/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json b/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json
index 5fe3751a..caa9089b 100644
--- a/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json
+++ b/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json
@@ -3,13 +3,13 @@
"description": "Grant access to Lyrion Music Server",
"read": {
"ubus": {
- "luci.lyrion": ["status", "get_config", "logs", "get_library_stats"]
+ "luci.lyrion": ["status"]
},
"uci": ["lyrion"]
},
"write": {
"ubus": {
- "luci.lyrion": ["install", "start", "stop", "restart", "update", "save_config", "rescan"]
+ "luci.lyrion": ["install", "start", "stop", "rescan"]
},
"uci": ["lyrion"]
}