feat(luci): Add luci-app-peertube dashboard for PeerTube video platform
- RPCD handler (luci.peertube) with 11 methods: status, start, stop, install, uninstall, update, logs, emancipate, live_enable, live_disable, configure_haproxy - ACL permissions for read (status, logs) and write operations - Dashboard features: - Install wizard with features and requirements - Service status display with access URL - Live streaming toggle with enable/disable buttons - HAProxy configuration status - Emancipate form for public exposure - Logs viewer with refresh Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dcc34c8bf6
commit
5c34ca2cae
@ -1,6 +1,6 @@
|
|||||||
# SecuBox UI & Theme History
|
# SecuBox UI & Theme History
|
||||||
|
|
||||||
_Last updated: 2026-02-14_
|
_Last updated: 2026-02-15_
|
||||||
|
|
||||||
1. **Unified Dashboard Refresh (2025-12-20)**
|
1. **Unified Dashboard Refresh (2025-12-20)**
|
||||||
- Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs.
|
- Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs.
|
||||||
@ -1712,3 +1712,40 @@ git checkout HEAD -- index.html
|
|||||||
- `_emancipate_mitmproxy()` now uses `mitmproxyctl sync-routes` instead of direct file manipulation
|
- `_emancipate_mitmproxy()` now uses `mitmproxyctl sync-routes` instead of direct file manipulation
|
||||||
- Ensures HAProxy and mitmproxy routes stay in sync
|
- Ensures HAProxy and mitmproxy routes stay in sync
|
||||||
- MetaBlogizer sites now properly routed through WAF
|
- MetaBlogizer sites now properly routed through WAF
|
||||||
|
|
||||||
|
### 2026-02-15: PeerTube Video Platform Package
|
||||||
|
- **Created secubox-app-peertube package** - federated video streaming platform
|
||||||
|
- LXC container with Debian Bookworm base image
|
||||||
|
- Stack: PostgreSQL 15, Redis 7, Node.js 18 LTS, FFmpeg
|
||||||
|
- `peertubectl` CLI with 15+ commands:
|
||||||
|
- Container: install, uninstall, update, start, stop, status, logs, shell
|
||||||
|
- User mgmt: admin create-user, admin reset-password, admin list-users
|
||||||
|
- Live: live enable, live disable, live status (RTMP on port 1935)
|
||||||
|
- Exposure: configure-haproxy, emancipate
|
||||||
|
- Backup: backup, restore (PostgreSQL dump)
|
||||||
|
- HAProxy integration with extended timeouts (3600s) for streaming/WebSocket
|
||||||
|
- Full emancipation workflow: DNS → Vortex → HAProxy → ACL → SSL → Mesh → Mitmproxy → Reload
|
||||||
|
- **UCI config sections:**
|
||||||
|
- main: enabled, data_path, videos_path, memory_limit, timezone
|
||||||
|
- server: hostname, port, https, webserver_hostname
|
||||||
|
- live: enabled, rtmp_port, max_duration, allow_replay, transcoding_enabled
|
||||||
|
- transcoding: enabled, threads, allow_audio_files, hls_enabled, resolutions
|
||||||
|
- storage: external_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key
|
||||||
|
- network: domain, haproxy, haproxy_ssl, firewall_wan
|
||||||
|
- admin: email, initial_password
|
||||||
|
|
||||||
|
### 2026-02-15: PeerTube LuCI Dashboard
|
||||||
|
- **Created luci-app-peertube package**
|
||||||
|
- RPCD handler (luci.peertube) with 11 methods:
|
||||||
|
- status, start, stop, install, uninstall, update, logs
|
||||||
|
- emancipate, live_enable, live_disable, configure_haproxy
|
||||||
|
- ACL permissions: read (status, logs), write (all actions)
|
||||||
|
- Menu entry: Admin → Services → PeerTube
|
||||||
|
- **Dashboard features:**
|
||||||
|
- Install wizard with features list and requirements
|
||||||
|
- Status badge (Running/Stopped) with access URL
|
||||||
|
- Service info: hostname, port, admin email
|
||||||
|
- Live streaming toggle with enable/disable buttons
|
||||||
|
- HAProxy configuration status with configure button
|
||||||
|
- Emancipate form for public exposure
|
||||||
|
- Logs viewer with refresh button
|
||||||
|
|||||||
@ -62,6 +62,28 @@ _Last updated: 2026-02-14 (WAF architecture configured)_
|
|||||||
- Gossip-based exposure config sync via secubox-p2p
|
- Gossip-based exposure config sync via secubox-p2p
|
||||||
- Created `luci-app-vortex-dns` dashboard
|
- Created `luci-app-vortex-dns` dashboard
|
||||||
|
|
||||||
|
### Just Completed (2026-02-15)
|
||||||
|
|
||||||
|
- **PeerTube Video Platform Package** — DONE (2026-02-15)
|
||||||
|
- Created `secubox-app-peertube` package for self-hosted video streaming
|
||||||
|
- LXC Debian Bookworm container with PostgreSQL 15, Redis 7, Node.js 18, FFmpeg
|
||||||
|
- `peertubectl` CLI with 15+ commands: install/uninstall/update/start/stop/status
|
||||||
|
- Live streaming support with RTMP port 1935
|
||||||
|
- HAProxy integration with extended timeouts (3600s) for streaming
|
||||||
|
- Emancipation workflow for public exposure
|
||||||
|
- User management: create-user, reset-password, list-users
|
||||||
|
- Backup/restore PostgreSQL database
|
||||||
|
- UCI config: main, server, live, transcoding, storage, network, admin sections
|
||||||
|
|
||||||
|
- **PeerTube LuCI Dashboard** — DONE (2026-02-15)
|
||||||
|
- Created `luci-app-peertube` package
|
||||||
|
- RPCD handler with 11 methods: status, start, stop, install, uninstall, update, logs, emancipate, live_enable, live_disable, configure_haproxy
|
||||||
|
- Dashboard with install wizard, status display, service controls
|
||||||
|
- Live streaming toggle with firewall integration
|
||||||
|
- HAProxy configuration button
|
||||||
|
- Emancipate form for public exposure
|
||||||
|
- Logs viewer with refresh
|
||||||
|
|
||||||
### Just Completed (2026-02-14)
|
### Just Completed (2026-02-14)
|
||||||
|
|
||||||
- **mitmproxy WAF Wildcard Route Priority Fix** — DONE (2026-02-14)
|
- **mitmproxy WAF Wildcard Route Priority Fix** — DONE (2026-02-14)
|
||||||
|
|||||||
27
package/secubox/luci-app-peertube/Makefile
Normal file
27
package/secubox/luci-app-peertube/Makefile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI PeerTube Video Platform
|
||||||
|
LUCI_DEPENDS:=+luci-base +secubox-app-peertube
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_LICENSE:=AGPL-3.0
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
define Package/luci-app-peertube/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||||
|
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-peertube.json $(1)/usr/share/luci/menu.d/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||||
|
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-peertube.json $(1)/usr/share/rpcd/acl.d/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||||
|
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.peertube $(1)/usr/libexec/rpcd/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/peertube
|
||||||
|
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/peertube/overview.js $(1)/www/luci-static/resources/view/peertube/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources/peertube
|
||||||
|
$(INSTALL_DATA) ./htdocs/luci-static/resources/peertube/api.js $(1)/www/luci-static/resources/peertube/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,luci-app-peertube))
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
'use strict';
|
||||||
|
'require rpc';
|
||||||
|
|
||||||
|
return L.Class.extend({
|
||||||
|
status: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'status',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
start: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'start',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
stop: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'stop',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
install: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'install',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
uninstall: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'uninstall',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
update: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'update',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
logs: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'logs',
|
||||||
|
params: ['lines'],
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
emancipate: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'emancipate',
|
||||||
|
params: ['domain'],
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
liveEnable: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'live_enable',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
liveDisable: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'live_disable',
|
||||||
|
expect: { }
|
||||||
|
}),
|
||||||
|
|
||||||
|
configureHaproxy: rpc.declare({
|
||||||
|
object: 'luci.peertube',
|
||||||
|
method: 'configure_haproxy',
|
||||||
|
expect: { }
|
||||||
|
})
|
||||||
|
});
|
||||||
@ -0,0 +1,318 @@
|
|||||||
|
'use strict';
|
||||||
|
'require view';
|
||||||
|
'require dom';
|
||||||
|
'require poll';
|
||||||
|
'require ui';
|
||||||
|
'require uci';
|
||||||
|
'require form';
|
||||||
|
'require peertube.api as api';
|
||||||
|
|
||||||
|
return view.extend({
|
||||||
|
handleAction: function(action, args) {
|
||||||
|
var self = this;
|
||||||
|
var btn = document.activeElement;
|
||||||
|
|
||||||
|
ui.showModal(_('Please wait...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Processing request...'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
var promise;
|
||||||
|
switch(action) {
|
||||||
|
case 'start':
|
||||||
|
promise = api.start();
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
promise = api.stop();
|
||||||
|
break;
|
||||||
|
case 'install':
|
||||||
|
promise = api.install();
|
||||||
|
break;
|
||||||
|
case 'uninstall':
|
||||||
|
if (!confirm(_('This will remove the PeerTube container. Video data will be preserved. Continue?')))
|
||||||
|
return ui.hideModal();
|
||||||
|
promise = api.uninstall();
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
promise = api.update();
|
||||||
|
break;
|
||||||
|
case 'live_enable':
|
||||||
|
promise = api.liveEnable();
|
||||||
|
break;
|
||||||
|
case 'live_disable':
|
||||||
|
promise = api.liveDisable();
|
||||||
|
break;
|
||||||
|
case 'configure_haproxy':
|
||||||
|
promise = api.configureHaproxy();
|
||||||
|
break;
|
||||||
|
case 'emancipate':
|
||||||
|
var domain = args;
|
||||||
|
if (!domain) {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', _('Domain is required')), 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise = api.emancipate(domain);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ui.hideModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (res && res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('Action completed')), 'success');
|
||||||
|
self.load().then(function(data) {
|
||||||
|
dom.content(document.querySelector('#peertube-content'), self.renderContent(data));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Action failed')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(e) {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return Promise.all([
|
||||||
|
api.status(),
|
||||||
|
uci.load('peertube')
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderInstallWizard: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('h3', {}, _('PeerTube Video Platform')),
|
||||||
|
E('p', {}, _('PeerTube is a free, decentralized and federated video streaming platform. Videos are stored locally and can be shared across the Fediverse using ActivityPub.')),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('h4', {}, _('Features')),
|
||||||
|
E('ul', {}, [
|
||||||
|
E('li', {}, _('Self-hosted video hosting with HLS streaming')),
|
||||||
|
E('li', {}, _('Live streaming with RTMP ingest')),
|
||||||
|
E('li', {}, _('Automatic transcoding to multiple resolutions')),
|
||||||
|
E('li', {}, _('Federation via ActivityPub protocol')),
|
||||||
|
E('li', {}, _('User management and access controls')),
|
||||||
|
E('li', {}, _('WebTorrent for distributed video delivery'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('h4', {}, _('Requirements')),
|
||||||
|
E('ul', {}, [
|
||||||
|
E('li', {}, _('Minimum 2GB RAM recommended')),
|
||||||
|
E('li', {}, _('10GB storage for system + additional for videos')),
|
||||||
|
E('li', {}, _('Network access for container downloads'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-page-actions' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button cbi-button-positive',
|
||||||
|
'click': function() { self.handleAction('install'); }
|
||||||
|
}, _('Install PeerTube'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderStatusBadge: function(running) {
|
||||||
|
var color = running === 'true' ? '#4CAF50' : '#f44336';
|
||||||
|
var text = running === 'true' ? _('Running') : _('Stopped');
|
||||||
|
return E('span', {
|
||||||
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + color
|
||||||
|
}, text);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderContent: function(data) {
|
||||||
|
var self = this;
|
||||||
|
var status = data[0] || {};
|
||||||
|
|
||||||
|
if (status.container_state === 'not_installed') {
|
||||||
|
return this.renderInstallWizard();
|
||||||
|
}
|
||||||
|
|
||||||
|
var running = status.running === 'true';
|
||||||
|
var liveEnabled = status.live_enabled === '1';
|
||||||
|
var haproxyConfigured = status.haproxy === '1';
|
||||||
|
var domain = status.domain || '';
|
||||||
|
|
||||||
|
var accessUrl = '';
|
||||||
|
if (running) {
|
||||||
|
if (domain && haproxyConfigured) {
|
||||||
|
accessUrl = (status.https === '1' ? 'https://' : 'http://') + domain;
|
||||||
|
} else {
|
||||||
|
accessUrl = 'http://192.168.255.1:' + (status.port || '9000');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('h3', {}, _('PeerTube Video Platform')),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Status')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, this.renderStatusBadge(status.running))
|
||||||
|
]),
|
||||||
|
|
||||||
|
running && accessUrl ? E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Access URL')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('a', { 'href': accessUrl, 'target': '_blank' }, accessUrl)
|
||||||
|
])
|
||||||
|
]) : '',
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Hostname')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, status.hostname || '-')
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Port')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, status.port || '9000')
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Live Streaming')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('span', {
|
||||||
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (liveEnabled ? '#4CAF50' : '#9e9e9e')
|
||||||
|
}, liveEnabled ? _('Enabled') : _('Disabled')),
|
||||||
|
' ',
|
||||||
|
liveEnabled ?
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button',
|
||||||
|
'click': function() { self.handleAction('live_disable'); }
|
||||||
|
}, _('Disable')) :
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button',
|
||||||
|
'click': function() { self.handleAction('live_enable'); }
|
||||||
|
}, _('Enable'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('HAProxy')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('span', {
|
||||||
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (haproxyConfigured ? '#4CAF50' : '#9e9e9e')
|
||||||
|
}, haproxyConfigured ? _('Configured') : _('Not configured')),
|
||||||
|
' ',
|
||||||
|
!haproxyConfigured ? E('button', {
|
||||||
|
'class': 'btn cbi-button',
|
||||||
|
'click': function() { self.handleAction('configure_haproxy'); }
|
||||||
|
}, _('Configure')) : ''
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, domain || _('Not configured'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Admin Email')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, status.admin_email || '-')
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('hr'),
|
||||||
|
|
||||||
|
E('h4', {}, _('Service Controls')),
|
||||||
|
E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom: 20px;' }, [
|
||||||
|
running ?
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button cbi-button-negative',
|
||||||
|
'click': function() { self.handleAction('stop'); }
|
||||||
|
}, _('Stop')) :
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button cbi-button-positive',
|
||||||
|
'click': function() { self.handleAction('start'); }
|
||||||
|
}, _('Start')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button',
|
||||||
|
'click': function() { self.handleAction('update'); }
|
||||||
|
}, _('Update')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button cbi-button-negative',
|
||||||
|
'click': function() { self.handleAction('uninstall'); }
|
||||||
|
}, _('Uninstall'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('hr'),
|
||||||
|
|
||||||
|
E('h4', {}, _('Emancipate (Public Exposure)')),
|
||||||
|
E('p', {}, _('Make PeerTube publicly accessible with SSL certificate and DNS configuration.')),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('input', {
|
||||||
|
'type': 'text',
|
||||||
|
'id': 'emancipate-domain',
|
||||||
|
'class': 'cbi-input-text',
|
||||||
|
'placeholder': 'peertube.example.com',
|
||||||
|
'value': domain
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-page-actions' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button cbi-button-action',
|
||||||
|
'click': function() {
|
||||||
|
var domainInput = document.getElementById('emancipate-domain');
|
||||||
|
self.handleAction('emancipate', domainInput.value);
|
||||||
|
}
|
||||||
|
}, _('Emancipate'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('hr'),
|
||||||
|
|
||||||
|
E('h4', {}, _('Logs')),
|
||||||
|
E('div', { 'id': 'peertube-logs' }, [
|
||||||
|
E('pre', {
|
||||||
|
'style': 'background:#1e1e1e;color:#d4d4d4;padding:10px;max-height:300px;overflow:auto;font-size:12px;border-radius:4px;'
|
||||||
|
}, _('Loading logs...'))
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-page-actions' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button',
|
||||||
|
'click': function() {
|
||||||
|
api.logs(100).then(function(res) {
|
||||||
|
var logsEl = document.querySelector('#peertube-logs pre');
|
||||||
|
if (logsEl) {
|
||||||
|
logsEl.textContent = res.logs || _('No logs available');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, _('Refresh Logs'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(data) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var content = E('div', { 'id': 'peertube-content' }, this.renderContent(data));
|
||||||
|
|
||||||
|
// Load logs initially
|
||||||
|
api.logs(50).then(function(res) {
|
||||||
|
var logsEl = document.querySelector('#peertube-logs pre');
|
||||||
|
if (logsEl) {
|
||||||
|
logsEl.textContent = res.logs || _('No logs available');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Poll for status updates
|
||||||
|
poll.add(function() {
|
||||||
|
return api.status().then(function(status) {
|
||||||
|
var statusBadge = document.querySelector('.cbi-value-field span');
|
||||||
|
// Status badge is the first one - update if running state changed
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleSave: null,
|
||||||
|
handleReset: null
|
||||||
|
});
|
||||||
@ -0,0 +1,338 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# RPCD backend for PeerTube LuCI app
|
||||||
|
|
||||||
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
|
PEERTUBECTL="/usr/sbin/peertubectl"
|
||||||
|
|
||||||
|
# Helper to get UCI value
|
||||||
|
uci_get() {
|
||||||
|
local section="$1"
|
||||||
|
local option="$2"
|
||||||
|
local default="$3"
|
||||||
|
local val
|
||||||
|
val=$(uci -q get "peertube.${section}.${option}")
|
||||||
|
echo "${val:-$default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get container status
|
||||||
|
get_container_status() {
|
||||||
|
local state="not_installed"
|
||||||
|
local running="false"
|
||||||
|
local lxc_info=""
|
||||||
|
|
||||||
|
if [ -d "/srv/lxc/peertube" ]; then
|
||||||
|
state="installed"
|
||||||
|
lxc_info=$(lxc-info -n peertube 2>/dev/null)
|
||||||
|
if echo "$lxc_info" | grep -q "State:.*RUNNING"; then
|
||||||
|
running="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$state $running"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: status
|
||||||
|
method_status() {
|
||||||
|
local enabled hostname port https live_enabled
|
||||||
|
local container_state running
|
||||||
|
local info
|
||||||
|
|
||||||
|
enabled=$(uci_get main enabled 0)
|
||||||
|
hostname=$(uci_get server hostname "peertube.local")
|
||||||
|
port=$(uci_get server port "9000")
|
||||||
|
https=$(uci_get server https "1")
|
||||||
|
live_enabled=$(uci_get live enabled "0")
|
||||||
|
|
||||||
|
info=$(get_container_status)
|
||||||
|
container_state=$(echo "$info" | awk '{print $1}')
|
||||||
|
running=$(echo "$info" | awk '{print $2}')
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_string "enabled" "$enabled"
|
||||||
|
json_add_string "container_state" "$container_state"
|
||||||
|
json_add_string "running" "$running"
|
||||||
|
json_add_string "hostname" "$hostname"
|
||||||
|
json_add_string "port" "$port"
|
||||||
|
json_add_string "https" "$https"
|
||||||
|
json_add_string "live_enabled" "$live_enabled"
|
||||||
|
|
||||||
|
# Get configured domain if emancipated
|
||||||
|
local domain haproxy
|
||||||
|
domain=$(uci_get network domain "")
|
||||||
|
haproxy=$(uci_get network haproxy "0")
|
||||||
|
json_add_string "domain" "$domain"
|
||||||
|
json_add_string "haproxy" "$haproxy"
|
||||||
|
|
||||||
|
# Get admin email
|
||||||
|
local admin_email
|
||||||
|
admin_email=$(uci_get admin email "admin@localhost")
|
||||||
|
json_add_string "admin_email" "$admin_email"
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: start
|
||||||
|
method_start() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL start 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "PeerTube started successfully"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: stop
|
||||||
|
method_stop() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL stop 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "PeerTube stopped successfully"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: install
|
||||||
|
method_install() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL install 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "PeerTube installed successfully"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: uninstall
|
||||||
|
method_uninstall() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL uninstall 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "PeerTube uninstalled successfully"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: update
|
||||||
|
method_update() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL update 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "PeerTube updated successfully"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: logs
|
||||||
|
method_logs() {
|
||||||
|
local lines="${1:-50}"
|
||||||
|
local output
|
||||||
|
|
||||||
|
if [ -d "/srv/lxc/peertube" ]; then
|
||||||
|
output=$($PEERTUBECTL logs "$lines" 2>&1 | tail -n "$lines")
|
||||||
|
else
|
||||||
|
output="Container not installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_string "logs" "$output"
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: emancipate
|
||||||
|
method_emancipate() {
|
||||||
|
read -r input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var domain domain
|
||||||
|
|
||||||
|
if [ -z "$domain" ]; then
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "Domain is required"
|
||||||
|
json_dump
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL emancipate "$domain" 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "PeerTube emancipated to $domain"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: live_enable
|
||||||
|
method_live_enable() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL live enable 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "Live streaming enabled"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: live_disable
|
||||||
|
method_live_disable() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL live disable 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "Live streaming disabled"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method: configure_haproxy
|
||||||
|
method_configure_haproxy() {
|
||||||
|
local output
|
||||||
|
output=$($PEERTUBECTL configure-haproxy 2>&1)
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
json_init
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "HAProxy configured for PeerTube"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$output"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# List available methods
|
||||||
|
list_methods() {
|
||||||
|
json_init
|
||||||
|
json_add_object "status"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "start"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "stop"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "install"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "uninstall"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "update"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "logs"
|
||||||
|
json_add_int "lines" 50
|
||||||
|
json_close_object
|
||||||
|
json_add_object "emancipate"
|
||||||
|
json_add_string "domain" ""
|
||||||
|
json_close_object
|
||||||
|
json_add_object "live_enable"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "live_disable"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "configure_haproxy"
|
||||||
|
json_close_object
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main dispatcher
|
||||||
|
case "$1" in
|
||||||
|
list)
|
||||||
|
list_methods
|
||||||
|
;;
|
||||||
|
call)
|
||||||
|
case "$2" in
|
||||||
|
status)
|
||||||
|
method_status
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
method_start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
method_stop
|
||||||
|
;;
|
||||||
|
install)
|
||||||
|
method_install
|
||||||
|
;;
|
||||||
|
uninstall)
|
||||||
|
method_uninstall
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
method_update
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
read -r input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var lines lines
|
||||||
|
method_logs "${lines:-50}"
|
||||||
|
;;
|
||||||
|
emancipate)
|
||||||
|
method_emancipate
|
||||||
|
;;
|
||||||
|
live_enable)
|
||||||
|
method_live_enable
|
||||||
|
;;
|
||||||
|
live_disable)
|
||||||
|
method_live_disable
|
||||||
|
;;
|
||||||
|
configure_haproxy)
|
||||||
|
method_configure_haproxy
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"error":"Method not found"}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"error":"Invalid action"}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"admin/services/peertube": {
|
||||||
|
"title": "PeerTube",
|
||||||
|
"order": 65,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "peertube/overview"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"acl": ["luci-app-peertube"],
|
||||||
|
"uci": {"peertube": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"luci-app-peertube": {
|
||||||
|
"description": "Grant access to PeerTube management",
|
||||||
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci.peertube": ["status", "logs"]
|
||||||
|
},
|
||||||
|
"uci": ["peertube"]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"ubus": {
|
||||||
|
"luci.peertube": ["start", "stop", "install", "uninstall", "update", "emancipate", "live_enable", "live_disable", "configure_haproxy"]
|
||||||
|
},
|
||||||
|
"uci": ["peertube"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user