feat(v0.23.0): Matrix homeserver, SaaS Relay CDN caching, Media Hub dashboard

Matrix Homeserver (Conduit):
- E2EE mesh messaging using Conduit v0.10.12 in LXC container
- matrixctl CLI: install/uninstall, user/room management, federation
- luci-app-matrix: status cards, user form, emancipate, mesh publish
- RPCD backend with 17 methods
- Identity (DID) integration and P2P mesh publication

SaaS Relay CDN Caching & Session Replay:
- CDN cache profiles: minimal, gandalf (default), aggressive
- Session replay modes: shared, per_user, master
- saasctl cache/session commands for management
- Enhanced mitmproxy addon (415 lines) with response caching

Media Services Hub Dashboard:
- Unified dashboard at /admin/services/media-hub
- Category-organized cards (streaming, conferencing, apps, etc.)
- Service status indicators with start/stop/restart controls
- RPCD backend querying 8 media services

Also includes:
- HexoJS static upload workflow and multi-user auth
- Jitsi config.js Promise handling fix
- Feed package updates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-20 11:44:26 +01:00
parent b6747c197e
commit 58220065b5
174 changed files with 9359 additions and 261 deletions

View File

@ -1,6 +1,6 @@
# SecuBox UI & Theme History
_Last updated: 2026-02-19_
_Last updated: 2026-02-20_
1. **Unified Dashboard Refresh (2025-12-20)**
- Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs.
@ -2490,3 +2490,110 @@ git checkout HEAD -- index.html
- UCI config section: `config recording 'recording'` with enabled/format/retention_days
- Menu entry: Services → VoIP PBX → Recordings
- Note: OVH SIP trunk registration requires correct password from OVH Manager
41. **Matrix Homeserver Integration (2026-02-19)**
- Created `secubox-app-matrix` package for Conduit Matrix server:
- Lightweight Rust-based homeserver (~15MB binary, ~500MB RAM)
- LXC Debian Bookworm container with pre-built ARM64/x86_64 binaries
- E2EE messaging with federation support
- RocksDB database for performance
- New `matrixctl` CLI commands:
- `install`, `uninstall`, `update` - Container lifecycle
- `start`, `stop`, `restart`, `status` - Service control
- `user add/del/passwd/list` - User management
- `room list/create/delete` - Room management
- `federation test/status` - Federation testing
- `configure-haproxy`, `emancipate <domain>` - Punk Exposure
- `identity link/unlink` - DID integration
- `mesh publish/unpublish` - P2P service registry
- `backup`, `restore` - Data persistence
- Created `luci-app-matrix` LuCI dashboard:
- Install wizard for new deployments
- Status card with running state, version, features
- Service controls (Start/Stop/Update/Uninstall)
- User management form
- Emancipate form for public exposure
- Identity integration section (DID linking)
- Mesh publication toggle
- Logs viewer with refresh
- RPCD methods (18 total): status, logs, start, stop, install, uninstall, update,
emancipate, configure_haproxy, user_add, user_del, federation_status,
identity_status, identity_link, identity_unlink, mesh_status, mesh_publish, mesh_unpublish
- UCI config sections: main, server, federation, admin, database, network, identity, mesh
- v1.0.0 roadmap: Matrix integration complements VoIP/Jabber for full mesh communication stack
- Files created:
- `package/secubox/secubox-app-matrix/` (Makefile, UCI, init.d, matrixctl)
- `package/secubox/luci-app-matrix/` (RPCD, ACL, menu, overview.js, api.js)
25. **HexoJS KISS Static Upload & Multi-User Authentication (2026-02-20)**
- Added multi-user/multi-instance authentication:
- HAProxy Basic Auth integration with apr1 password hashing
- `hexoctl user add/del/passwd/list/grant/revoke` commands
- `hexoctl auth enable/disable/status/haproxy` commands
- UCI config sections for users and per-instance auth
- KISS Static Upload workflow (no Hexo build process):
- `hexoctl static create <name>` - Create static-only site
- `hexoctl static upload <file> [inst]` - Upload HTML/CSS/JS directly
- `hexoctl static publish [inst]` - Copy to /www/ for uhttpd serving
- `hexoctl static quick <file> [inst]` - One-command upload + publish
- `hexoctl static list [inst]` - List static files
- `hexoctl static serve [inst]` - Python/busybox httpd server
- `hexoctl static delete <name>` - Delete static instance
- Goal: Fast publishing experiment (KISSS) for HTML files without Node.js/Hexo build
- Tested and verified on router with immediate uhttpd serving
26. **SaaS Relay CDN Caching & Session Replay (2026-02-20)**
- Enhanced `secubox-app-saas-relay` with CDN caching layer and multi-user session replay
- CDN Cache features:
- Configurable cache profiles: minimal, gandalf (default), aggressive
- Profile-based caching rules (content types, TTL, max size, exclude patterns)
- File-based cache storage with metadata for expiry tracking
- Cache-Control header respect (max-age, no-store, private)
- `X-SaaSRelay-Cache: HIT/MISS` header for debugging
- Session Replay features:
- Three modes: shared (default), per_user, master
- Shared mode: All SecuBox users share same session cookies
- Per-user mode: Each user gets their own session storage
- Master mode: One user (admin) authenticates, others replay their session
- New CLI commands:
- `saasctl cache {status|clear|profile|enable|disable}` - Cache management
- `saasctl session {status|mode|master|enable|disable}` - Session management
- Enhanced mitmproxy addon (415 lines) with:
- Response caching before network request
- Cache key generation with SHA-256 URL hashing
- Per-user session file storage with fallback to master
- Activity logging with emoji indicators
- UCI config sections added: cache, cache_profile (3), session_replay
- Config JSON export for container: config.json + services.json
27. **Matrix Homeserver (Conduit) Integration (2026-02-20)**
- E2EE mesh messaging using Conduit Matrix homeserver (v0.10.12)
- `secubox-app-matrix` package with LXC container management:
- Pre-built ARM64 Conduit binary from GitLab artifacts
- Debian Bookworm base, RocksDB backend
- 512MB RAM limit, persistent data in /srv/matrix
- `matrixctl` CLI tool (1279 lines):
- Container: install, uninstall, update, check, shell
- Service: start, stop, restart, status, logs
- Users: add, del, passwd, list
- Rooms: list, create, delete
- Federation: test, status
- Exposure: configure-haproxy, emancipate
- Identity: link, unlink, status (DID integration)
- Mesh: publish, unpublish
- Backup: backup, restore
- `luci-app-matrix` dashboard:
- Install wizard for first-time setup
- Status cards with feature badges
- Service controls
- User management form
- Emancipate (public exposure) form
- Identity/DID linking section
- P2P mesh publication toggle
- Logs viewer with refresh
- RPCD methods (17 total): status, logs, start, stop, install, uninstall, update,
emancipate, configure_haproxy, user_add, user_del, federation_status,
identity_status, identity_link, identity_unlink, mesh_status, mesh_publish, mesh_unpublish
- UCI config sections: main, server, federation, admin, database, network, identity, mesh
- Matrix API responding with v1.1-v1.12 support
- Files: `package/secubox/secubox-app-matrix/`, `package/secubox/luci-app-matrix/`

View File

@ -188,8 +188,8 @@ All cloud providers are **opt-in**. Offline resilience: local tier always active
- [x] Config Advisor (ANSSI prep) — Done 2026-02-07
- [ ] P2P Mesh Intelligence
- [ ] Factory auto-provisioning
- [ ] VoIP integration
- [ ] Matrix integration
- [x] VoIP integration — Done 2026-02-19
- [x] Matrix integration — Done 2026-02-19
### v1.1+ — Extended Mesh

View File

@ -1,6 +1,6 @@
# Work In Progress (Claude)
_Last updated: 2026-02-19 (v0.22.0 - VoIP + Jabber Integration)_
_Last updated: 2026-02-20 (v0.23.0 - Matrix + SaaS Relay + Media Hub)_
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
@ -62,6 +62,52 @@ _Last updated: 2026-02-19 (v0.22.0 - VoIP + Jabber Integration)_
- Gossip-based exposure config sync via secubox-p2p
- Created `luci-app-vortex-dns` dashboard
### Just Completed (2026-02-20)
- **Matrix Homeserver (Conduit)** — DONE (2026-02-20)
- E2EE mesh messaging server using Conduit Matrix homeserver
- LXC container with pre-built ARM64 Conduit binary (0.10.12)
- `matrixctl` CLI (1279 lines): install/uninstall/update, user management, rooms, federation
- `luci-app-matrix` dashboard with:
- Install wizard, status cards, feature badges
- Service controls (Start/Stop/Update/Uninstall)
- User management form
- Emancipate (public exposure) with HAProxy + SSL
- Identity (DID) integration section
- P2P mesh publication toggle
- Logs viewer
- RPCD backend with 17 methods
- UCI config: main, server, federation, admin, database, network, identity, mesh
- Tested and verified on router (all checks pass, API responding)
- **SaaS Relay CDN Caching & Session Replay** — DONE (2026-02-20)
- CDN cache with configurable profiles: minimal, gandalf, aggressive
- Session replay modes: shared (default), per_user, master
- New CLI commands: `saasctl cache {status|clear|profile|enable|disable}`
- New CLI commands: `saasctl session {status|mode|master|enable|disable}`
- Enhanced mitmproxy addon (415 lines) with response caching
- UCI config sections: cache, cache_profile (3), session_replay
- Config JSON export: config.json + services.json
- **Media Services Hub Dashboard** — DONE (2026-02-20)
- Unified dashboard for all SecuBox media services at `/admin/services/media-hub`
- Category-organized cards: streaming, conferencing, apps, display, social, monitoring
- Service cards with status indicators, start/stop/restart controls
- RPCD backend querying 8 media services (Jellyfin, Lyrion, Jitsi, PeerTube, etc.)
- Files: `luci-app-media-hub` package
- **HexoJS KISS Static Upload** — DONE (2026-02-20)
- Multi-user/multi-instance authentication with HAProxy Basic Auth
- UCI config for users, auth, and instances
- `hexoctl user add/del/passwd/grant/revoke` commands
- `hexoctl auth enable/disable/status/haproxy` commands
- KISS static upload workflow (no Hexo build required):
- `hexoctl static create <name>` - Create static-only site
- `hexoctl static upload <file>` - Upload HTML/CSS/JS directly
- `hexoctl static publish` - Copy to /www/ for immediate serving
- `hexoctl static quick <file>` - One-command upload + publish
- Tested and verified on router
### Just Completed (2026-02-19)
- **WAF VoIP/XMPP Security Filters** — DONE (2026-02-19)
@ -94,6 +140,16 @@ _Last updated: 2026-02-19 (v0.22.0 - VoIP + Jabber Integration)_
- Updated luci.jabber RPCD with 9 new VoIP methods
- UCI config sections: jingle, sms, voicemail
- **Matrix Homeserver Integration** — DONE (2026-02-19)
- Created `secubox-app-matrix` package with Conduit Matrix server in LXC
- Pre-built ARM64/x86_64 binaries (~15MB), ~500MB RAM footprint
- `matrixctl` CLI: install/start/stop, user management, federation, emancipate
- HAProxy integration, identity linking (DID), P2P mesh publication
- Created `luci-app-matrix` dashboard with KISS theme
- Install wizard, status cards, user form, emancipate form, logs viewer
- RPCD backend with 18 methods
- Completes v1.0.0 roadmap: Matrix + VoIP + Jabber = full mesh communication stack
### Just Completed (2026-02-17)
- **PeerTube yt-dlp Video Import** — DONE (2026-02-17)

View File

@ -353,7 +353,29 @@
"Bash(gh repo view:*)",
"Bash(gh repo edit:*)",
"Bash(gh auth:*)",
"WebFetch(domain:cloud.gk2.secubox.in)"
"WebFetch(domain:cloud.gk2.secubox.in)",
"WebFetch(domain:docs.conduit.rs)",
"Bash(' -f1\\)\n local nc_ok=$\\(echo \"\"$nc_stats\"\" | cut -d')",
"Bash(' -f2\\)\n local nc_fail=$\\(echo \"\"$nc_stats\"\" | cut -d')",
"Bash(' -f3\\)\n \n local pt_last=$\\(echo \"\"$pt_stats\"\" | cut -d')",
"Bash(' -f1\\)\n local pt_ok=$\\(echo \"\"$pt_stats\"\" | cut -d')",
"Bash(' -f2\\)\n local pt_fail=$\\(echo \"\"$pt_stats\"\" | cut -d')",
"Bash(' -f3\\)\n \n local jb_last=$\\(echo \"\"$jb_stats\"\" | cut -d')",
"Bash(' -f1\\)\n local jb_ok=$\\(echo \"\"$jb_stats\"\" | cut -d')",
"Bash(' -f2\\)\n local jb_fail=$\\(echo \"\"$jb_stats\"\" | cut -d')",
"Bash(' -f3\\)\n \n local mx_last=$\\(echo \"\"$mx_stats\"\" | cut -d')",
"Bash(' -f1\\)\n local mx_ok=$\\(echo \"\"$mx_stats\"\" | cut -d')",
"Bash(' -f2\\)\n local mx_fail=$\\(echo \"\"$mx_stats\"\" | cut -d')",
"Bash(' -f3\\)\n \n local em_last=$\\(echo \"\"$em_stats\"\" | cut -d')",
"Bash(' -f1\\)\n local em_ok=$\\(echo \"\"$em_stats\"\" | cut -d')",
"Bash(' -f2\\)\n local em_fail=$\\(echo \"\"$em_stats\"\" | cut -d')",
"Bash(' -f3\\)\n \n # Calculate totals\n local total_success=$\\(\\(${nc_ok:-0} + ${pt_ok:-0} + ${jb_ok:-0} + ${mx_ok:-0} + ${em_ok:-0}\\)\\)\n local total_failure=$\\(\\(${nc_fail:-0} + ${pt_fail:-0} + ${jb_fail:-0} + ${mx_fail:-0} + ${em_fail:-0}\\)\\)\n \n # Find most recent login\n local last_login=\"\"\"\"\n for ts in \"\"$nc_last\"\" \"\"$pt_last\"\" \"\"$jb_last\"\" \"\"$mx_last\"\" \"\"$em_last\"\"; do\n [ -n \"\"$ts\"\" ] && last_login=\"\"$ts\"\"\n done\n \n # Output JSON\n cat << EOFSTATS\n{\n \"\"last_login\"\": \"\"${last_login:-never}\"\",\n \"\"total_success\"\": $total_success,\n \"\"total_failure\"\": $total_failure,\n \"\"services\"\": {\n \"\"nextcloud\"\": {\"\"last\"\": \"\"${nc_last:-}\"\", \"\"success\"\": ${nc_ok:-0}, \"\"failure\"\": ${nc_fail:-0}},\n \"\"peertube\"\": {\"\"last\"\": \"\"${pt_last:-}\"\", \"\"success\"\": ${pt_ok:-0}, \"\"failure\"\": ${pt_fail:-0}},\n \"\"jabber\"\": {\"\"last\"\": \"\"${jb_last:-}\"\", \"\"success\"\": ${jb_ok:-0}, \"\"failure\"\": ${jb_fail:-0}},\n \"\"matrix\"\": {\"\"last\"\": \"\"${mx_last:-}\"\", \"\"success\"\": ${mx_ok:-0}, \"\"failure\"\": ${mx_fail:-0}},\n \"\"email\"\": {\"\"last\"\": \"\"${em_last:-}\"\", \"\"success\"\": ${em_ok:-0}, \"\"failure\"\": ${em_fail:-0}}\n }\n}\nEOFSTATS\n}\nEOF\nchmod +x /usr/lib/secubox/users-login-stats.sh\necho \"\"Login stats library created\"\"')",
"WebFetch(domain:cdnjs.com)",
"Bash(npm install:*)",
"Bash(npm run build:*)",
"Bash(npx gulp browserify:*)",
"Bash(npx terser:*)",
"Bash(read)"
]
}
}

View File

@ -13,6 +13,24 @@
**When the user says "continue" or "next"**, consult WIP.md "Next Up" and TODO.md "Open" to pick the next task. When completing work, update these files to keep them current. New features and fixes must be appended to HISTORY.md with the date.
## Security Policies
### WAF Bypass Prohibition
- **NEVER set `waf_bypass='1'` on any HAProxy vhost** — All traffic MUST pass through the mitmproxy WAF for inspection
- When adding new services/domains to HAProxy, route through `mitmproxy_inspector` backend and add the upstream route to `/srv/mitmproxy/haproxy-routes.json`
- If a service needs special handling (WebSocket, long connections), configure mitmproxy to properly forward the traffic rather than bypassing WAF
- Use `mitmproxyctl sync-routes` to regenerate routes from HAProxy backends, then manually add any missing routes for backends that don't have standard server entries
### Mitmproxy Route Configuration
When adding a new service that routes through HAProxy → mitmproxy:
1. Add vhost: `haproxyctl vhost add <domain>`
2. Backend will default to `mitmproxy_inspector` (correct)
3. Add route to mitmproxy: Edit `/srv/mitmproxy/haproxy-routes.json` and `/srv/mitmproxy-in/haproxy-routes.json`:
```json
"domain.example.com": ["127.0.0.1", PORT]
```
4. Restart mitmproxy: `/etc/init.d/mitmproxy restart`
## OpenWrt Shell Scripting Guidelines
### Process Detection

View File

@ -0,0 +1,24 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-secubox-users
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
LUCI_TITLE:=LuCI SecuBox User Management
LUCI_DEPENDS:=+secubox-core-users
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
define Package/luci-app-secubox-users/install
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-users $(1)/usr/libexec/rpcd/luci.secubox-users
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-secubox-users.json $(1)/usr/share/rpcd/acl.d/luci-app-secubox-users.json
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-secubox-users.json $(1)/usr/share/luci/menu.d/luci-app-secubox-users.json
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/secubox-users
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-users/overview.js $(1)/www/luci-static/resources/view/secubox-users/overview.js
endef
$(eval $(call BuildPackage,luci-app-secubox-users))

View File

@ -0,0 +1,284 @@
'use strict';
'require view';
'require dom';
'require ui';
'require rpc';
var callStatus = rpc.declare({
object: 'luci.secubox-users',
method: 'status',
expect: { }
});
var callUsers = rpc.declare({
object: 'luci.secubox-users',
method: 'users',
expect: { }
});
var callAddUser = rpc.declare({
object: 'luci.secubox-users',
method: 'add',
params: ['username', 'password', 'services'],
expect: { }
});
var callDeleteUser = rpc.declare({
object: 'luci.secubox-users',
method: 'delete',
params: ['username'],
expect: { }
});
var callPasswd = rpc.declare({
object: 'luci.secubox-users',
method: 'passwd',
params: ['username', 'password'],
expect: { }
});
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callUsers()
]);
},
renderServiceBadge: function(name, running) {
var color = running ? '#4CAF50' : '#9e9e9e';
return E('span', {
'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:' + color + ';font-size:0.85em;'
}, name);
},
renderUserRow: function(user) {
var self = this;
var services = (user.services || []).map(function(s) {
return E('span', {
'style': 'display:inline-block;padding:1px 6px;margin:1px;border-radius:3px;background:#e3f2fd;font-size:0.8em;'
}, s);
});
return E('tr', {}, [
E('td', {}, user.username),
E('td', {}, user.email),
E('td', {}, services),
E('td', {}, user.enabled === '1' ? 'Yes' : 'No'),
E('td', {}, [
E('button', {
'class': 'btn cbi-button',
'click': function() { self.handlePasswd(user.username); },
'style': 'margin-right:5px;'
}, _('Password')),
E('button', {
'class': 'btn cbi-button cbi-button-remove',
'click': function() { self.handleDelete(user.username); }
}, _('Delete'))
])
]);
},
handleAdd: function() {
var self = this;
ui.showModal(_('Add User'), [
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Username')),
E('div', { 'class': 'cbi-value-field' }, [
E('input', { 'type': 'text', 'id': 'new-username', 'style': 'width:200px;' })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Password')),
E('div', { 'class': 'cbi-value-field' }, [
E('input', { 'type': 'password', 'id': 'new-password', 'placeholder': _('Leave empty to generate'), 'style': 'width:200px;' })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Services')),
E('div', { 'class': 'cbi-value-field' }, [
E('label', {}, [E('input', { 'type': 'checkbox', 'id': 'svc-nextcloud', 'checked': true }), ' Nextcloud']),
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-peertube', 'checked': true }), ' PeerTube']),
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-jabber', 'checked': true }), ' Jabber']),
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-matrix', 'checked': true }), ' Matrix']),
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-email', 'checked': true }), ' Email'])
])
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
E('button', {
'class': 'btn cbi-button-positive',
'click': function() {
var username = document.getElementById('new-username').value;
var password = document.getElementById('new-password').value;
var services = [];
if (document.getElementById('svc-nextcloud').checked) services.push('nextcloud');
if (document.getElementById('svc-peertube').checked) services.push('peertube');
if (document.getElementById('svc-jabber').checked) services.push('jabber');
if (document.getElementById('svc-matrix').checked) services.push('matrix');
if (document.getElementById('svc-email').checked) services.push('email');
if (!username) {
ui.addNotification(null, E('p', {}, _('Username required')), 'error');
return;
}
ui.hideModal();
ui.showModal(_('Creating User...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
]);
callAddUser(username, password, services.join(',')).then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.showModal(_('User Created'), [
E('div', { 'style': 'padding:20px;' }, [
E('p', {}, _('User created successfully!')),
E('table', { 'style': 'margin:15px 0;' }, [
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Username:')), E('td', { 'style': 'padding:5px;' }, res.username)]),
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Password:')), E('td', { 'style': 'padding:5px;font-family:monospace;background:#f5f5f5;' }, res.password)]),
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Email:')), E('td', { 'style': 'padding:5px;' }, res.email)])
])
]),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn cbi-button-positive', 'click': function() { ui.hideModal(); location.reload(); } }, _('OK'))
])
]);
} else {
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
}
});
},
'style': 'margin-left:10px;'
}, _('Create User'))
])
]);
},
handleDelete: function(username) {
var self = this;
if (!confirm(_('Delete user "%s" from all services?').format(username))) {
return;
}
ui.showModal(_('Deleting...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
]);
callDeleteUser(username).then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.addNotification(null, E('p', {}, _('User deleted')), 'success');
location.reload();
} else {
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
}
});
},
handlePasswd: function(username) {
var self = this;
ui.showModal(_('Change Password'), [
E('div', { 'class': 'cbi-section' }, [
E('p', {}, _('Change password for: %s').format(username)),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('New Password')),
E('div', { 'class': 'cbi-value-field' }, [
E('input', { 'type': 'password', 'id': 'new-passwd', 'placeholder': _('Leave empty to generate'), 'style': 'width:200px;' })
])
])
]),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
E('button', {
'class': 'btn cbi-button-positive',
'click': function() {
var password = document.getElementById('new-passwd').value;
ui.hideModal();
ui.showModal(_('Updating...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
]);
callPasswd(username, password).then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.showModal(_('Password Updated'), [
E('div', { 'style': 'padding:20px;' }, [
E('p', {}, _('Password updated for all services!')),
E('p', { 'style': 'font-family:monospace;background:#f5f5f5;padding:10px;margin:10px 0;' }, res.password)
]),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn cbi-button-positive', 'click': ui.hideModal }, _('OK'))
])
]);
} else {
ui.addNotification(null, E('p', {}, _('Error updating password')), 'error');
}
});
},
'style': 'margin-left:10px;'
}, _('Update'))
])
]);
},
render: function(data) {
var self = this;
var status = data[0] || {};
var usersData = data[1] || {};
var users = usersData.users || [];
var services = status.services || {};
var content = [];
// Header
content.push(E('h2', {}, _('SecuBox User Management')));
// Status Section
content.push(E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Services')),
E('div', { 'style': 'margin:10px 0;' }, [
this.renderServiceBadge('Nextcloud', services.nextcloud),
this.renderServiceBadge('PeerTube', services.peertube),
this.renderServiceBadge('Matrix', services.matrix),
this.renderServiceBadge('Jabber', services.jabber),
this.renderServiceBadge('Email', services.email)
]),
E('p', { 'style': 'color:#666;' }, _('Domain: %s | Users: %d').format(status.domain, status.user_count))
]));
// Users Table
var userRows = users.map(function(u) { return self.renderUserRow(u); });
content.push(E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Users')),
E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom:15px;' }, [
E('button', {
'class': 'btn cbi-button-positive',
'click': function() { self.handleAdd(); }
}, _('Add User'))
]),
users.length > 0 ?
E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Username')),
E('th', { 'class': 'th' }, _('Email')),
E('th', { 'class': 'th' }, _('Services')),
E('th', { 'class': 'th' }, _('Enabled')),
E('th', { 'class': 'th' }, _('Actions'))
])
].concat(userRows)) :
E('p', { 'style': 'color:#666;' }, _('No users configured. Click "Add User" to create one.'))
]));
return E('div', { 'class': 'cbi-map' }, content);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,190 @@
#!/bin/sh
# RPCD handler for SecuBox User Management
. /usr/share/libubox/jshn.sh
CONFIG="secubox-users"
uci_get() { uci -q get ${CONFIG}.$1; }
# Check if service is running
check_service() {
local service="$1"
case "$service" in
nextcloud) [ -x /usr/sbin/nextcloudctl ] && lxc-info -n nextcloud 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
peertube) [ -x /usr/sbin/peertubectl ] && lxc-info -n peertube 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
matrix) [ -x /usr/sbin/matrixctl ] && lxc-info -n matrix 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
jabber) [ -x /usr/sbin/jabberctl ] && lxc-info -n jabber 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
email) [ -x /usr/sbin/mailserverctl ] && lxc-info -n mailserver 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
*) echo "0" ;;
esac
}
get_status() {
local domain=$(uci_get main.domain || echo "secubox.in")
local matrix_server=$(uci_get main.matrix_server || echo "matrix.local")
local user_count=$(uci show ${CONFIG} 2>/dev/null | grep -c "=user$" || echo 0)
local nc_running=$(check_service nextcloud)
local pt_running=$(check_service peertube)
local mx_running=$(check_service matrix)
local jb_running=$(check_service jabber)
local em_running=$(check_service email)
cat <<EOFJ
{
"domain": "$domain",
"matrix_server": "$matrix_server",
"user_count": $user_count,
"services": {
"nextcloud": $nc_running,
"peertube": $pt_running,
"matrix": $mx_running,
"jabber": $jb_running,
"email": $em_running
}
}
EOFJ
}
get_users() {
local users=$(uci show ${CONFIG} 2>/dev/null | grep "=user$" | cut -d'.' -f2 | cut -d'=' -f1)
json_init
json_add_array "users"
for user in $users; do
json_add_object
json_add_string "username" "$user"
json_add_string "email" "$(uci_get ${user}.email)"
json_add_string "enabled" "$(uci_get ${user}.enabled)"
json_add_string "created" "$(uci_get ${user}.created)"
# Get services as array
local services=$(uci -q get ${CONFIG}.${user}.services 2>/dev/null)
json_add_array "services"
for svc in $services; do
json_add_string "" "$svc"
done
json_close_array
json_close_object
done
json_close_array
json_dump
}
add_user() {
read -r input
local username=$(echo "$input" | jsonfilter -e '@.username' 2>/dev/null)
local password=$(echo "$input" | jsonfilter -e '@.password' 2>/dev/null)
local services=$(echo "$input" | jsonfilter -e '@.services' 2>/dev/null)
if [ -z "$username" ]; then
echo '{"success":false,"error":"Username required"}'
return
fi
# Generate password if not provided
if [ -z "$password" ]; then
password=$(head -c 12 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16)
fi
# Run secubox-users add
local output
if [ -n "$services" ]; then
output=$(secubox-users add "$username" "$password" "$services" 2>&1)
else
output=$(secubox-users add "$username" "$password" 2>&1)
fi
if echo "$output" | grep -q "USER CREDENTIALS"; then
json_init
json_add_boolean "success" 1
json_add_string "username" "$username"
json_add_string "password" "$password"
json_add_string "email" "${username}@$(uci_get main.domain)"
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Failed to create user"
json_add_string "output" "$output"
json_dump
fi
}
delete_user() {
read -r input
local username=$(echo "$input" | jsonfilter -e '@.username' 2>/dev/null)
if [ -z "$username" ]; then
echo '{"success":false,"error":"Username required"}'
return
fi
local output=$(secubox-users del "$username" 2>&1)
if echo "$output" | grep -q "deleted"; then
echo '{"success":true}'
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Failed to delete user"
json_add_string "output" "$output"
json_dump
fi
}
update_password() {
read -r input
local username=$(echo "$input" | jsonfilter -e '@.username' 2>/dev/null)
local password=$(echo "$input" | jsonfilter -e '@.password' 2>/dev/null)
if [ -z "$username" ]; then
echo '{"success":false,"error":"Username required"}'
return
fi
local output
if [ -n "$password" ]; then
output=$(secubox-users passwd "$username" "$password" 2>&1)
else
output=$(secubox-users passwd "$username" 2>&1)
password=$(echo "$output" | grep "Generated password:" | cut -d: -f2 | xargs)
fi
if echo "$output" | grep -q "Password updated"; then
json_init
json_add_boolean "success" 1
json_add_string "password" "$password"
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Failed to update password"
json_dump
fi
}
list_methods() {
cat <<'EOFM'
{"status":{},"users":{},"add":{"username":"str","password":"str","services":"str"},"delete":{"username":"str"},"passwd":{"username":"str","password":"str"}}
EOFM
}
case "$1" in
list) list_methods ;;
call)
case "$2" in
status) get_status ;;
users) get_users ;;
add) add_user ;;
delete) delete_user ;;
passwd) update_password ;;
*) echo '{"error":"Unknown method"}' ;;
esac
;;
*) echo '{"error":"Unknown command"}' ;;
esac

View File

@ -0,0 +1,14 @@
{
"admin/system/secubox-users": {
"title": "SecuBox Users",
"order": 85,
"action": {
"type": "view",
"path": "secubox-users/overview"
},
"depends": {
"acl": ["luci-app-secubox-users"],
"uci": {"secubox-users": true}
}
}
}

View File

@ -0,0 +1,17 @@
{
"luci-app-secubox-users": {
"description": "Grant access to SecuBox User Management",
"read": {
"ubus": {
"luci.secubox-users": ["status", "users"]
},
"uci": ["secubox-users"]
},
"write": {
"ubus": {
"luci.secubox-users": ["add", "delete", "passwd"]
},
"uci": ["secubox-users"]
}
}
}

View File

@ -0,0 +1,396 @@
'use strict';
'require view';
'require rpc';
'require ui';
'require form';
'require fs';
var callStaticList = rpc.declare({
object: 'luci.hexojs',
method: 'static_list',
params: ['instance'],
expect: {}
});
var callStaticUpload = rpc.declare({
object: 'luci.hexojs',
method: 'static_upload',
params: ['instance', 'filename', 'content'],
expect: {}
});
var callStaticCreate = rpc.declare({
object: 'luci.hexojs',
method: 'static_create',
params: ['name', 'domain'],
expect: {}
});
var callStaticDelete = rpc.declare({
object: 'luci.hexojs',
method: 'static_delete',
params: ['name'],
expect: {}
});
var callStaticPublish = rpc.declare({
object: 'luci.hexojs',
method: 'static_publish',
params: ['instance'],
expect: {}
});
var callStaticDeleteFile = rpc.declare({
object: 'luci.hexojs',
method: 'static_delete_file',
params: ['instance', 'filename'],
expect: {}
});
var callStaticConfigureAuth = rpc.declare({
object: 'luci.hexojs',
method: 'static_configure_auth',
params: ['instance', 'enabled', 'domain'],
expect: {}
});
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatDate(timestamp) {
if (!timestamp) return '-';
var d = new Date(timestamp * 1000);
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
}
return view.extend({
selectedInstance: null,
load: function() {
return callStaticList({});
},
renderInstanceCard: function(instance) {
var self = this;
var card = E('div', { 'class': 'cbi-section', 'style': 'margin-bottom: 10px; padding: 15px; border: 1px solid #ccc; border-radius: 8px;' }, [
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;' }, [
E('div', {}, [
E('strong', { 'style': 'font-size: 1.2em;' }, instance.name),
instance.domain ? E('span', { 'style': 'margin-left: 10px; color: #666;' }, instance.domain) : '',
instance.auth_enabled ? E('span', { 'class': 'badge', 'style': 'margin-left: 10px; background: #28a745; color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.8em;' }, 'Auth') : ''
]),
E('div', {}, [
E('span', { 'style': 'margin-right: 15px; color: #666;' }, instance.file_count + ' files'),
instance.port > 0 ? E('span', { 'style': 'color: #666;' }, 'Port ' + instance.port) : ''
])
]),
E('div', { 'style': 'display: flex; gap: 10px;' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.showFiles(instance.name); }
}, 'Manage Files'),
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': function() { self.publishSite(instance.name); }
}, 'Publish'),
E('button', {
'class': 'cbi-button',
'click': function() { self.configureAuth(instance.name, instance.domain); }
}, 'Auth'),
E('button', {
'class': 'cbi-button cbi-button-remove',
'click': function() { self.deleteSite(instance.name); }
}, 'Delete')
])
]);
return card;
},
showFiles: function(instanceName) {
var self = this;
callStaticList({ instance: instanceName }).then(function(result) {
if (!result.success) {
ui.addNotification(null, E('p', result.error || 'Failed to list files'), 'error');
return;
}
self.selectedInstance = instanceName;
var container = document.getElementById('file-list-container');
container.innerHTML = '';
// Header
container.appendChild(E('h3', {}, 'Files in "' + instanceName + '"'));
// Upload section
var uploadSection = E('div', { 'class': 'cbi-section', 'style': 'padding: 15px; background: #f9f9f9; border-radius: 8px; margin-bottom: 15px;' }, [
E('h4', {}, 'Upload Files'),
E('input', {
'type': 'file',
'id': 'file-upload-input',
'multiple': true,
'style': 'margin-right: 10px;'
}),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.uploadFiles(instanceName); }
}, 'Upload Selected')
]);
container.appendChild(uploadSection);
// File list table
var table = E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Filename'),
E('th', { 'class': 'th' }, 'Size'),
E('th', { 'class': 'th' }, 'Modified'),
E('th', { 'class': 'th' }, 'Actions')
])
]);
var files = result.files || [];
if (files.length === 0) {
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'colspan': '4', 'style': 'text-align: center; color: #666;' }, 'No files yet. Upload some files above.')
]));
} else {
files.forEach(function(file) {
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, file.name),
E('td', { 'class': 'td' }, formatBytes(file.size)),
E('td', { 'class': 'td' }, formatDate(file.modified)),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'cbi-button cbi-button-remove',
'style': 'padding: 2px 8px; font-size: 0.9em;',
'click': function() { self.deleteFile(instanceName, file.name); }
}, 'Delete')
])
]));
});
}
container.appendChild(table);
// Back button
container.appendChild(E('button', {
'class': 'cbi-button',
'style': 'margin-top: 15px;',
'click': function() { self.refreshView(); }
}, 'Back to Sites'));
});
},
uploadFiles: function(instanceName) {
var self = this;
var input = document.getElementById('file-upload-input');
var files = input.files;
if (files.length === 0) {
ui.addNotification(null, E('p', 'Please select files to upload'), 'warning');
return;
}
var uploadPromises = [];
for (var i = 0; i < files.length; i++) {
(function(file) {
var promise = new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function(e) {
// Convert to base64
var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(e.target.result)));
callStaticUpload({
instance: instanceName,
filename: file.name,
content: base64
}).then(function(result) {
if (result.success) {
resolve(file.name);
} else {
reject(result.error || 'Upload failed for ' + file.name);
}
}).catch(reject);
};
reader.onerror = function() {
reject('Failed to read file: ' + file.name);
};
reader.readAsArrayBuffer(file);
});
uploadPromises.push(promise);
})(files[i]);
}
Promise.all(uploadPromises).then(function(uploaded) {
ui.addNotification(null, E('p', 'Uploaded ' + uploaded.length + ' file(s)'), 'success');
self.showFiles(instanceName);
}).catch(function(err) {
ui.addNotification(null, E('p', 'Upload error: ' + err), 'error');
self.showFiles(instanceName);
});
},
deleteFile: function(instanceName, filename) {
var self = this;
if (!confirm('Delete file "' + filename + '"?')) return;
callStaticDeleteFile({ instance: instanceName, filename: filename }).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', 'File deleted'), 'success');
self.showFiles(instanceName);
} else {
ui.addNotification(null, E('p', result.error || 'Failed to delete file'), 'error');
}
});
},
publishSite: function(instanceName) {
callStaticPublish({ instance: instanceName }).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', 'Published to ' + result.url), 'success');
} else {
ui.addNotification(null, E('p', result.error || 'Failed to publish'), 'error');
}
});
},
configureAuth: function(instanceName, currentDomain) {
var self = this;
var domain = prompt('Enter domain for HAProxy auth (e.g., site.example.com):', currentDomain || '');
if (domain === null) return;
if (!domain) {
ui.addNotification(null, E('p', 'Domain is required for HAProxy auth'), 'warning');
return;
}
callStaticConfigureAuth({
instance: instanceName,
enabled: true,
domain: domain
}).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', 'Auth configured for ' + domain), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', result.error || 'Failed to configure auth'), 'error');
}
});
},
deleteSite: function(instanceName) {
var self = this;
if (!confirm('Delete static site "' + instanceName + '" and all its files?')) return;
callStaticDelete({ name: instanceName }).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', 'Site deleted'), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', result.error || 'Failed to delete site'), 'error');
}
});
},
createSite: function() {
var self = this;
var name = prompt('Enter site name (lowercase, no spaces):');
if (!name) return;
// Validate name
if (!/^[a-z][a-z0-9_]*$/.test(name)) {
ui.addNotification(null, E('p', 'Invalid name. Use lowercase letters, numbers, underscore.'), 'error');
return;
}
var domain = prompt('Enter domain (optional, e.g., site.example.com):');
callStaticCreate({ name: name, domain: domain || '' }).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', 'Site "' + name + '" created on port ' + result.port), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', result.error || 'Failed to create site'), 'error');
}
});
},
refreshView: function() {
var self = this;
callStaticList({}).then(function(result) {
var container = document.getElementById('file-list-container');
container.innerHTML = '';
var instancesContainer = document.getElementById('static-instances');
instancesContainer.innerHTML = '';
if (!result.success) {
instancesContainer.appendChild(E('p', { 'style': 'color: red;' }, result.error || 'Failed to load sites'));
return;
}
var instances = result.instances || [];
if (instances.length === 0) {
instancesContainer.appendChild(E('p', { 'style': 'color: #666; text-align: center; padding: 20px;' },
'No static sites yet. Click "Create New Site" to get started.'));
} else {
instances.forEach(function(inst) {
instancesContainer.appendChild(self.renderInstanceCard(inst));
});
}
});
},
render: function(data) {
var self = this;
var instances = (data && data.instances) || [];
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', { 'class': 'cbi-map-title' }, 'Static Sites'),
E('div', { 'class': 'cbi-map-descr' },
'Upload and manage static HTML sites with optional Basic Auth via HAProxy. Fast KISS publishing without Hexo build process.'),
// Create button
E('div', { 'style': 'margin: 20px 0;' }, [
E('button', {
'class': 'cbi-button cbi-button-add',
'click': function() { self.createSite(); }
}, 'Create New Site')
]),
// Instances container
E('div', { 'id': 'static-instances' }),
// File list container (shown when managing a site)
E('div', { 'id': 'file-list-container', 'style': 'margin-top: 20px;' })
]);
// Render initial instances
var instancesContainer = view.querySelector('#static-instances');
if (instances.length === 0) {
instancesContainer.appendChild(E('p', { 'style': 'color: #666; text-align: center; padding: 20px;' },
'No static sites yet. Click "Create New Site" to get started.'));
} else {
instances.forEach(function(inst) {
instancesContainer.appendChild(self.renderInstanceCard(inst));
});
}
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -3163,6 +3163,295 @@ delete_backup() {
json_dump
}
# ============================================
# Static Site Methods (KISS Upload)
# ============================================
# List static sites or files in a site
static_list() {
read input
json_load "$input"
json_get_var instance instance
local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
json_init
json_add_boolean "success" 1
if [ -n "$instance" ]; then
# List files in specific instance
local static_dir="$data_path/static/$instance"
if [ ! -d "$static_dir" ]; then
json_add_boolean "success" 0
json_add_string "error" "Static instance '$instance' not found"
json_dump
return
fi
json_add_string "instance" "$instance"
json_add_array "files"
for f in "$static_dir"/*; do
[ -f "$f" ] || continue
local filename=$(basename "$f")
local size=$(stat -c%s "$f" 2>/dev/null || echo 0)
local modified=$(stat -c%Y "$f" 2>/dev/null || echo 0)
json_add_object
json_add_string "name" "$filename"
json_add_int "size" "$size"
json_add_int "modified" "$modified"
json_close_object
done
json_close_array
else
# List all static instances
json_add_array "instances"
for dir in "$data_path/static"/*; do
[ -d "$dir" ] || continue
local name=$(basename "$dir")
local count=$(find "$dir" -type f 2>/dev/null | wc -l)
local port=$(uci -q get hexojs.${name}.port)
local domain=$(uci -q get hexojs.${name}.domain)
local auth=$(uci -q get hexojs.${name}.auth_enabled)
json_add_object
json_add_string "name" "$name"
json_add_int "file_count" "$count"
json_add_int "port" "${port:-0}"
json_add_string "domain" "$domain"
json_add_boolean "auth_enabled" "${auth:-0}"
json_close_object
done
json_close_array
fi
json_dump
}
# Upload file to static site (base64 encoded)
static_upload() {
read input
json_load "$input"
json_get_var instance instance
json_get_var filename filename
json_get_var content content
json_init
if [ -z "$filename" ] || [ -z "$content" ]; then
json_add_boolean "success" 0
json_add_string "error" "Filename and content required"
json_dump
return
fi
[ -z "$instance" ] && instance="default"
local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
local static_dir="$data_path/static/$instance"
# Auto-create instance if needed
if [ ! -d "$static_dir" ]; then
"$HEXOCTL" static create "$instance" >/dev/null 2>&1
fi
# Sanitize filename (prevent path traversal)
local safe_filename=$(basename "$filename" | tr -cd 'a-zA-Z0-9._-')
local target_path="$static_dir/$safe_filename"
# Decode base64 content and save
echo "$content" | base64 -d > "$target_path" 2>/dev/null
local result=$?
if [ "$result" -eq 0 ] && [ -f "$target_path" ]; then
local size=$(stat -c%s "$target_path" 2>/dev/null || echo 0)
json_add_boolean "success" 1
json_add_string "message" "File uploaded"
json_add_string "filename" "$safe_filename"
json_add_string "path" "$target_path"
json_add_int "size" "$size"
else
json_add_boolean "success" 0
json_add_string "error" "Failed to save file"
fi
json_dump
}
# Create static site instance
static_create() {
read input
json_load "$input"
json_get_var name name
json_get_var domain domain
json_init
if [ -z "$name" ]; then
json_add_boolean "success" 0
json_add_string "error" "Instance name required"
json_dump
return
fi
local output=$("$HEXOCTL" static create "$name" 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
# Set domain if provided
[ -n "$domain" ] && uci set hexojs.${name}.domain="$domain" && uci commit hexojs
local port=$(uci -q get hexojs.${name}.port)
json_add_boolean "success" 1
json_add_string "message" "Static instance created"
json_add_string "name" "$name"
json_add_int "port" "$port"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
# Delete static site instance
static_delete() {
read input
json_load "$input"
json_get_var name name
json_init
if [ -z "$name" ]; then
json_add_boolean "success" 0
json_add_string "error" "Instance name required"
json_dump
return
fi
local output=$("$HEXOCTL" static delete "$name" 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Static instance deleted"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
# Publish static site to /www/static/
static_publish() {
read input
json_load "$input"
json_get_var instance instance
json_init
[ -z "$instance" ] && instance="default"
local output=$("$HEXOCTL" static publish "$instance" 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Published to /www/static/$instance"
json_add_string "url" "/static/$instance/"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
# Delete file from static site
static_delete_file() {
read input
json_load "$input"
json_get_var instance instance
json_get_var filename filename
json_init
if [ -z "$instance" ] || [ -z "$filename" ]; then
json_add_boolean "success" 0
json_add_string "error" "Instance and filename required"
json_dump
return
fi
local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
local static_dir="$data_path/static/$instance"
local safe_filename=$(basename "$filename")
local target="$static_dir/$safe_filename"
if [ -f "$target" ]; then
rm -f "$target"
json_add_boolean "success" 1
json_add_string "message" "File deleted"
else
json_add_boolean "success" 0
json_add_string "error" "File not found"
fi
json_dump
}
# Configure HAProxy auth for static site
static_configure_auth() {
read input
json_load "$input"
json_get_var instance instance
json_get_var enabled enabled
json_get_var domain domain
json_init
if [ -z "$instance" ]; then
json_add_boolean "success" 0
json_add_string "error" "Instance required"
json_dump
return
fi
[ -z "$domain" ] && domain=$(uci -q get hexojs.${instance}.domain)
if [ -z "$domain" ]; then
json_add_boolean "success" 0
json_add_string "error" "Domain required for HAProxy auth"
json_dump
return
fi
# Update UCI config
uci set hexojs.${instance}.auth_enabled="${enabled:-1}"
uci set hexojs.${instance}.domain="$domain"
uci commit hexojs
# Apply auth via hexoctl
local output=$("$HEXOCTL" auth apply "$instance" 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Auth configured for $domain"
json_add_string "domain" "$domain"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
# ============================================
# GitHub Integration Methods
# ============================================
@ -3379,7 +3668,14 @@ case "$1" in
"handle_webhook": {"event": "str", "repository": "str", "ref": "str", "secret": "str"},
"setup_webhook": {"auto_build": "bool", "webhook_secret": "str"},
"get_instance_health": {"instance": "str"},
"get_pipeline_status": {}
"get_pipeline_status": {},
"static_list": {"instance": "str"},
"static_upload": {"instance": "str", "filename": "str", "content": "str"},
"static_create": {"name": "str", "domain": "str"},
"static_delete": {"name": "str"},
"static_publish": {"instance": "str"},
"static_delete_file": {"instance": "str", "filename": "str"},
"static_configure_auth": {"instance": "str", "enabled": "bool", "domain": "str"}
}
EOF
;;
@ -3459,6 +3755,13 @@ EOF
setup_webhook) setup_webhook ;;
get_instance_health) get_instance_health ;;
get_pipeline_status) get_pipeline_status ;;
static_list) static_list ;;
static_upload) static_upload ;;
static_create) static_create ;;
static_delete) static_delete ;;
static_publish) static_publish ;;
static_delete_file) static_delete_file ;;
static_configure_auth) static_configure_auth ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;

View File

@ -105,5 +105,13 @@
"type": "view",
"path": "hexojs/settings"
}
},
"admin/services/hexojs/static": {
"title": "Static Sites",
"order": 115,
"action": {
"type": "view",
"path": "hexojs/static"
}
}
}

View File

@ -31,7 +31,8 @@
"get_tor_status",
"get_instance_endpoints",
"get_instance_health",
"get_pipeline_status"
"get_pipeline_status",
"static_list"
]
},
"uci": ["hexojs"]
@ -84,7 +85,13 @@
"unpublish_from_tor",
"full_publish",
"handle_webhook",
"setup_webhook"
"setup_webhook",
"static_upload",
"static_create",
"static_delete",
"static_publish",
"static_delete_file",
"static_configure_auth"
]
},
"uci": ["hexojs"]

View File

@ -294,6 +294,8 @@ return view.extend({
'</div>';
};
return KissTheme.wrap([m.render()], 'admin/services/jitsi');
return m.render().then(function(mapEl) {
return KissTheme.wrap([mapEl], 'admin/services/jitsi');
});
}
});

View File

@ -0,0 +1,29 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-media-hub
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
PKG_LICENSE:=MIT
LUCI_TITLE:=LuCI Media Services Hub
LUCI_DEPENDS:=+luci-base
include $(TOPDIR)/feeds/luci/luci.mk
define Package/luci-app-media-hub/install
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.media-hub $(1)/usr/libexec/rpcd/luci.media-hub
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-media-hub.json $(1)/usr/share/rpcd/acl.d/
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-media-hub.json $(1)/usr/share/luci/menu.d/
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/media-hub
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/media-hub/*.js $(1)/www/luci-static/resources/view/media-hub/
endef
$(eval $(call BuildPackage,luci-app-media-hub))

View File

@ -0,0 +1,376 @@
'use strict';
'require view';
'require dom';
'require poll';
'require rpc';
'require ui';
var callMediaHubStatus = rpc.declare({
object: 'luci.media-hub',
method: 'status',
expect: { '': {} }
});
var callMediaHubServices = rpc.declare({
object: 'luci.media-hub',
method: 'services',
expect: { services: [] }
});
var callServiceStart = rpc.declare({
object: 'luci.media-hub',
method: 'service_start',
params: ['id']
});
var callServiceStop = rpc.declare({
object: 'luci.media-hub',
method: 'service_stop',
params: ['id']
});
var callServiceRestart = rpc.declare({
object: 'luci.media-hub',
method: 'service_restart',
params: ['id']
});
// Category icons and colors
var categoryConfig = {
streaming: { icon: '🎬', color: '#e74c3c', label: 'Streaming' },
conferencing: { icon: '📹', color: '#3498db', label: 'Conferencing' },
apps: { icon: '📊', color: '#9b59b6', label: 'Apps' },
display: { icon: '🪞', color: '#1abc9c', label: 'Display' },
social: { icon: '🦣', color: '#e67e22', label: 'Social' },
monitoring: { icon: '📡', color: '#2ecc71', label: 'Monitoring' }
};
// Status colors and icons
var statusConfig = {
running: { color: '#27ae60', icon: '●', label: 'Running' },
stopped: { color: '#e74c3c', icon: '○', label: 'Stopped' },
not_installed: { color: '#95a5a6', icon: '◌', label: 'Not Installed' },
unknown: { color: '#f39c12', icon: '?', label: 'Unknown' }
};
return view.extend({
load: function() {
return Promise.all([
callMediaHubStatus(),
callMediaHubServices()
]);
},
renderStatusBar: function(status) {
var total = status.total_services || 0;
var installed = status.installed || 0;
var running = status.running || 0;
var stopped = status.stopped || 0;
return E('div', { 'class': 'media-hub-status-bar' }, [
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-value', 'style': 'color: #3498db' }, String(total)),
E('span', { 'class': 'status-label' }, 'Total')
]),
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-value', 'style': 'color: #9b59b6' }, String(installed)),
E('span', { 'class': 'status-label' }, 'Installed')
]),
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-value', 'style': 'color: #27ae60' }, String(running)),
E('span', { 'class': 'status-label' }, 'Running')
]),
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-value', 'style': 'color: #e74c3c' }, String(stopped)),
E('span', { 'class': 'status-label' }, 'Stopped')
])
]);
},
renderServiceCard: function(service) {
var self = this;
var statusCfg = statusConfig[service.status] || statusConfig.unknown;
var categoryCfg = categoryConfig[service.category] || { icon: '📦', color: '#7f8c8d', label: 'Other' };
var controls = [];
if (service.installed) {
if (service.status === 'running') {
controls.push(
E('button', {
'class': 'cbi-button cbi-button-negative',
'style': 'margin-right: 5px; padding: 4px 12px; font-size: 12px;',
'click': ui.createHandlerFn(this, function() {
return callServiceStop(service.id).then(function() {
window.location.reload();
});
})
}, '⏹ Stop'),
E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'padding: 4px 12px; font-size: 12px;',
'click': ui.createHandlerFn(this, function() {
return callServiceRestart(service.id).then(function() {
window.location.reload();
});
})
}, '🔄 Restart')
);
} else if (service.status === 'stopped') {
controls.push(
E('button', {
'class': 'cbi-button cbi-button-positive',
'style': 'padding: 4px 12px; font-size: 12px;',
'click': ui.createHandlerFn(this, function() {
return callServiceStart(service.id).then(function() {
window.location.reload();
});
})
}, '▶ Start')
);
}
}
// Add settings link
if (service.url) {
controls.push(
E('a', {
'href': service.url,
'class': 'cbi-button cbi-button-neutral',
'style': 'margin-left: 5px; padding: 4px 12px; font-size: 12px; text-decoration: none;'
}, '⚙ Settings')
);
}
return E('div', {
'class': 'media-service-card',
'data-status': service.status,
'style': 'border-left: 4px solid ' + categoryCfg.color + ';'
}, [
E('div', { 'class': 'card-header' }, [
E('span', { 'class': 'service-emoji' }, service.emoji || '📦'),
E('div', { 'class': 'service-info' }, [
E('span', { 'class': 'service-name' }, service.name),
E('span', { 'class': 'service-category', 'style': 'color: ' + categoryCfg.color }, categoryCfg.label)
]),
E('span', {
'class': 'service-status',
'style': 'color: ' + statusCfg.color,
'title': statusCfg.label
}, statusCfg.icon + ' ' + statusCfg.label)
]),
E('div', { 'class': 'card-body' }, [
E('p', { 'class': 'service-description' }, service.description),
service.port > 0 ? E('p', { 'class': 'service-port' }, 'Port: ' + service.port) : E('span')
]),
E('div', { 'class': 'card-footer' }, controls)
]);
},
renderCategorySection: function(category, services) {
var categoryCfg = categoryConfig[category] || { icon: '📦', color: '#7f8c8d', label: category };
var categoryServices = services.filter(function(s) { return s.category === category; });
if (categoryServices.length === 0) return E('span');
var self = this;
return E('div', { 'class': 'media-category-section' }, [
E('h3', { 'class': 'category-title', 'style': 'color: ' + categoryCfg.color }, [
E('span', { 'class': 'category-icon' }, categoryCfg.icon),
' ',
categoryCfg.label,
E('span', { 'class': 'category-count' }, ' (' + categoryServices.length + ')')
]),
E('div', { 'class': 'media-cards-grid' },
categoryServices.map(function(service) {
return self.renderServiceCard(service);
})
)
]);
},
render: function(data) {
var status = data[0];
var services = data[1];
// Sort by category then by name
services.sort(function(a, b) {
if (a.category !== b.category) {
return a.category.localeCompare(b.category);
}
return a.name.localeCompare(b.name);
});
// Get unique categories in order
var categories = ['streaming', 'conferencing', 'apps', 'display', 'social', 'monitoring'];
var self = this;
var view = E('div', { 'class': 'media-hub-dashboard' }, [
E('style', {}, `
.media-hub-dashboard {
padding: 20px;
}
.media-hub-header {
text-align: center;
margin-bottom: 30px;
}
.media-hub-header h2 {
font-size: 2em;
margin-bottom: 10px;
}
.media-hub-header .subtitle {
color: #666;
font-size: 1.1em;
}
.media-hub-status-bar {
display: flex;
justify-content: center;
gap: 40px;
padding: 20px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 12px;
margin-bottom: 30px;
}
.status-item {
text-align: center;
}
.status-value {
display: block;
font-size: 2.5em;
font-weight: bold;
}
.status-label {
color: #aaa;
font-size: 0.9em;
text-transform: uppercase;
}
.media-category-section {
margin-bottom: 30px;
}
.category-title {
font-size: 1.4em;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #333;
}
.category-icon {
font-size: 1.2em;
}
.category-count {
font-weight: normal;
font-size: 0.8em;
color: #666;
}
.media-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.media-service-card {
background: #1a1a2e;
border-radius: 12px;
padding: 20px;
transition: transform 0.2s, box-shadow 0.2s;
}
.media-service-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}
.media-service-card[data-status="running"] {
background: linear-gradient(135deg, #1a2e1a 0%, #162e16 100%);
}
.media-service-card[data-status="stopped"] {
background: linear-gradient(135deg, #2e1a1a 0%, #2e1616 100%);
}
.media-service-card[data-status="not_installed"] {
opacity: 0.7;
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.service-emoji {
font-size: 2.5em;
margin-right: 15px;
}
.service-info {
flex: 1;
}
.service-name {
display: block;
font-size: 1.3em;
font-weight: bold;
color: #fff;
}
.service-category {
font-size: 0.85em;
text-transform: uppercase;
}
.service-status {
font-size: 0.9em;
font-weight: bold;
}
.card-body {
margin-bottom: 15px;
}
.service-description {
color: #aaa;
font-size: 0.95em;
margin-bottom: 8px;
}
.service-port {
color: #666;
font-size: 0.85em;
font-family: monospace;
}
.card-footer {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.card-footer button, .card-footer a {
border-radius: 6px;
}
@media (max-width: 768px) {
.media-hub-status-bar {
flex-wrap: wrap;
gap: 20px;
}
.media-cards-grid {
grid-template-columns: 1fr;
}
}
`),
E('div', { 'class': 'media-hub-header' }, [
E('h2', {}, '🎬 Media Services Hub'),
E('p', { 'class': 'subtitle' }, 'Unified dashboard for all SecuBox media services')
]),
this.renderStatusBar(status)
]);
categories.forEach(function(category) {
var section = self.renderCategorySection(category, services);
if (section.tagName !== 'SPAN') {
view.appendChild(section);
}
});
// Setup polling for status updates
poll.add(L.bind(function() {
return callMediaHubServices().then(L.bind(function(services) {
// Update status indicators without full reload
services.forEach(function(service) {
var card = document.querySelector('.media-service-card[data-status]');
// Could update individual cards here for smooth updates
});
}, this));
}, this), 30);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,523 @@
#!/bin/sh
# Media Hub RPCD backend - Unified media services dashboard
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
# Service definitions: id|name|emoji|port|ctl_cmd|container|category
MEDIA_SERVICES="
jellyfin|Jellyfin|🎬|8096|jellyfinctl|jellyfin|streaming
lyrion|Lyrion Music|🎵|9000|lyrionctl|lyrion|streaming
jitsi|Jitsi Meet|📹|8443|jitsctl|jitsi-web|conferencing
peertube|PeerTube|📺|9000|peertubectl|peertube|streaming
streamlit|Streamlit|📊|8501|streamlitctl|streamlit|apps
magicmirror|MagicMirror²|🪞|8080|mmctl|magicmirror|display
gotosocial|GoToSocial|🦣|8080|gotosocialctl|gotosocial|social
"
# Check if a service is installed
_service_installed() {
local ctl="$1"
command -v "$ctl" >/dev/null 2>&1
}
# Check container status
_container_status() {
local name="$1"
if command -v lxc-info >/dev/null 2>&1; then
lxc-info -n "$name" -s 2>/dev/null | grep -q "RUNNING" && echo "running" && return
[ -d "/srv/lxc/$name" ] && echo "stopped" && return
fi
if command -v docker >/dev/null 2>&1; then
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$" && echo "running" && return
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$" && echo "stopped" && return
fi
echo "not_installed"
}
# Check if port is listening
_port_listening() {
local port="$1"
netstat -tln 2>/dev/null | grep -q ":${port} " && echo "1" || echo "0"
}
# Get service status via its control command
_get_service_status() {
local ctl="$1"
local output
if ! _service_installed "$ctl"; then
echo "not_installed"
return
fi
# Try to get status from the service's ctl command
output=$("$ctl" status 2>/dev/null | head -5)
if echo "$output" | grep -qi "running\|active\|started"; then
echo "running"
elif echo "$output" | grep -qi "stopped\|inactive\|not running"; then
echo "stopped"
else
echo "unknown"
fi
}
# ===========================================
# Status - Overall dashboard status
# ===========================================
get_status() {
json_init
local total=0
local running=0
local stopped=0
local not_installed=0
echo "$MEDIA_SERVICES" | while IFS='|' read -r id name emoji port ctl container category; do
[ -z "$id" ] && continue
total=$((total + 1))
if ! _service_installed "$ctl"; then
not_installed=$((not_installed + 1))
elif [ "$(_port_listening "$port")" = "1" ]; then
running=$((running + 1))
else
stopped=$((stopped + 1))
fi
done
# Count installed services
local installed=0
local active=0
for svc in jellyfin lyrion jitsi peertube streamlit magicmirror gotosocial; do
case "$svc" in
jellyfin) [ -x /usr/sbin/jellyfinctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":8096 " && active=$((active+1)) ;;
lyrion) [ -x /usr/sbin/lyrionctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":9000 " && active=$((active+1)) ;;
jitsi) [ -x /usr/sbin/jitsctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":8443 " && active=$((active+1)) ;;
peertube) [ -x /usr/sbin/peertubectl ] && installed=$((installed+1)) ;;
streamlit) [ -x /usr/sbin/streamlitctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":8501 " && active=$((active+1)) ;;
magicmirror) [ -x /usr/sbin/mmctl ] && installed=$((installed+1)) ;;
gotosocial) [ -x /usr/sbin/gotosocialctl ] && installed=$((installed+1)) ;;
esac
done
json_add_int "total_services" 7
json_add_int "installed" "$installed"
json_add_int "running" "$active"
json_add_int "stopped" "$((installed - active))"
json_dump
}
# ===========================================
# Services - List all media services
# ===========================================
get_services() {
json_init
json_add_array "services"
# Jellyfin
json_add_object
json_add_string "id" "jellyfin"
json_add_string "name" "Jellyfin"
json_add_string "emoji" "🎬"
json_add_string "description" "Media server for movies, TV, music & photos"
json_add_string "category" "streaming"
json_add_int "port" 8096
json_add_string "ctl" "jellyfinctl"
if [ -x /usr/sbin/jellyfinctl ]; then
json_add_boolean "installed" 1
if netstat -tln 2>/dev/null | grep -q ":8096 "; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/jellyfin"
json_close_object
# Lyrion
json_add_object
json_add_string "id" "lyrion"
json_add_string "name" "Lyrion Music Server"
json_add_string "emoji" "🎵"
json_add_string "description" "Music streaming (Squeezebox/LMS)"
json_add_string "category" "streaming"
json_add_int "port" 9000
json_add_string "ctl" "lyrionctl"
if [ -x /usr/sbin/lyrionctl ]; then
json_add_boolean "installed" 1
if netstat -tln 2>/dev/null | grep -q ":9000 "; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/lyrion"
json_close_object
# Jitsi Meet
json_add_object
json_add_string "id" "jitsi"
json_add_string "name" "Jitsi Meet"
json_add_string "emoji" "📹"
json_add_string "description" "Video conferencing with E2E encryption"
json_add_string "category" "conferencing"
json_add_int "port" 8443
json_add_string "ctl" "jitsctl"
if [ -x /usr/sbin/jitsctl ]; then
json_add_boolean "installed" 1
# Check if jitsi containers are running
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "jitsi"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/jitsi"
json_close_object
# PeerTube
json_add_object
json_add_string "id" "peertube"
json_add_string "name" "PeerTube"
json_add_string "emoji" "📺"
json_add_string "description" "Federated video platform"
json_add_string "category" "streaming"
json_add_int "port" 9000
json_add_string "ctl" "peertubectl"
if [ -x /usr/sbin/peertubectl ]; then
json_add_boolean "installed" 1
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "peertube"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/peertube"
json_close_object
# Streamlit
json_add_object
json_add_string "id" "streamlit"
json_add_string "name" "Streamlit Apps"
json_add_string "emoji" "📊"
json_add_string "description" "Python data apps platform"
json_add_string "category" "apps"
json_add_int "port" 8501
json_add_string "ctl" "streamlitctl"
if [ -x /usr/sbin/streamlitctl ]; then
json_add_boolean "installed" 1
if lxc-info -n streamlit -s 2>/dev/null | grep -q "RUNNING"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/streamlit"
json_close_object
# MagicMirror²
json_add_object
json_add_string "id" "magicmirror"
json_add_string "name" "MagicMirror²"
json_add_string "emoji" "🪞"
json_add_string "description" "Smart mirror display platform"
json_add_string "category" "display"
json_add_int "port" 8080
json_add_string "ctl" "mmctl"
if [ -x /usr/sbin/mmctl ]; then
json_add_boolean "installed" 1
if lxc-info -n magicmirror -s 2>/dev/null | grep -q "RUNNING" || docker ps --format '{{.Names}}' 2>/dev/null | grep -q "magicmirror"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/magicmirror2"
json_close_object
# GoToSocial
json_add_object
json_add_string "id" "gotosocial"
json_add_string "name" "GoToSocial"
json_add_string "emoji" "🦣"
json_add_string "description" "Fediverse social network"
json_add_string "category" "social"
json_add_int "port" 8080
json_add_string "ctl" "gotosocialctl"
if [ -x /usr/sbin/gotosocialctl ]; then
json_add_boolean "installed" 1
if lxc-info -n gotosocial -s 2>/dev/null | grep -q "RUNNING" || docker ps --format '{{.Names}}' 2>/dev/null | grep -q "gotosocial"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/gotosocial"
json_close_object
# Media Flow (monitoring)
json_add_object
json_add_string "id" "media-flow"
json_add_string "name" "Media Flow"
json_add_string "emoji" "📡"
json_add_string "description" "Stream detection & monitoring"
json_add_string "category" "monitoring"
json_add_int "port" 0
json_add_string "ctl" ""
# Media Flow is always "installed" if the menu exists
if [ -f /usr/share/luci/menu.d/luci-app-media-flow.json ]; then
json_add_boolean "installed" 1
json_add_string "status" "running"
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
json_add_string "url" "/cgi-bin/luci/admin/services/media-flow"
json_close_object
json_close_array
json_dump
}
# ===========================================
# Service Status - Get detailed status for one service
# ===========================================
get_service_status() {
read input
json_load "$input"
json_get_var service_id id
json_init
case "$service_id" in
jellyfin)
if [ -x /usr/sbin/jellyfinctl ]; then
json_add_boolean "installed" 1
local status=$(jellyfinctl status 2>/dev/null)
if echo "$status" | grep -qi "running"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
json_add_string "details" "$status"
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
;;
lyrion)
if [ -x /usr/sbin/lyrionctl ]; then
json_add_boolean "installed" 1
local status=$(lyrionctl status 2>/dev/null)
if echo "$status" | grep -qi "running"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
json_add_string "details" "$status"
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
;;
jitsi)
if [ -x /usr/sbin/jitsctl ]; then
json_add_boolean "installed" 1
local status=$(jitsctl status 2>/dev/null)
if echo "$status" | grep -qi "running"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
json_add_string "details" "$status"
else
json_add_boolean "installed" 0
json_add_string "status" "not_installed"
fi
;;
*)
json_add_boolean "installed" 0
json_add_string "status" "unknown"
json_add_string "error" "Unknown service: $service_id"
;;
esac
json_dump
}
# ===========================================
# Service Control
# ===========================================
service_start() {
read input
json_load "$input"
json_get_var service_id id
json_init
local ctl=""
case "$service_id" in
jellyfin) ctl="jellyfinctl" ;;
lyrion) ctl="lyrionctl" ;;
jitsi) ctl="jitsctl" ;;
peertube) ctl="peertubectl" ;;
streamlit) ctl="streamlitctl" ;;
magicmirror) ctl="mmctl" ;;
gotosocial) ctl="gotosocialctl" ;;
esac
if [ -z "$ctl" ] || ! command -v "$ctl" >/dev/null 2>&1; then
json_add_boolean "success" 0
json_add_string "error" "Service not installed or unknown"
json_dump
return
fi
local output=$("$ctl" start 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Service started"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
service_stop() {
read input
json_load "$input"
json_get_var service_id id
json_init
local ctl=""
case "$service_id" in
jellyfin) ctl="jellyfinctl" ;;
lyrion) ctl="lyrionctl" ;;
jitsi) ctl="jitsctl" ;;
peertube) ctl="peertubectl" ;;
streamlit) ctl="streamlitctl" ;;
magicmirror) ctl="mmctl" ;;
gotosocial) ctl="gotosocialctl" ;;
esac
if [ -z "$ctl" ] || ! command -v "$ctl" >/dev/null 2>&1; then
json_add_boolean "success" 0
json_add_string "error" "Service not installed or unknown"
json_dump
return
fi
local output=$("$ctl" stop 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Service stopped"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
service_restart() {
read input
json_load "$input"
json_get_var service_id id
json_init
local ctl=""
case "$service_id" in
jellyfin) ctl="jellyfinctl" ;;
lyrion) ctl="lyrionctl" ;;
jitsi) ctl="jitsctl" ;;
peertube) ctl="peertubectl" ;;
streamlit) ctl="streamlitctl" ;;
magicmirror) ctl="mmctl" ;;
gotosocial) ctl="gotosocialctl" ;;
esac
if [ -z "$ctl" ] || ! command -v "$ctl" >/dev/null 2>&1; then
json_add_boolean "success" 0
json_add_string "error" "Service not installed or unknown"
json_dump
return
fi
local output=$("$ctl" restart 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Service restarted"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
# ===========================================
# Main Dispatcher
# ===========================================
case "$1" in
list)
cat << 'EOF'
{
"status": {},
"services": {},
"service_status": {"id": "str"},
"service_start": {"id": "str"},
"service_stop": {"id": "str"},
"service_restart": {"id": "str"}
}
EOF
;;
call)
case "$2" in
status) get_status ;;
services) get_services ;;
service_status) get_service_status ;;
service_start) service_start ;;
service_stop) service_stop ;;
service_restart) service_restart ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;
esac

View File

@ -0,0 +1,13 @@
{
"admin/services/media-hub": {
"title": "Media Hub",
"order": 10,
"action": {
"type": "view",
"path": "media-hub/dashboard"
},
"depends": {
"acl": ["luci-app-media-hub"]
}
}
}

View File

@ -0,0 +1,23 @@
{
"luci-app-media-hub": {
"description": "Grant access to Media Hub dashboard",
"read": {
"ubus": {
"luci.media-hub": [
"status",
"services",
"service_status"
]
}
},
"write": {
"ubus": {
"luci.media-hub": [
"service_start",
"service_stop",
"service_restart"
]
}
}
}
}

View File

@ -0,0 +1,29 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-saas-relay
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
PKG_LICENSE:=MIT
LUCI_TITLE:=LuCI SaaS Relay Dashboard
LUCI_DEPENDS:=+secubox-app-saas-relay +luci-base
include $(TOPDIR)/feeds/luci/luci.mk
define Package/luci-app-saas-relay/install
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.saas-relay $(1)/usr/libexec/rpcd/luci.saas-relay
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-saas-relay.json $(1)/usr/share/rpcd/acl.d/
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-saas-relay.json $(1)/usr/share/luci/menu.d/
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/saas-relay
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/saas-relay/*.js $(1)/www/luci-static/resources/view/saas-relay/
endef
$(eval $(call BuildPackage,luci-app-saas-relay))

View File

@ -0,0 +1,457 @@
'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
var callStatus = rpc.declare({
object: 'luci.saas-relay',
method: 'status',
expect: {}
});
var callListServices = rpc.declare({
object: 'luci.saas-relay',
method: 'list_services',
expect: {}
});
var callServiceEnable = rpc.declare({
object: 'luci.saas-relay',
method: 'service_enable',
params: ['id'],
expect: {}
});
var callServiceDisable = rpc.declare({
object: 'luci.saas-relay',
method: 'service_disable',
params: ['id'],
expect: {}
});
var callServiceAdd = rpc.declare({
object: 'luci.saas-relay',
method: 'service_add',
params: ['id', 'name', 'domain', 'emoji'],
expect: {}
});
var callServiceDelete = rpc.declare({
object: 'luci.saas-relay',
method: 'service_delete',
params: ['id'],
expect: {}
});
var callStart = rpc.declare({
object: 'luci.saas-relay',
method: 'start',
expect: {}
});
var callStop = rpc.declare({
object: 'luci.saas-relay',
method: 'stop',
expect: {}
});
var callSetup = rpc.declare({
object: 'luci.saas-relay',
method: 'setup',
expect: {}
});
var callListCookies = rpc.declare({
object: 'luci.saas-relay',
method: 'list_cookies',
params: ['service'],
expect: {}
});
var callImportCookies = rpc.declare({
object: 'luci.saas-relay',
method: 'import_cookies',
params: ['service', 'cookies'],
expect: {}
});
var callClearCookies = rpc.declare({
object: 'luci.saas-relay',
method: 'clear_cookies',
params: ['service'],
expect: {}
});
var callGetLog = rpc.declare({
object: 'luci.saas-relay',
method: 'get_log',
params: ['lines'],
expect: {}
});
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callListServices()
]);
},
renderStatusCard: function(status) {
var statusEmoji = status.status === 'running' ? '✅' : '⏸️';
var enabledEmoji = status.enabled ? '🔓' : '🔐';
return E('div', { 'class': 'cbi-section', 'style': 'background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); color: #e0e0e0; border-radius: 12px; padding: 20px; margin-bottom: 20px;' }, [
E('h3', { 'style': 'color: #f97316; margin-bottom: 15px;' }, '🔄 SaaS Relay Status'),
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;' }, [
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
E('div', { 'style': 'font-size: 2em;' }, statusEmoji),
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Status'),
E('div', { 'style': 'font-weight: bold;' }, status.status || 'Unknown')
]),
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
E('div', { 'style': 'font-size: 2em;' }, '🔗'),
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Services'),
E('div', { 'style': 'font-weight: bold;' }, (status.enabled_services || 0) + ' / ' + (status.service_count || 0))
]),
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
E('div', { 'style': 'font-size: 2em;' }, '🍪'),
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Cookies'),
E('div', { 'style': 'font-weight: bold;' }, status.total_cookies || 0)
]),
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
E('div', { 'style': 'font-size: 2em;' }, '🌐'),
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Port'),
E('div', { 'style': 'font-weight: bold;' }, status.proxy_port || 8890)
])
])
]);
},
renderServiceCard: function(service) {
var self = this;
var statusEmoji = service.enabled ? '🟢' : '🔴';
var cookieEmoji = service.cookie_count > 0 ? '🍪' : '⚪';
var card = E('div', {
'class': 'service-card',
'style': 'background: #fff; border: 1px solid #ddd; border-radius: 10px; padding: 15px; margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between;'
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 15px;' }, [
E('span', { 'style': 'font-size: 2em;' }, service.emoji || '🔗'),
E('div', {}, [
E('div', { 'style': 'font-weight: bold; font-size: 1.1em;' }, [
statusEmoji, ' ', service.name
]),
E('div', { 'style': 'color: #666; font-size: 0.9em;' }, service.domain),
E('div', { 'style': 'color: #999; font-size: 0.8em;' }, [
cookieEmoji, ' ', service.cookie_count, ' cookies'
])
])
]),
E('div', { 'style': 'display: flex; gap: 8px;' }, [
E('button', {
'class': 'cbi-button',
'style': 'padding: 5px 10px;',
'click': function() { self.toggleService(service); }
}, service.enabled ? '🔐 Disable' : '🔓 Enable'),
E('button', {
'class': 'cbi-button',
'style': 'padding: 5px 10px;',
'click': function() { self.manageCookies(service); }
}, '🍪 Cookies'),
E('button', {
'class': 'cbi-button cbi-button-remove',
'style': 'padding: 5px 10px;',
'click': function() { self.deleteService(service.id); }
}, '🗑️')
])
]);
return card;
},
toggleService: function(service) {
var self = this;
var action = service.enabled ? callServiceDisable : callServiceEnable;
action({ id: service.id }).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', '❌ ' + (result.error || 'Failed')), 'error');
}
});
},
deleteService: function(id) {
var self = this;
if (!confirm('🗑️ Delete service "' + id + '" and its cookies?')) return;
callServiceDelete({ id: id }).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
self.refreshView();
}
});
},
manageCookies: function(service) {
var self = this;
callListCookies({ service: service.id }).then(function(result) {
var content = E('div', {}, [
E('h4', {}, '🍪 Cookies for ' + service.name),
E('p', {}, 'Current: ' + (result.count || 0) + ' cookies'),
E('hr'),
E('h5', {}, 'Import Cookies (JSON format)'),
E('textarea', {
'id': 'cookie-import-text',
'style': 'width: 100%; height: 150px; font-family: monospace;',
'placeholder': '{"cookie_name": "cookie_value", ...}'
}, result.cookies !== '{}' ? result.cookies : ''),
E('div', { 'style': 'margin-top: 10px; display: flex; gap: 10px;' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
var cookies = document.getElementById('cookie-import-text').value;
try {
JSON.parse(cookies);
callImportCookies({ service: service.id, cookies: cookies }).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', res.emoji + ' ' + res.message), 'success');
ui.hideModal();
}
});
} catch (e) {
ui.addNotification(null, E('p', '❌ Invalid JSON'), 'error');
}
}
}, '📥 Import'),
E('button', {
'class': 'cbi-button cbi-button-remove',
'click': function() {
if (confirm('Clear all cookies for ' + service.name + '?')) {
callClearCookies({ service: service.id }).then(function(res) {
ui.addNotification(null, E('p', res.emoji + ' ' + res.message), 'success');
ui.hideModal();
});
}
}
}, '🗑️ Clear'),
E('button', {
'class': 'cbi-button',
'click': ui.hideModal
}, 'Close')
])
]);
ui.showModal('Cookie Manager', content);
});
},
addService: function() {
var self = this;
var content = E('div', {}, [
E('h4', {}, ' Add New Service'),
E('div', { 'style': 'display: grid; gap: 10px;' }, [
E('label', {}, [
'ID (lowercase, no spaces):',
E('input', { 'type': 'text', 'id': 'new-svc-id', 'style': 'width: 100%; padding: 5px;' })
]),
E('label', {}, [
'Name:',
E('input', { 'type': 'text', 'id': 'new-svc-name', 'style': 'width: 100%; padding: 5px;' })
]),
E('label', {}, [
'Domain:',
E('input', { 'type': 'text', 'id': 'new-svc-domain', 'style': 'width: 100%; padding: 5px;', 'placeholder': 'example.com' })
]),
E('label', {}, [
'Emoji:',
E('input', { 'type': 'text', 'id': 'new-svc-emoji', 'style': 'width: 100%; padding: 5px;', 'placeholder': '🔗', 'value': '🔗' })
])
]),
E('div', { 'style': 'margin-top: 15px; display: flex; gap: 10px;' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
var id = document.getElementById('new-svc-id').value;
var name = document.getElementById('new-svc-name').value;
var domain = document.getElementById('new-svc-domain').value;
var emoji = document.getElementById('new-svc-emoji').value || '🔗';
if (!id || !name || !domain) {
ui.addNotification(null, E('p', '❌ All fields required'), 'error');
return;
}
callServiceAdd({ id: id, name: name, domain: domain, emoji: emoji }).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', res.emoji + ' ' + res.message), 'success');
ui.hideModal();
self.refreshView();
} else {
ui.addNotification(null, E('p', '❌ ' + res.error), 'error');
}
});
}
}, ' Add Service'),
E('button', {
'class': 'cbi-button',
'click': ui.hideModal
}, 'Cancel')
])
]);
ui.showModal('Add Service', content);
},
startRelay: function() {
var self = this;
callStart().then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', '❌ ' + result.error), 'error');
}
});
},
stopRelay: function() {
var self = this;
callStop().then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', '❌ ' + result.error), 'error');
}
});
},
setupRelay: function() {
var self = this;
callSetup().then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
self.refreshView();
} else {
ui.addNotification(null, E('p', '❌ ' + result.error), 'error');
}
});
},
showLog: function() {
callGetLog({ lines: 50 }).then(function(result) {
var entries = result.entries || [];
var logContent = entries.length > 0 ? entries.join('\n') : 'No activity logged';
var content = E('div', {}, [
E('h4', {}, '📋 Activity Log'),
E('pre', {
'style': 'background: #1a1a2e; color: #e0e0e0; padding: 15px; border-radius: 8px; max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 0.85em;'
}, logContent),
E('div', { 'style': 'margin-top: 10px;' }, [
E('button', {
'class': 'cbi-button',
'click': ui.hideModal
}, 'Close')
])
]);
ui.showModal('Activity Log', content);
});
},
refreshView: function() {
var self = this;
Promise.all([callStatus(), callListServices()]).then(function(results) {
var status = results[0];
var services = results[1].services || [];
// Update status card
var statusContainer = document.getElementById('status-container');
statusContainer.innerHTML = '';
statusContainer.appendChild(self.renderStatusCard(status));
// Update services
var servicesContainer = document.getElementById('services-container');
servicesContainer.innerHTML = '';
services.forEach(function(svc) {
servicesContainer.appendChild(self.renderServiceCard(svc));
});
});
},
render: function(data) {
var self = this;
var status = data[0] || {};
var services = (data[1] && data[1].services) || [];
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', { 'class': 'cbi-map-title' }, '🔄 SaaS Relay'),
E('div', { 'class': 'cbi-map-descr' },
'Shared browser session proxy for team access to external SaaS services. Uses SecuBox authentication with mitmproxy cookie injection.'),
// Control buttons
E('div', { 'style': 'margin: 20px 0; display: flex; gap: 10px; flex-wrap: wrap;' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.startRelay(); }
}, '▶️ Start'),
E('button', {
'class': 'cbi-button',
'click': function() { self.stopRelay(); }
}, '⏹️ Stop'),
E('button', {
'class': 'cbi-button',
'click': function() { self.setupRelay(); }
}, '⚙️ Setup'),
E('button', {
'class': 'cbi-button cbi-button-add',
'click': function() { self.addService(); }
}, ' Add Service'),
E('button', {
'class': 'cbi-button',
'click': function() { self.showLog(); }
}, '📋 View Log'),
E('button', {
'class': 'cbi-button',
'click': function() { self.refreshView(); }
}, '🔄 Refresh')
]),
// Status card container
E('div', { 'id': 'status-container' }),
// Services section
E('h3', { 'style': 'margin-top: 20px;' }, '🔗 Connected Services'),
E('div', { 'id': 'services-container' })
]);
// Render initial content
var statusContainer = view.querySelector('#status-container');
statusContainer.appendChild(this.renderStatusCard(status));
var servicesContainer = view.querySelector('#services-container');
if (services.length === 0) {
servicesContainer.appendChild(E('p', { 'style': 'color: #666; text-align: center; padding: 20px;' },
'No services configured. Click " Add Service" to get started.'));
} else {
services.forEach(function(svc) {
servicesContainer.appendChild(self.renderServiceCard(svc));
});
}
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,461 @@
#!/bin/sh
# SaaS Relay RPCD backend
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
CONFIG="saas-relay"
SAASCTL="/usr/sbin/saasctl"
DATA_PATH="/srv/saas-relay"
COOKIES_PATH="$DATA_PATH/cookies"
LOG_PATH="$DATA_PATH/logs"
uci_get() { uci -q get ${CONFIG}.$1; }
# ===========================================
# Status Methods
# ===========================================
get_status() {
json_init
local enabled=$(uci_get main.enabled) || enabled="0"
local status=$(uci_get main.status) || status="stopped"
local proxy_port=$(uci_get main.proxy_port) || proxy_port="8890"
json_add_boolean "enabled" "$enabled"
json_add_string "status" "$status"
json_add_int "proxy_port" "$proxy_port"
json_add_string "data_path" "$DATA_PATH"
# Count services and cookies
local service_count=0
local cookie_count=0
local enabled_services=0
config_load "$CONFIG"
_count_service() {
local section="$1"
local svc_enabled domain
config_get domain "$section" domain
[ -z "$domain" ] && return
service_count=$((service_count + 1))
config_get svc_enabled "$section" enabled "0"
[ "$svc_enabled" = "1" ] && enabled_services=$((enabled_services + 1))
}
config_foreach _count_service service
for f in "$COOKIES_PATH"/*.json; do
[ -f "$f" ] || continue
local count=$(grep -c '"' "$f" 2>/dev/null | awk '{print int($1/2)}')
cookie_count=$((cookie_count + count))
done
json_add_int "service_count" "$service_count"
json_add_int "enabled_services" "$enabled_services"
json_add_int "total_cookies" "$cookie_count"
json_dump
}
# ===========================================
# Service Methods
# ===========================================
list_services() {
json_init
json_add_array "services"
config_load "$CONFIG"
_add_service() {
local section="$1"
local enabled name emoji domain status cookie_domains auth_required last_check
config_get domain "$section" domain
[ -z "$domain" ] && return
config_get enabled "$section" enabled "0"
config_get name "$section" name "$section"
config_get emoji "$section" emoji "🔗"
config_get status "$section" status "disconnected"
config_get cookie_domains "$section" cookie_domains "$domain"
config_get auth_required "$section" auth_required "1"
config_get last_check "$section" last_check "0"
# Count cookies for this service
local cookie_file="$COOKIES_PATH/${section}.json"
local cookie_count=0
[ -f "$cookie_file" ] && cookie_count=$(grep -c '"' "$cookie_file" 2>/dev/null | awk '{print int($1/2)}')
json_add_object
json_add_string "id" "$section"
json_add_string "name" "$name"
json_add_string "emoji" "$emoji"
json_add_string "domain" "$domain"
json_add_boolean "enabled" "$enabled"
json_add_string "status" "$status"
json_add_int "cookie_count" "$cookie_count"
json_add_string "cookie_domains" "$cookie_domains"
json_add_boolean "auth_required" "$auth_required"
json_add_int "last_check" "$last_check"
json_close_object
}
config_foreach _add_service service
json_close_array
json_dump
}
service_enable() {
read input
json_load "$input"
json_get_var id id
json_init
if [ -z "$id" ]; then
json_add_boolean "success" 0
json_add_string "error" "Service ID required"
json_dump
return
fi
uci set ${CONFIG}.${id}.enabled='1'
uci commit ${CONFIG}
json_add_boolean "success" 1
json_add_string "message" "Service enabled"
json_add_string "emoji" "🔓"
json_dump
}
service_disable() {
read input
json_load "$input"
json_get_var id id
json_init
if [ -z "$id" ]; then
json_add_boolean "success" 0
json_add_string "error" "Service ID required"
json_dump
return
fi
uci set ${CONFIG}.${id}.enabled='0'
uci commit ${CONFIG}
json_add_boolean "success" 1
json_add_string "message" "Service disabled"
json_add_string "emoji" "🔐"
json_dump
}
service_add() {
read input
json_load "$input"
json_get_var id id
json_get_var name name
json_get_var domain domain
json_get_var emoji emoji
json_init
if [ -z "$id" ] || [ -z "$name" ] || [ -z "$domain" ]; then
json_add_boolean "success" 0
json_add_string "error" "ID, name, and domain required"
json_dump
return
fi
[ -z "$emoji" ] && emoji="🔗"
uci set ${CONFIG}.${id}=service
uci set ${CONFIG}.${id}.enabled='1'
uci set ${CONFIG}.${id}.name="$name"
uci set ${CONFIG}.${id}.emoji="$emoji"
uci set ${CONFIG}.${id}.domain="$domain"
uci set ${CONFIG}.${id}.cookie_domains="$domain,.$domain"
uci set ${CONFIG}.${id}.auth_required='1'
uci set ${CONFIG}.${id}.status='disconnected'
uci commit ${CONFIG}
json_add_boolean "success" 1
json_add_string "message" "Service added: $name"
json_add_string "emoji" "$emoji"
json_dump
}
service_delete() {
read input
json_load "$input"
json_get_var id id
json_init
if [ -z "$id" ]; then
json_add_boolean "success" 0
json_add_string "error" "Service ID required"
json_dump
return
fi
uci delete ${CONFIG}.${id} 2>/dev/null
uci commit ${CONFIG}
# Remove cookies
rm -f "$COOKIES_PATH/${id}.json"
json_add_boolean "success" 1
json_add_string "message" "Service deleted"
json_add_string "emoji" "🗑️"
json_dump
}
# ===========================================
# Cookie Methods
# ===========================================
list_cookies() {
read input
json_load "$input"
json_get_var service service
json_init
json_add_boolean "success" 1
if [ -n "$service" ]; then
local cookie_file="$COOKIES_PATH/${service}.json"
json_add_string "service" "$service"
if [ -f "$cookie_file" ]; then
local content=$(cat "$cookie_file")
json_add_string "cookies" "$content"
local count=$(grep -c '"' "$cookie_file" 2>/dev/null | awk '{print int($1/2)}')
json_add_int "count" "$count"
else
json_add_string "cookies" "{}"
json_add_int "count" 0
fi
else
json_add_array "services"
for f in "$COOKIES_PATH"/*.json; do
[ -f "$f" ] || continue
local svc=$(basename "$f" .json)
local count=$(grep -c '"' "$f" 2>/dev/null | awk '{print int($1/2)}')
local size=$(stat -c%s "$f" 2>/dev/null || echo 0)
local modified=$(stat -c%Y "$f" 2>/dev/null || echo 0)
json_add_object
json_add_string "service" "$svc"
json_add_int "count" "$count"
json_add_int "size" "$size"
json_add_int "modified" "$modified"
json_close_object
done
json_close_array
fi
json_dump
}
import_cookies() {
read input
json_load "$input"
json_get_var service service
json_get_var cookies cookies
json_init
if [ -z "$service" ] || [ -z "$cookies" ]; then
json_add_boolean "success" 0
json_add_string "error" "Service and cookies required"
json_dump
return
fi
mkdir -p "$COOKIES_PATH"
echo "$cookies" > "$COOKIES_PATH/${service}.json"
chmod 600 "$COOKIES_PATH/${service}.json"
local count=$(grep -c '"' "$COOKIES_PATH/${service}.json" 2>/dev/null | awk '{print int($1/2)}')
json_add_boolean "success" 1
json_add_string "message" "Imported $count cookies"
json_add_string "emoji" "🍪"
json_add_int "count" "$count"
json_dump
}
clear_cookies() {
read input
json_load "$input"
json_get_var service service
json_init
if [ -n "$service" ]; then
rm -f "$COOKIES_PATH/${service}.json"
json_add_string "message" "Cleared cookies for $service"
else
rm -f "$COOKIES_PATH"/*.json
json_add_string "message" "Cleared all cookies"
fi
json_add_boolean "success" 1
json_add_string "emoji" "🗑️"
json_dump
}
# ===========================================
# Control Methods
# ===========================================
start_relay() {
json_init
local output=$("$SAASCTL" start 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "SaaS Relay started"
json_add_string "emoji" "🔄"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
stop_relay() {
json_init
local output=$("$SAASCTL" stop 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "SaaS Relay stopped"
json_add_string "emoji" "🔌"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
setup_relay() {
json_init
local output=$("$SAASCTL" setup 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Setup complete"
json_add_string "emoji" "✅"
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_dump
}
# ===========================================
# Activity Log
# ===========================================
get_log() {
read input
json_load "$input"
json_get_var lines lines
[ -z "$lines" ] && lines=50
json_init
json_add_boolean "success" 1
json_add_array "entries"
if [ -f "$LOG_PATH/activity.log" ]; then
tail -n "$lines" "$LOG_PATH/activity.log" | while read line; do
json_add_string "" "$line"
done
fi
json_close_array
json_dump
}
clear_log() {
json_init
> "$LOG_PATH/activity.log" 2>/dev/null
json_add_boolean "success" 1
json_add_string "message" "Log cleared"
json_add_string "emoji" "📋"
json_dump
}
# ===========================================
# Main Dispatcher
# ===========================================
case "$1" in
list)
cat << 'EOF'
{
"status": {},
"list_services": {},
"service_enable": {"id": "str"},
"service_disable": {"id": "str"},
"service_add": {"id": "str", "name": "str", "domain": "str", "emoji": "str"},
"service_delete": {"id": "str"},
"list_cookies": {"service": "str"},
"import_cookies": {"service": "str", "cookies": "str"},
"clear_cookies": {"service": "str"},
"start": {},
"stop": {},
"setup": {},
"get_log": {"lines": "int"},
"clear_log": {}
}
EOF
;;
call)
case "$2" in
status) get_status ;;
list_services) list_services ;;
service_enable) service_enable ;;
service_disable) service_disable ;;
service_add) service_add ;;
service_delete) service_delete ;;
list_cookies) list_cookies ;;
import_cookies) import_cookies ;;
clear_cookies) clear_cookies ;;
start) start_relay ;;
stop) stop_relay ;;
setup) setup_relay ;;
get_log) get_log ;;
clear_log) clear_log ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;
esac

View File

@ -0,0 +1,14 @@
{
"admin/services/saas-relay": {
"title": "SaaS Relay",
"order": 85,
"action": {
"type": "view",
"path": "saas-relay/overview"
},
"depends": {
"acl": ["luci-app-saas-relay"],
"uci": { "saas-relay": true }
}
}
}

View File

@ -0,0 +1,33 @@
{
"luci-app-saas-relay": {
"description": "Grant access to SaaS Relay",
"read": {
"ubus": {
"luci.saas-relay": [
"status",
"list_services",
"list_cookies",
"get_log"
]
},
"uci": ["saas-relay"]
},
"write": {
"ubus": {
"luci.saas-relay": [
"service_enable",
"service_disable",
"service_add",
"service_delete",
"import_cookies",
"clear_cookies",
"start",
"stop",
"setup",
"clear_log"
]
},
"uci": ["saas-relay"]
}
}
}

View File

@ -60,11 +60,24 @@ return view.extend({
}, s);
});
// Login stats
var lastLogin = user.last_login || 'never';
var loginSuccess = user.login_success || 0;
var loginFailure = user.login_failure || 0;
// Color code failures
var failColor = loginFailure > 10 ? '#f44336' : (loginFailure > 0 ? '#ff9800' : '#4caf50');
return E('tr', {}, [
E('td', {}, user.username),
E('td', {}, user.email),
E('td', {}, lastLogin),
E('td', { 'style': 'text-align:center;' }, [
E('span', { 'style': 'color:#4caf50;font-weight:bold;' }, String(loginSuccess)),
E('span', {}, ' / '),
E('span', { 'style': 'color:' + failColor + ';font-weight:bold;' }, String(loginFailure))
]),
E('td', {}, services),
E('td', {}, user.enabled === '1' ? 'Yes' : 'No'),
E('td', {}, [
E('button', {
'class': 'btn cbi-button',
@ -271,8 +284,9 @@ return view.extend({
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Username')),
E('th', { 'class': 'th' }, _('Email')),
E('th', { 'class': 'th' }, _('Last Login')),
E('th', { 'class': 'th', 'style': 'text-align:center;' }, _('OK / Fail')),
E('th', { 'class': 'th' }, _('Services')),
E('th', { 'class': 'th' }, _('Enabled')),
E('th', { 'class': 'th' }, _('Actions'))
])
].concat(userRows)) :

View File

@ -11,7 +11,7 @@ Description: Unified AI security insights dashboard for SecuBox.
DNS Guard, Network Anomaly, CVE Triage, and LocalRecall.
Provides security posture scoring and AI-powered analysis.
Filename: luci-app-ai-insights_1.0.0-r1_all.ipk
Size: 11638
Size: 11639
Package: luci-app-auth-guardian
Version: 0.4.0-r3
@ -23,7 +23,7 @@ Architecture: all
Installed-Size: 71680
Description: Comprehensive authentication and session management with captive portal, OAuth2/OIDC integration, voucher system, and time-based access control
Filename: luci-app-auth-guardian_0.4.0-r3_all.ipk
Size: 12400
Size: 12391
Package: luci-app-backup
Version: 1.0.0-r1
@ -35,7 +35,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI Backup Manager
Filename: luci-app-backup_1.0.0-r1_all.ipk
Size: 4534
Size: 4537
Package: luci-app-bandwidth-manager
Version: 0.5.0-r2
@ -59,7 +59,7 @@ Architecture: all
Installed-Size: 122880
Description: Dashboard for managing local CDN caching proxy on OpenWrt
Filename: luci-app-cdn-cache_0.5.0-r3_all.ipk
Size: 24255
Size: 24257
Package: luci-app-client-guardian
Version: 0.4.0-r7
@ -71,7 +71,7 @@ Architecture: all
Installed-Size: 276480
Description: Network Access Control with client monitoring, zone management, captive portal, parental controls, and SMS/email alerts
Filename: luci-app-client-guardian_0.4.0-r7_all.ipk
Size: 52684
Size: 52687
Package: luci-app-cloner
Version: 1.0.0-r1
@ -82,7 +82,7 @@ Architecture: all
Installed-Size: 102400
Description: SecuBox cloning station for building and deploying clone images
Filename: luci-app-cloner_1.0.0-r1_all.ipk
Size: 19433
Size: 19435
Package: luci-app-config-advisor
Version: 1.0.0-r1
@ -105,7 +105,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI Cookie Tracker Dashboard
Filename: luci-app-cookie-tracker_1.0.0-r1_all.ipk
Size: 5653
Size: 5660
Package: luci-app-crowdsec-dashboard
Version: 0.7.0-r32
@ -117,7 +117,7 @@ Architecture: all
Installed-Size: 184320
Description: Real-time security monitoring dashboard for CrowdSec on OpenWrt
Filename: luci-app-crowdsec-dashboard_0.7.0-r32_all.ipk
Size: 34938
Size: 34940
Package: luci-app-cve-triage
Version: 1.0.0-r1
@ -129,7 +129,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI CVE Triage Dashboard
Filename: luci-app-cve-triage_1.0.0-r1_all.ipk
Size: 5948
Size: 5943
Package: luci-app-cyberfeed
Version: 0.1.1-r1
@ -153,7 +153,7 @@ Architecture: all
Installed-Size: 71680
Description: LuCI SecuBox Device Intelligence
Filename: luci-app-device-intel_1.0.0-r1_all.ipk
Size: 12047
Size: 12049
Package: luci-app-dnsguard
Version: 1.1.0-r1
@ -172,7 +172,7 @@ Description: SecuBox DNS Guard provides privacy-focused DNS management with AI-
- Real-time alerts and blocklist management
- Domain analysis with LocalAI integration
Filename: luci-app-dnsguard_1.1.0-r1_all.ipk
Size: 12448
Size: 12449
Package: luci-app-dns-provider
Version: 1.0.0-r1
@ -184,7 +184,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI DNS Provider Manager
Filename: luci-app-dns-provider_1.0.0-r1_all.ipk
Size: 7168
Size: 7174
Package: luci-app-domoticz
Version: 1.0.0-r1
@ -196,7 +196,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI Domoticz Home Automation Configuration
Filename: luci-app-domoticz_1.0.0-r1_all.ipk
Size: 7123
Size: 7118
Package: luci-app-exposure
Version: 1.0.0-r3
@ -208,7 +208,7 @@ Architecture: all
Installed-Size: 71680
Description: LuCI SecuBox Service Exposure Manager
Filename: luci-app-exposure_1.0.0-r3_all.ipk
Size: 11696
Size: 11698
Package: luci-app-gitea
Version: 1.0.0-r2
@ -220,7 +220,7 @@ Architecture: all
Installed-Size: 92160
Description: Modern dashboard for Gitea Platform management on OpenWrt
Filename: luci-app-gitea_1.0.0-r2_all.ipk
Size: 16623
Size: 16621
Package: luci-app-glances
Version: 1.0.0-r2
@ -232,7 +232,7 @@ Architecture: all
Installed-Size: 51200
Description: Modern dashboard for Glances system monitoring with SecuBox theme
Filename: luci-app-glances_1.0.0-r2_all.ipk
Size: 7022
Size: 7017
Package: luci-app-gotosocial
Version: 0.1.0-r1
@ -244,7 +244,7 @@ Architecture: all
Installed-Size: 51200
Description: LuCI app for GoToSocial Fediverse Server
Filename: luci-app-gotosocial_0.1.0-r1_all.ipk
Size: 8207
Size: 8210
Package: luci-app-haproxy
Version: 1.0.0-r8
@ -268,7 +268,7 @@ Architecture: all
Installed-Size: 194560
Description: Modern dashboard for Hexo static site generator on OpenWrt
Filename: luci-app-hexojs_1.0.0-r3_all.ipk
Size: 30452
Size: 30450
Package: luci-app-iot-guard
Version: 1.0.0-r1
@ -280,7 +280,7 @@ Architecture: all
Installed-Size: 61440
Description: IoT device isolation and security monitoring interface
Filename: luci-app-iot-guard_1.0.0-r1_all.ipk
Size: 10535
Size: 10538
Package: luci-app-jabber
Version: 0
@ -292,7 +292,7 @@ Architecture: all
Installed-Size: 61440
Description: LuCI Jabber/XMPP Server (Prosody)
Filename: luci-app-jabber_0_all.ipk
Size: 9304
Size: 9308
Package: luci-app-jellyfin
Version: 1.0.0-r1
@ -304,7 +304,7 @@ Architecture: all
Installed-Size: 51200
Description: LuCI Jellyfin Media Server Configuration
Filename: luci-app-jellyfin_1.0.0-r1_all.ipk
Size: 10482
Size: 10485
Package: luci-app-jitsi
Version: 1.0.0-r1
@ -316,7 +316,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI Jitsi Meet Configuration
Filename: luci-app-jitsi_1.0.0-r1_all.ipk
Size: 5175
Size: 5173
Package: luci-app-ksm-manager
Version: 0.4.0-r2
@ -328,7 +328,7 @@ Architecture: all
Installed-Size: 112640
Description: Centralized cryptographic key management with hardware security module (HSM) support for Nitrokey and YubiKey devices. Provides secure key storage, certificate management, SSH key handling, and secret storage with audit logging.
Filename: luci-app-ksm-manager_0.4.0-r2_all.ipk
Size: 18779
Size: 18777
Package: luci-app-localai
Version: 0.1.0-r15
@ -352,7 +352,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI LocalRecall AI Memory Dashboard
Filename: luci-app-localrecall_1.0.0-r1_all.ipk
Size: 8418
Size: 8420
Package: luci-app-lyrion
Version: 1.0.0-r1
@ -364,7 +364,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI support for Lyrion Music Server
Filename: luci-app-lyrion_1.0.0-r1_all.ipk
Size: 6839
Size: 6840
Package: luci-app-mac-guardian
Version: 0.5.0-r1
@ -376,7 +376,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI MAC Guardian - WiFi MAC Security Monitor
Filename: luci-app-mac-guardian_0.5.0-r1_all.ipk
Size: 6664
Size: 6660
Package: luci-app-magicmirror2
Version: 0.4.0-r6
@ -388,7 +388,7 @@ Architecture: all
Installed-Size: 71680
Description: Modern dashboard for MagicMirror2 smart display platform with module manager and SecuBox theme
Filename: luci-app-magicmirror2_0.4.0-r6_all.ipk
Size: 12361
Size: 12358
Package: luci-app-mailinabox
Version: 1.0.0-r1
@ -400,7 +400,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI support for Mail-in-a-Box
Filename: luci-app-mailinabox_1.0.0-r1_all.ipk
Size: 5484
Size: 5487
Package: luci-app-mailserver
Version: 1.0.0-r1
@ -412,7 +412,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI Mail Server Manager
Filename: luci-app-mailserver_1.0.0-r1_all.ipk
Size: 6511
Size: 6509
Package: luci-app-master-link
Version: 1.0.0-r1
@ -424,7 +424,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI SecuBox Master-Link Mesh Management
Filename: luci-app-master-link_1.0.0-r1_all.ipk
Size: 6304
Size: 6309
Package: luci-app-matrix
Version: 1.0.0-r1
@ -448,7 +448,7 @@ Architecture: all
Installed-Size: 133120
Description: Real-time detection and monitoring of streaming services (Netflix, YouTube, Spotify, etc.) with quality estimation, history tracking, and alerts. Supports nDPId local DPI and netifyd.
Filename: luci-app-media-flow_0.6.4-r1_all.ipk
Size: 25381
Size: 25379
Package: luci-app-metablogizer
Version: 1.1.0-r1
@ -460,7 +460,7 @@ Architecture: all
Installed-Size: 133120
Description: LuCI support for MetaBlogizer Static Site Publisher
Filename: luci-app-metablogizer_1.1.0-r1_all.ipk
Size: 26207
Size: 26203
Package: luci-app-metabolizer
Version: 1.0.0-r2
@ -472,7 +472,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI support for Metabolizer CMS
Filename: luci-app-metabolizer_1.0.0-r2_all.ipk
Size: 4821
Size: 4819
Package: luci-app-mitmproxy
Version: 0.5.0-r2
@ -484,7 +484,7 @@ Architecture: all
Installed-Size: 71680
Description: Modern dashboard for mitmproxy HTTPS traffic inspection with SecuBox theme
Filename: luci-app-mitmproxy_0.5.0-r2_all.ipk
Size: 13232
Size: 13234
Package: luci-app-mmpm
Version: 0.2.0-r3
@ -496,7 +496,7 @@ Architecture: all
Installed-Size: 51200
Description: Web interface for MMPM - MagicMirror Package Manager
Filename: luci-app-mmpm_0.2.0-r3_all.ipk
Size: 7975
Size: 7971
Package: luci-app-mqtt-bridge
Version: 0.4.0-r4
@ -508,7 +508,7 @@ Architecture: all
Installed-Size: 122880
Description: USB-to-MQTT IoT hub with SecuBox theme
Filename: luci-app-mqtt-bridge_0.4.0-r4_all.ipk
Size: 22689
Size: 22687
Package: luci-app-ndpid
Version: 1.1.2-r2
@ -520,7 +520,7 @@ Architecture: all
Installed-Size: 122880
Description: Modern dashboard for nDPId deep packet inspection on OpenWrt
Filename: luci-app-ndpid_1.1.2-r2_all.ipk
Size: 21704
Size: 21701
Package: luci-app-netdata-dashboard
Version: 0.5.0-r2
@ -532,7 +532,7 @@ Architecture: all
Installed-Size: 112640
Description: Real-time system monitoring dashboard with Netdata integration for OpenWrt
Filename: luci-app-netdata-dashboard_0.5.0-r2_all.ipk
Size: 20558
Size: 20559
Package: luci-app-network-anomaly
Version: 1.0.0-r1
@ -544,7 +544,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI Network Anomaly Detection Dashboard
Filename: luci-app-network-anomaly_1.0.0-r1_all.ipk
Size: 7641
Size: 7643
Package: luci-app-network-modes
Version: 0.5.0-r3
@ -568,7 +568,7 @@ Architecture: all
Installed-Size: 81920
Description: Unified network services dashboard with DNS/hosts sync, CDN cache control, and WPAD auto-proxy configuration
Filename: luci-app-network-tweaks_1.0.0-r7_all.ipk
Size: 15949
Size: 15947
Package: luci-app-nextcloud
Version: 1.0.0-r1
@ -580,7 +580,7 @@ Architecture: all
Installed-Size: 51200
Description: LuCI support for Nextcloud LXC
Filename: luci-app-nextcloud_1.0.0-r1_all.ipk
Size: 10347
Size: 10348
Package: luci-app-ollama
Version: 0.1.0-r1
@ -592,7 +592,7 @@ Architecture: all
Installed-Size: 71680
Description: Modern dashboard for Ollama LLM management on OpenWrt
Filename: luci-app-ollama_0.1.0-r1_all.ipk
Size: 14338
Size: 14337
Package: luci-app-peertube
Version: 0
@ -604,7 +604,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI PeerTube Video Platform
Filename: luci-app-peertube_0_all.ipk
Size: 5760
Size: 5757
Package: luci-app-picobrew
Version: 1.0.0-r1
@ -616,7 +616,7 @@ Architecture: all
Installed-Size: 51200
Description: Modern dashboard for PicoBrew Server management on OpenWrt
Filename: luci-app-picobrew_1.0.0-r1_all.ipk
Size: 9534
Size: 9529
Package: luci-app-secubox
Version: 0.7.1-r4
@ -628,7 +628,7 @@ Architecture: all
Installed-Size: 440320
Description: Central control hub for all SecuBox modules. Provides unified dashboard, module status, system health monitoring, and quick actions.
Filename: luci-app-secubox_0.7.1-r4_all.ipk
Size: 82095
Size: 82096
Package: luci-app-secubox-admin
Version: 1.0.0-r19
@ -639,7 +639,7 @@ Architecture: all
Installed-Size: 337920
Description: Unified admin control center for SecuBox appstore plugins with system monitoring
Filename: luci-app-secubox-admin_1.0.0-r19_all.ipk
Size: 58041
Size: 58040
Package: luci-app-secubox-crowdsec
Version: 1.0.0-r3
@ -651,7 +651,7 @@ Architecture: all
Installed-Size: 81920
Description: LuCI SecuBox CrowdSec Dashboard
Filename: luci-app-secubox-crowdsec_1.0.0-r3_all.ipk
Size: 13918
Size: 13916
Package: luci-app-secubox-mirror
Version: 0.1.0-r1
@ -675,7 +675,7 @@ Architecture: all
Installed-Size: 81920
Description: Real-time DSA switch port statistics, error monitoring, and network health diagnostics
Filename: luci-app-secubox-netdiag_1.0.0-r1_all.ipk
Size: 15347
Size: 15344
Package: luci-app-secubox-netifyd
Version: 1.2.1-r1
@ -699,7 +699,7 @@ Architecture: all
Installed-Size: 245760
Description: LuCI SecuBox P2P Hub
Filename: luci-app-secubox-p2p_0.1.0-r1_all.ipk
Size: 46832
Size: 46831
Package: luci-app-secubox-portal
Version: 0.7.0-r3
@ -711,7 +711,7 @@ Architecture: all
Installed-Size: 194560
Description: Unified entry point for all SecuBox applications with tabbed navigation
Filename: luci-app-secubox-portal_0.7.0-r3_all.ipk
Size: 41686
Size: 41681
Package: luci-app-secubox-security-threats
Version: 1.0.0-r4
@ -723,7 +723,18 @@ Architecture: all
Installed-Size: 61440
Description: Unified dashboard integrating netifyd DPI threats with CrowdSec intelligence for real-time threat monitoring and automated blocking
Filename: luci-app-secubox-security-threats_1.0.0-r4_all.ipk
Size: 10659
Size: 10657
Package: luci-app-secubox-users
Version: 1.0.0-r1
Depends: secubox-core-users
Section: luci
Maintainer: OpenWrt LuCI community
Architecture: all
Installed-Size: 30720
Description: LuCI SecuBox User Management
Filename: luci-app-secubox-users_1.0.0-r1_all.ipk
Size: 5141
Package: luci-app-service-registry
Version: 1.0.0-r1
@ -735,7 +746,7 @@ Architecture: all
Installed-Size: 194560
Description: Unified service aggregation with HAProxy vhosts, Tor hidden services, and QR-coded landing page
Filename: luci-app-service-registry_1.0.0-r1_all.ipk
Size: 39952
Size: 39951
Package: luci-app-simplex
Version: 1.0.0-r1
@ -747,7 +758,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI SimpleX Chat Server Configuration
Filename: luci-app-simplex_1.0.0-r1_all.ipk
Size: 7036
Size: 7042
Package: luci-app-streamlit
Version: 1.0.0-r11
@ -759,7 +770,7 @@ Architecture: all
Installed-Size: 112640
Description: Multi-instance Streamlit management with Gitea integration
Filename: luci-app-streamlit_1.0.0-r11_all.ipk
Size: 20568
Size: 20567
Package: luci-app-system-hub
Version: 0.5.1-r4
@ -771,7 +782,7 @@ Architecture: all
Installed-Size: 327680
Description: Central system control with monitoring, services, logs, and backup
Filename: luci-app-system-hub_0.5.1-r4_all.ipk
Size: 62082
Size: 62081
Package: luci-app-threat-analyst
Version: 1.0.0-r1
@ -783,7 +794,7 @@ Architecture: all
Installed-Size: 51200
Description: LuCI Threat Analyst Dashboard
Filename: luci-app-threat-analyst_1.0.0-r1_all.ipk
Size: 10144
Size: 10145
Package: luci-app-tor
Version: 1.0.0-r1
@ -795,7 +806,7 @@ Architecture: all
Installed-Size: 92160
Description: Modern dashboard for Tor anonymization on OpenWrt
Filename: luci-app-tor_1.0.0-r1_all.ipk
Size: 17822
Size: 17816
Package: luci-app-tor-shield
Version: 1.0.0-r10
@ -807,7 +818,7 @@ Architecture: all
Installed-Size: 122880
Description: Modern dashboard for Tor anonymization on OpenWrt
Filename: luci-app-tor-shield_1.0.0-r10_all.ipk
Size: 22766
Size: 22764
Package: luci-app-traffic-shaper
Version: 0.4.0-r2
@ -819,7 +830,7 @@ Architecture: all
Installed-Size: 81920
Description: Advanced traffic shaping with TC/CAKE for precise bandwidth control
Filename: luci-app-traffic-shaper_0.4.0-r2_all.ipk
Size: 14591
Size: 14587
Package: luci-app-vhost-manager
Version: 0.5.0-r5
@ -831,7 +842,7 @@ Architecture: all
Installed-Size: 153600
Description: Nginx reverse proxy manager with Let's Encrypt SSL certificates, authentication, and WebSocket support
Filename: luci-app-vhost-manager_0.5.0-r5_all.ipk
Size: 26284
Size: 26283
Package: luci-app-voip
Version: 1.0.0-r1
@ -843,7 +854,7 @@ Architecture: all
Installed-Size: 81920
Description: LuCI VoIP PBX Management
Filename: luci-app-voip_1.0.0-r1_all.ipk
Size: 11046
Size: 11043
Package: luci-app-vortex-dns
Version: 1.0.0-r1
@ -855,7 +866,7 @@ Architecture: all
Installed-Size: 40960
Description: LuCI Vortex DNS Dashboard
Filename: luci-app-vortex-dns_1.0.0-r1_all.ipk
Size: 6078
Size: 6079
Package: luci-app-vortex-firewall
Version: 1.0.0-r1
@ -867,7 +878,7 @@ Architecture: all
Installed-Size: 30720
Description: LuCI Vortex DNS Firewall Dashboard
Filename: luci-app-vortex-firewall_1.0.0-r1_all.ipk
Size: 5451
Size: 5450
Package: luci-app-wazuh
Version: 1.0.0-r1
@ -878,7 +889,7 @@ Architecture: all
Installed-Size: 71680
Description: Unified security monitoring dashboard for Wazuh SIEM/XDR integration
Filename: luci-app-wazuh_1.0.0-r1_all.ipk
Size: 11072
Size: 11073
Package: luci-app-wireguard-dashboard
Version: 0.7.0-r5
@ -890,7 +901,7 @@ Architecture: all
Installed-Size: 215040
Description: Modern dashboard for WireGuard VPN monitoring on OpenWrt
Filename: luci-app-wireguard-dashboard_0.7.0-r5_all.ipk
Size: 42291
Size: 42286
Package: luci-app-zigbee2mqtt
Version: 1.0.0-r2
@ -902,7 +913,7 @@ Architecture: all
Installed-Size: 40960
Description: Graphical interface for managing the Zigbee2MQTT LXC application.
Filename: luci-app-zigbee2mqtt_1.0.0-r2_all.ipk
Size: 6591
Size: 6592
Package: luci-theme-secubox
Version: 0.4.7-r1
@ -914,7 +925,7 @@ Architecture: all
Installed-Size: 450560
Description: Global CyberMood design system (CSS/JS/i18n) shared by all SecuBox dashboards.
Filename: luci-theme-secubox_0.4.7-r1_all.ipk
Size: 110239
Size: 110238
Package: secubox-app
Version: 1.0.0-r2
@ -925,7 +936,7 @@ Installed-Size: 92160
Description: Command line helper for SecuBox App Store manifests. Installs /usr/sbin/secubox-app
and ships the default manifests under /usr/share/secubox/plugins/.
Filename: secubox-app_1.0.0-r2_all.ipk
Size: 11182
Size: 11183
Package: secubox-app-adguardhome
Version: 1.0.0-r2
@ -939,7 +950,7 @@ Description: Installer, configuration, and service manager for running AdGuard
inside Docker on SecuBox-powered OpenWrt systems. Network-wide ad blocker
with DNS-over-HTTPS/TLS support and detailed analytics.
Filename: secubox-app-adguardhome_1.0.0-r2_all.ipk
Size: 2883
Size: 2880
Package: secubox-app-auth-logger
Version: 1.2.2-r1
@ -957,7 +968,7 @@ Description: Logs authentication failures from LuCI/rpcd and Dropbear SSH
- JavaScript hook to intercept login failures
- CrowdSec parser and bruteforce scenario
Filename: secubox-app-auth-logger_1.2.2-r1_all.ipk
Size: 9377
Size: 9374
Package: secubox-app-crowdsec-custom
Version: 1.1.0-r1
@ -1009,7 +1020,7 @@ Description: SecuBox CrowdSec Firewall Bouncer for OpenWrt.
- Automatic restart on firewall reload
- procd service management
Filename: secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk
Size: 5049325
Size: 5049324
Package: secubox-app-cyberfeed
Version: 0.2.1-r1
@ -1023,7 +1034,7 @@ Description: Cyberpunk-themed RSS feed aggregator for OpenWrt/SecuBox.
Features emoji injection, neon styling, and RSS-Bridge support
for social media feeds (Facebook, Twitter, Mastodon).
Filename: secubox-app-cyberfeed_0.2.1-r1_all.ipk
Size: 12453
Size: 12450
Package: secubox-app-device-intel
Version: 1.0.0-r1
@ -1037,7 +1048,7 @@ Description: Unified device inventory aggregating mac-guardian, client-guardian
P2P mesh, and exposure scanner data. Includes heuristic classification
and pluggable emulator modules for MQTT, Zigbee, and USB devices.
Filename: secubox-app-device-intel_1.0.0-r1_all.ipk
Size: 13108
Size: 13104
Package: secubox-app-dns-provider
Version: 1.0.0-r1
@ -1051,7 +1062,7 @@ Description: Programmatic DNS record management via provider APIs (OVH, Gandi
Cloudflare). Provides the dnsctl CLI for record CRUD, zone sync
DNS propagation verification, and ACME DNS-01 challenge support.
Filename: secubox-app-dns-provider_1.0.0-r1_all.ipk
Size: 8261
Size: 8257
Package: secubox-app-domoticz
Version: 1.0.0-r4
@ -1064,7 +1075,7 @@ Installed-Size: 30720
Description: Installer, configuration, and service manager for running Domoticz
inside an LXC Alpine container on SecuBox-powered OpenWrt systems.
Filename: secubox-app-domoticz_1.0.0-r4_all.ipk
Size: 7503
Size: 7507
Package: secubox-app-exposure
Version: 1.0.0-r1
@ -1079,7 +1090,7 @@ Description: Unified service exposure manager for SecuBox.
- Dynamic Tor hidden service management
- HAProxy SSL reverse proxy configuration
Filename: secubox-app-exposure_1.0.0-r1_all.ipk
Size: 9147
Size: 9145
Package: secubox-app-gitea
Version: 1.0.0-r5
@ -1102,7 +1113,7 @@ Description: Gitea Git Platform - Self-hosted lightweight Git service
Runs in LXC container with Alpine Linux.
Configure in /etc/config/gitea.
Filename: secubox-app-gitea_1.0.0-r5_all.ipk
Size: 9439
Size: 9441
Package: secubox-app-gk2hub
Version: 0.1.0-r1
@ -1116,7 +1127,7 @@ Description: Dynamic landing page generator for GK2 SecuBox services.
Aggregates Streamlit apps, MetaBlogizer sites, and infrastructure
services into a single service directory page.
Filename: secubox-app-gk2hub_0.1.0-r1_all.ipk
Size: 4064
Size: 4059
Package: secubox-app-glances
Version: 1.0.0-r1
@ -1139,7 +1150,7 @@ Description: Glances - Cross-platform system monitoring tool for SecuBox.
Runs in LXC container for isolation and security.
Configure in /etc/config/glances.
Filename: secubox-app-glances_1.0.0-r1_all.ipk
Size: 6142
Size: 6137
Package: secubox-app-guacamole
Version: 1.0.0-r1
@ -1153,7 +1164,7 @@ Description: Apache Guacamole clientless remote desktop gateway.
Runs in an LXC Debian container with guacd and Tomcat.
Supports SSH, VNC, and RDP connections via web browser.
Filename: secubox-app-guacamole_1.0.0-r1_all.ipk
Size: 6948
Size: 6945
Package: secubox-app-haproxy
Version: 1.0.0-r24
@ -1173,7 +1184,7 @@ Description: HAProxy load balancer and reverse proxy running in an LXC containe
- Stats dashboard
- Rate limiting and ACLs
Filename: secubox-app-haproxy_1.0.0-r24_all.ipk
Size: 22012
Size: 22008
Package: secubox-app-hexojs
Version: 1.0.0-r8
@ -1182,7 +1193,7 @@ License: MIT
Section: utils
Maintainer: CyberMind Studio <contact@cybermind.fr>
Architecture: all
Installed-Size: 501760
Installed-Size: 522240
Description: Hexo CMS - Self-hosted static blog generator for OpenWrt
Features:
@ -1197,7 +1208,7 @@ Description: Hexo CMS - Self-hosted static blog generator for OpenWrt
Runs in LXC container with Alpine Linux.
Configure in /etc/config/hexojs.
Filename: secubox-app-hexojs_1.0.0-r8_all.ipk
Size: 94937
Size: 100060
Package: secubox-app-jabber
Version: 1.0.0-r1
@ -1211,7 +1222,7 @@ Description: Jabber/XMPP instant messaging server based on Prosody.
Runs in an LXC Debian container with full XMPP support.
Features multi-user chat (MUC), file uploads, and S2S federation.
Filename: secubox-app-jabber_1.0.0-r1_all.ipk
Size: 13277
Size: 13276
Package: secubox-app-jellyfin
Version: 3.0.0-r1
@ -1224,7 +1235,7 @@ Installed-Size: 20480
Description: Jellyfin media server running in LXC container.
Free media server for streaming movies, TV shows, music, and photos.
Filename: secubox-app-jellyfin_3.0.0-r1_all.ipk
Size: 4748
Size: 4755
Package: secubox-app-jitsi
Version: 1.0.0-r1
@ -1249,7 +1260,7 @@ Description: Jitsi Meet - Secure, fully featured video conferencing for SecuBox
Integrates with HAProxy for SSL termination.
Configure in /etc/config/jitsi.
Filename: secubox-app-jitsi_1.0.0-r1_all.ipk
Size: 8927
Size: 8924
Package: secubox-app-localai
Version: 3.9.0-r1
@ -1271,7 +1282,7 @@ Description: LocalAI native binary package for OpenWrt.
API: http://<router-ip>:8081/v1
Filename: secubox-app-localai_3.9.0-r1_all.ipk
Size: 5848
Size: 5841
Package: secubox-app-localai-wb
Version: 2.25.0-r1
@ -1315,7 +1326,7 @@ Description: Lyrion Media Server (formerly Logitech Media Server / Squeezebox S
Auto-detects available runtime, preferring LXC for lower resource usage.
Configure runtime in /etc/config/lyrion.
Filename: secubox-app-lyrion_2.0.2-r1_all.ipk
Size: 8130
Size: 8124
Package: secubox-app-mac-guardian
Version: 0.5.0-r1
@ -1330,7 +1341,7 @@ Description: WiFi MAC address security monitor for SecuBox.
and spoofing. Integrates with CrowdSec and provides
real-time hostapd hotplug detection.
Filename: secubox-app-mac-guardian_0.5.0-r1_all.ipk
Size: 12098
Size: 12097
Package: secubox-app-magicmirror2
Version: 0.4.0-r8
@ -1377,7 +1388,7 @@ Description: Complete email server solution using docker-mailserver for SecuBox
Commands: mailinaboxctl --help
Filename: secubox-app-mailinabox_2.0.0-r1_all.ipk
Size: 7569
Size: 7574
Package: secubox-app-mailserver
Version: 2.0.0-r1
@ -1390,7 +1401,7 @@ Installed-Size: 20480
Description: Postfix + Dovecot mail server running in LXC container.
Supports IMAP/SMTP with SSL/TLS.
Filename: secubox-app-mailserver_2.0.0-r1_all.ipk
Size: 5697
Size: 5693
Package: secubox-app-matrix
Version: 1.0.0-r1
@ -1425,7 +1436,7 @@ Description: Metabolizer Blog Pipeline - Integrated CMS with Git-based workflow
Pipeline: Edit in Streamlit -> Push to Gitea -> Build with Hexo -> Publish
Filename: secubox-app-metabolizer_1.0.0-r3_all.ipk
Size: 13980
Size: 13975
Package: secubox-app-mitmproxy
Version: 0.5.0-r19
@ -1452,7 +1463,7 @@ Description: mitmproxy - Interactive HTTPS proxy for SecuBox-powered OpenWrt sy
Runs in LXC container for isolation and security.
Configure in /etc/config/mitmproxy.
Filename: secubox-app-mitmproxy_0.5.0-r19_all.ipk
Size: 22958
Size: 22957
Package: secubox-app-mmpm
Version: 0.2.0-r5
@ -1473,7 +1484,7 @@ Description: MMPM (MagicMirror Package Manager) for SecuBox.
Runs inside the MagicMirror2 LXC container.
Filename: secubox-app-mmpm_0.2.0-r5_all.ipk
Size: 3978
Size: 3979
Package: secubox-app-nextcloud
Version: 1.0.0-r2
@ -1509,7 +1520,7 @@ Description: Ollama - Simple local LLM runtime for SecuBox-powered OpenWrt syst
Runs in Docker/Podman container.
Configure in /etc/config/ollama.
Filename: secubox-app-ollama_0.1.0-r1_all.ipk
Size: 5735
Size: 5736
Package: secubox-app-picobrew
Version: 1.0.0-r7
@ -1544,7 +1555,7 @@ Installed-Size: 20480
Description: Self-hosted RustDesk relay server for remote desktop access.
Downloads and manages hbbs (ID server) and hbbr (relay server) binaries.
Filename: secubox-app-rustdesk_1.0.0-r1_all.ipk
Size: 4469
Size: 4467
Package: secubox-app-simplex
Version: 1.0.0-r1
@ -1568,7 +1579,7 @@ Description: SimpleX Chat self-hosted messaging infrastructure for SecuBox.
Privacy-first messaging relay that you control.
Configure in /etc/config/simplex.
Filename: secubox-app-simplex_1.0.0-r1_all.ipk
Size: 9369
Size: 9367
Package: secubox-app-smbfs
Version: 1.0.0-r1
@ -1582,7 +1593,7 @@ Description: SMB/CIFS remote directory mount manager for SecuBox. Manages share
network mounts for media servers (Jellyfin, Lyrion), backups, and
general-purpose remote storage over SMB/CIFS protocol.
Filename: secubox-app-smbfs_1.0.0-r1_all.ipk
Size: 5269
Size: 5268
Package: secubox-app-streamlit
Version: 1.0.0-r5
@ -1609,7 +1620,7 @@ Description: Streamlit App Platform - Self-hosted Python data app platform
Configure in /etc/config/streamlit.
Filename: secubox-app-streamlit_1.0.0-r5_all.ipk
Size: 16517
Size: 16519
Package: secubox-app-tor
Version: 1.0.0-r1
@ -1632,7 +1643,7 @@ Description: SecuBox Tor Shield - One-click Tor anonymization for OpenWrt
Configure in /etc/config/tor-shield.
Filename: secubox-app-tor_1.0.0-r1_all.ipk
Size: 7372
Size: 7370
Package: secubox-app-voip
Version: 1.0.0-r1
@ -1646,7 +1657,7 @@ Description: VoIP PBX solution with Asterisk in LXC container.
Features OVH SIP trunk integration, WebRTC support
and Jabber/XMPP relay for SMS and voicemail notifications.
Filename: secubox-app-voip_1.0.0-r1_all.ipk
Size: 11955
Size: 11954
Package: secubox-app-webapp
Version: 1.5.0-r7
@ -1664,7 +1675,7 @@ Description: SecuBox Control Center Dashboard - A web-based dashboard for monit
- Service management
- Network interface control
Filename: secubox-app-webapp_1.5.0-r7_all.ipk
Size: 39176
Size: 39177
Package: secubox-app-zigbee2mqtt
Version: 1.0.0-r3
@ -1677,7 +1688,7 @@ Installed-Size: 20480
Description: Installer, configuration, and service manager for running Zigbee2MQTT
inside an Alpine LXC container on SecuBox-powered OpenWrt systems.
Filename: secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk
Size: 5533
Size: 5539
Package: secubox-config-advisor
Version: 0.1.0-r1
@ -1696,7 +1707,7 @@ Description: AI-powered configuration security advisor for SecuBox.
- LocalAI integration for intelligent analysis
- Automated remediation suggestions
Filename: secubox-config-advisor_0.1.0-r1_all.ipk
Size: 14850
Size: 14849
Package: secubox-content-pkg
Version: 1.0.0-r1
@ -1709,7 +1720,7 @@ Installed-Size: 20480
Description: Package Metablogizer sites and Streamlit apps as IPKs for P2P distribution.
Auto-publishes content to the mesh feed for peer auto-sync.
Filename: secubox-content-pkg_1.0.0-r1_all.ipk
Size: 3914
Size: 3904
Package: secubox-cookie-tracker
Version: 1.0.0-r1
@ -1732,7 +1743,7 @@ Description: Cookie Tracker for SecuBox InterceptoR.
Works with secubox-app-mitmproxy for transparent interception.
Filename: secubox-cookie-tracker_1.0.0-r1_all.ipk
Size: 10645
Size: 10641
Package: secubox-core
Version: 0.10.0-r16
@ -1752,7 +1763,7 @@ Description: SecuBox Core Framework provides the foundational infrastructure fo
- Unified CLI interface
- ubus RPC backend
Filename: secubox-core_0.10.0-r16_all.ipk
Size: 123047
Size: 123045
Package: secubox-cve-triage
Version: 1.0.0-r1
@ -1772,7 +1783,7 @@ Description: AI-powered CVE analysis and vulnerability management agent for Sec
- Approval workflow for patch recommendations
- LXC and Docker package monitoring
Filename: secubox-cve-triage_1.0.0-r1_all.ipk
Size: 11826
Size: 11827
Package: secubox-dns-guard
Version: 1.0.0-r1
@ -1791,7 +1802,7 @@ Description: SecuBox DNS Guard provides AI-powered DNS anomaly detection using
- Unusual TLD pattern detection
- Automatic blocklist generation with approval workflow
Filename: secubox-dns-guard_1.0.0-r1_all.ipk
Size: 12488
Size: 12485
Package: secubox-identity
Version: 0.1.0-r1
@ -1810,7 +1821,7 @@ Description: Decentralized identity management for SecuBox mesh nodes.
- Peer identity verification
- Trust scoring integration
Filename: secubox-identity_0.1.0-r1_all.ipk
Size: 8089
Size: 8088
Package: secubox-iot-guard
Version: 1.0.0-r1
@ -1826,7 +1837,7 @@ Description: IoT device isolation, classification, and security monitoring.
risk scoring. Orchestrates Client Guardian, MAC Guardian
Vortex Firewall, and Bandwidth Manager for IoT protection.
Filename: secubox-iot-guard_1.0.0-r1_all.ipk
Size: 13374
Size: 13365
Package: secubox-localrecall
Version: 1.0.0-r1
@ -1841,7 +1852,7 @@ Description: Persistent memory system for SecuBox AI agents.
for context across sessions. LocalAI integration for
semantic search and AI-powered summarization.
Filename: secubox-localrecall_1.0.0-r1_all.ipk
Size: 7802
Size: 7796
Package: secubox-master-link
Version: 1.0.0-r1
@ -1863,7 +1874,7 @@ Description: Secure mesh onboarding for SecuBox nodes via master/peer link.
Configure in /etc/config/master-link.
Filename: secubox-master-link_1.0.0-r1_all.ipk
Size: 15038
Size: 15034
Package: secubox-mcp-server
Version: 1.0.0-r1
@ -1891,7 +1902,7 @@ Description: Model Context Protocol (MCP) server for SecuBox.
- ai.explain_ban (Explain CrowdSec decisions)
- ai.security_posture (Security assessment)
Filename: secubox-mcp-server_1.0.0-r1_all.ipk
Size: 11433
Size: 11427
Package: secubox-mirrornet
Version: 0.1.0-r1
@ -1909,7 +1920,7 @@ Description: MirrorNet core mesh orchestration for SecuBox.
- Mesh health monitoring and anomaly detection
- DID-based identity (did:plc compatible)
Filename: secubox-mirrornet_0.1.0-r1_all.ipk
Size: 15306
Size: 15303
Package: secubox-network-anomaly
Version: 1.0.0-r1
@ -1924,7 +1935,7 @@ Description: AI-powered network anomaly detection for SecuBox.
DNS anomalies, and protocol anomalies using statistical
analysis and optional LocalAI integration.
Filename: secubox-network-anomaly_1.0.0-r1_all.ipk
Size: 6170
Size: 6163
Package: secubox-p2p
Version: 0.6.0-r3
@ -1943,7 +1954,7 @@ Description: SecuBox P2P Hub backend providing peer discovery, mesh networking
and MirrorBox NetMesh Catalog for cross-chain distributed service
registry with HAProxy vhost discovery and multi-endpoint access URLs.
Filename: secubox-p2p_0.6.0-r3_all.ipk
Size: 47872
Size: 47860
Package: secubox-p2p-intel
Version: 0.1.0-r1
@ -1962,7 +1973,7 @@ Description: Decentralized threat intelligence sharing for SecuBox mesh.
- CrowdSec and mitmproxy integration
- Automatic firewall rule application
Filename: secubox-p2p-intel_0.1.0-r1_all.ipk
Size: 9798
Size: 9799
Package: secubox-threat-analyst
Version: 1.0.0-r1
@ -1981,7 +1992,7 @@ Description: Autonomous threat analysis agent for SecuBox.
Part of SecuBox AI Gateway (Couche 2).
Filename: secubox-threat-analyst_1.0.0-r1_all.ipk
Size: 9867
Size: 9864
Package: secubox-vortex-dns
Version: 1.0.0-r1
@ -2000,7 +2011,7 @@ Description: Meshed multi-dynamic subdomain delegation system for SecuBox.
- Gossip-based exposure config sync
- Submastering for nested hierarchies
Filename: secubox-vortex-dns_1.0.0-r1_all.ipk
Size: 5443
Size: 5439
Package: secubox-vortex-firewall
Version: 1.0.0-r1
@ -2015,5 +2026,5 @@ Description: DNS-level threat blocking with x47 impact multiplier.
any connection is established. Integrates threat feeds from
abuse.ch, OpenPhish, and local DNS Guard detections.
Filename: secubox-vortex-firewall_1.0.0-r1_all.ipk
Size: 8898
Size: 8892

View File

@ -1,12 +1,12 @@
{
"feed_url": "/secubox-feed",
"generated": "2026-02-19T18:57:57+01:00",
"generated": "2026-02-20T08:56:23+01:00",
"packages": [
{
"name": "luci-app-ai-insights",
"version": "1.0.0-r1",
"filename": "luci-app-ai-insights_1.0.0-r1_all.ipk",
"size": 11638,
"size": 11639,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -18,7 +18,7 @@
"name": "luci-app-auth-guardian",
"version": "0.4.0-r3",
"filename": "luci-app-auth-guardian_0.4.0-r3_all.ipk",
"size": 12400,
"size": 12391,
"category": "security",
"icon": "key",
"description": "Authentication management",
@ -30,7 +30,7 @@
"name": "luci-app-backup",
"version": "1.0.0-r1",
"filename": "luci-app-backup_1.0.0-r1_all.ipk",
"size": 4534,
"size": 4537,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -54,7 +54,7 @@
"name": "luci-app-cdn-cache",
"version": "0.5.0-r3",
"filename": "luci-app-cdn-cache_0.5.0-r3_all.ipk",
"size": 24255,
"size": 24257,
"category": "network",
"icon": "globe",
"description": "CDN caching",
@ -66,7 +66,7 @@
"name": "luci-app-client-guardian",
"version": "0.4.0-r7",
"filename": "luci-app-client-guardian_0.4.0-r7_all.ipk",
"size": 52684,
"size": 52687,
"category": "network",
"icon": "users",
"description": "Client management and monitoring",
@ -78,7 +78,7 @@
"name": "luci-app-cloner",
"version": "1.0.0-r1",
"filename": "luci-app-cloner_1.0.0-r1_all.ipk",
"size": 19433,
"size": 19435,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -102,7 +102,7 @@
"name": "luci-app-cookie-tracker",
"version": "1.0.0-r1",
"filename": "luci-app-cookie-tracker_1.0.0-r1_all.ipk",
"size": 5653,
"size": 5660,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -114,7 +114,7 @@
"name": "luci-app-crowdsec-dashboard",
"version": "0.7.0-r32",
"filename": "luci-app-crowdsec-dashboard_0.7.0-r32_all.ipk",
"size": 34938,
"size": 34940,
"category": "security",
"icon": "shield",
"description": "CrowdSec security monitoring",
@ -126,7 +126,7 @@
"name": "luci-app-cve-triage",
"version": "1.0.0-r1",
"filename": "luci-app-cve-triage_1.0.0-r1_all.ipk",
"size": 5948,
"size": 5943,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -150,7 +150,7 @@
"name": "luci-app-device-intel",
"version": "1.0.0-r1",
"filename": "luci-app-device-intel_1.0.0-r1_all.ipk",
"size": 12047,
"size": 12049,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -162,7 +162,7 @@
"name": "luci-app-dnsguard",
"version": "1.1.0-r1",
"filename": "luci-app-dnsguard_1.1.0-r1_all.ipk",
"size": 12448,
"size": 12449,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -174,7 +174,7 @@
"name": "luci-app-dns-provider",
"version": "1.0.0-r1",
"filename": "luci-app-dns-provider_1.0.0-r1_all.ipk",
"size": 7168,
"size": 7174,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -186,7 +186,7 @@
"name": "luci-app-domoticz",
"version": "1.0.0-r1",
"filename": "luci-app-domoticz_1.0.0-r1_all.ipk",
"size": 7123,
"size": 7118,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -198,7 +198,7 @@
"name": "luci-app-exposure",
"version": "1.0.0-r3",
"filename": "luci-app-exposure_1.0.0-r3_all.ipk",
"size": 11696,
"size": 11698,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -210,7 +210,7 @@
"name": "luci-app-gitea",
"version": "1.0.0-r2",
"filename": "luci-app-gitea_1.0.0-r2_all.ipk",
"size": 16623,
"size": 16621,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -222,7 +222,7 @@
"name": "luci-app-glances",
"version": "1.0.0-r2",
"filename": "luci-app-glances_1.0.0-r2_all.ipk",
"size": 7022,
"size": 7017,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -234,7 +234,7 @@
"name": "luci-app-gotosocial",
"version": "0.1.0-r1",
"filename": "luci-app-gotosocial_0.1.0-r1_all.ipk",
"size": 8207,
"size": 8210,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -258,7 +258,7 @@
"name": "luci-app-hexojs",
"version": "1.0.0-r3",
"filename": "luci-app-hexojs_1.0.0-r3_all.ipk",
"size": 30452,
"size": 30450,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -270,7 +270,7 @@
"name": "luci-app-iot-guard",
"version": "1.0.0-r1",
"filename": "luci-app-iot-guard_1.0.0-r1_all.ipk",
"size": 10535,
"size": 10538,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -282,7 +282,7 @@
"name": "luci-app-jabber",
"version": "0",
"filename": "luci-app-jabber_0_all.ipk",
"size": 9304,
"size": 9308,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -294,7 +294,7 @@
"name": "luci-app-jellyfin",
"version": "1.0.0-r1",
"filename": "luci-app-jellyfin_1.0.0-r1_all.ipk",
"size": 10482,
"size": 10485,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -306,7 +306,7 @@
"name": "luci-app-jitsi",
"version": "1.0.0-r1",
"filename": "luci-app-jitsi_1.0.0-r1_all.ipk",
"size": 5175,
"size": 5173,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -318,7 +318,7 @@
"name": "luci-app-ksm-manager",
"version": "0.4.0-r2",
"filename": "luci-app-ksm-manager_0.4.0-r2_all.ipk",
"size": 18779,
"size": 18777,
"category": "system",
"icon": "cpu",
"description": "Kernel memory management",
@ -342,7 +342,7 @@
"name": "luci-app-localrecall",
"version": "1.0.0-r1",
"filename": "luci-app-localrecall_1.0.0-r1_all.ipk",
"size": 8418,
"size": 8420,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -354,7 +354,7 @@
"name": "luci-app-lyrion",
"version": "1.0.0-r1",
"filename": "luci-app-lyrion_1.0.0-r1_all.ipk",
"size": 6839,
"size": 6840,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -366,7 +366,7 @@
"name": "luci-app-mac-guardian",
"version": "0.5.0-r1",
"filename": "luci-app-mac-guardian_0.5.0-r1_all.ipk",
"size": 6664,
"size": 6660,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -378,7 +378,7 @@
"name": "luci-app-magicmirror2",
"version": "0.4.0-r6",
"filename": "luci-app-magicmirror2_0.4.0-r6_all.ipk",
"size": 12361,
"size": 12358,
"category": "iot",
"icon": "monitor",
"description": "Smart mirror display",
@ -390,7 +390,7 @@
"name": "luci-app-mailinabox",
"version": "1.0.0-r1",
"filename": "luci-app-mailinabox_1.0.0-r1_all.ipk",
"size": 5484,
"size": 5487,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -402,7 +402,7 @@
"name": "luci-app-mailserver",
"version": "1.0.0-r1",
"filename": "luci-app-mailserver_1.0.0-r1_all.ipk",
"size": 6511,
"size": 6509,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -414,7 +414,7 @@
"name": "luci-app-master-link",
"version": "1.0.0-r1",
"filename": "luci-app-master-link_1.0.0-r1_all.ipk",
"size": 6304,
"size": 6309,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -438,7 +438,7 @@
"name": "luci-app-media-flow",
"version": "0.6.4-r1",
"filename": "luci-app-media-flow_0.6.4-r1_all.ipk",
"size": 25381,
"size": 25379,
"category": "media",
"icon": "film",
"description": "Media streaming",
@ -450,7 +450,7 @@
"name": "luci-app-metablogizer",
"version": "1.1.0-r1",
"filename": "luci-app-metablogizer_1.1.0-r1_all.ipk",
"size": 26207,
"size": 26203,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -462,7 +462,7 @@
"name": "luci-app-metabolizer",
"version": "1.0.0-r2",
"filename": "luci-app-metabolizer_1.0.0-r2_all.ipk",
"size": 4821,
"size": 4819,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -474,7 +474,7 @@
"name": "luci-app-mitmproxy",
"version": "0.5.0-r2",
"filename": "luci-app-mitmproxy_0.5.0-r2_all.ipk",
"size": 13232,
"size": 13234,
"category": "security",
"icon": "lock",
"description": "HTTPS proxy and traffic inspection",
@ -486,7 +486,7 @@
"name": "luci-app-mmpm",
"version": "0.2.0-r3",
"filename": "luci-app-mmpm_0.2.0-r3_all.ipk",
"size": 7975,
"size": 7971,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -498,7 +498,7 @@
"name": "luci-app-mqtt-bridge",
"version": "0.4.0-r4",
"filename": "luci-app-mqtt-bridge_0.4.0-r4_all.ipk",
"size": 22689,
"size": 22687,
"category": "iot",
"icon": "message-square",
"description": "MQTT bridge",
@ -510,7 +510,7 @@
"name": "luci-app-ndpid",
"version": "1.1.2-r2",
"filename": "luci-app-ndpid_1.1.2-r2_all.ipk",
"size": 21704,
"size": 21701,
"category": "security",
"icon": "eye",
"description": "Deep packet inspection",
@ -522,7 +522,7 @@
"name": "luci-app-netdata-dashboard",
"version": "0.5.0-r2",
"filename": "luci-app-netdata-dashboard_0.5.0-r2_all.ipk",
"size": 20558,
"size": 20559,
"category": "monitoring",
"icon": "bar-chart-2",
"description": "System monitoring dashboard",
@ -534,7 +534,7 @@
"name": "luci-app-network-anomaly",
"version": "1.0.0-r1",
"filename": "luci-app-network-anomaly_1.0.0-r1_all.ipk",
"size": 7641,
"size": 7643,
"category": "network",
"icon": "wifi",
"description": "Network configuration",
@ -558,7 +558,7 @@
"name": "luci-app-network-tweaks",
"version": "1.0.0-r7",
"filename": "luci-app-network-tweaks_1.0.0-r7_all.ipk",
"size": 15949,
"size": 15947,
"category": "network",
"icon": "wifi",
"description": "Network configuration",
@ -570,7 +570,7 @@
"name": "luci-app-nextcloud",
"version": "1.0.0-r1",
"filename": "luci-app-nextcloud_1.0.0-r1_all.ipk",
"size": 10347,
"size": 10348,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -582,7 +582,7 @@
"name": "luci-app-ollama",
"version": "0.1.0-r1",
"filename": "luci-app-ollama_0.1.0-r1_all.ipk",
"size": 14338,
"size": 14337,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -594,7 +594,7 @@
"name": "luci-app-peertube",
"version": "0",
"filename": "luci-app-peertube_0_all.ipk",
"size": 5760,
"size": 5757,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -606,7 +606,7 @@
"name": "luci-app-picobrew",
"version": "1.0.0-r1",
"filename": "luci-app-picobrew_1.0.0-r1_all.ipk",
"size": 9534,
"size": 9529,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -618,7 +618,7 @@
"name": "luci-app-secubox",
"version": "0.7.1-r4",
"filename": "luci-app-secubox_0.7.1-r4_all.ipk",
"size": 82095,
"size": 82096,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -630,7 +630,7 @@
"name": "luci-app-secubox-admin",
"version": "1.0.0-r19",
"filename": "luci-app-secubox-admin_1.0.0-r19_all.ipk",
"size": 58041,
"size": 58040,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -642,7 +642,7 @@
"name": "luci-app-secubox-crowdsec",
"version": "1.0.0-r3",
"filename": "luci-app-secubox-crowdsec_1.0.0-r3_all.ipk",
"size": 13918,
"size": 13916,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -666,7 +666,7 @@
"name": "luci-app-secubox-netdiag",
"version": "1.0.0-r1",
"filename": "luci-app-secubox-netdiag_1.0.0-r1_all.ipk",
"size": 15347,
"size": 15344,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -690,7 +690,7 @@
"name": "luci-app-secubox-p2p",
"version": "0.1.0-r1",
"filename": "luci-app-secubox-p2p_0.1.0-r1_all.ipk",
"size": 46832,
"size": 46831,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -702,7 +702,7 @@
"name": "luci-app-secubox-portal",
"version": "0.7.0-r3",
"filename": "luci-app-secubox-portal_0.7.0-r3_all.ipk",
"size": 41686,
"size": 41681,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -714,7 +714,19 @@
"name": "luci-app-secubox-security-threats",
"version": "1.0.0-r4",
"filename": "luci-app-secubox-security-threats_1.0.0-r4_all.ipk",
"size": 10659,
"size": 10657,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
"installed": false,
"luci_app": null
}
,
{
"name": "luci-app-secubox-users",
"version": "1.0.0-r1",
"filename": "luci-app-secubox-users_1.0.0-r1_all.ipk",
"size": 5141,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -726,7 +738,7 @@
"name": "luci-app-service-registry",
"version": "1.0.0-r1",
"filename": "luci-app-service-registry_1.0.0-r1_all.ipk",
"size": 39952,
"size": 39951,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -738,7 +750,7 @@
"name": "luci-app-simplex",
"version": "1.0.0-r1",
"filename": "luci-app-simplex_1.0.0-r1_all.ipk",
"size": 7036,
"size": 7042,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -750,7 +762,7 @@
"name": "luci-app-streamlit",
"version": "1.0.0-r11",
"filename": "luci-app-streamlit_1.0.0-r11_all.ipk",
"size": 20568,
"size": 20567,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -762,7 +774,7 @@
"name": "luci-app-system-hub",
"version": "0.5.1-r4",
"filename": "luci-app-system-hub_0.5.1-r4_all.ipk",
"size": 62082,
"size": 62081,
"category": "system",
"icon": "settings",
"description": "System management",
@ -774,7 +786,7 @@
"name": "luci-app-threat-analyst",
"version": "1.0.0-r1",
"filename": "luci-app-threat-analyst_1.0.0-r1_all.ipk",
"size": 10144,
"size": 10145,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -786,7 +798,7 @@
"name": "luci-app-tor",
"version": "1.0.0-r1",
"filename": "luci-app-tor_1.0.0-r1_all.ipk",
"size": 17822,
"size": 17816,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -798,7 +810,7 @@
"name": "luci-app-tor-shield",
"version": "1.0.0-r10",
"filename": "luci-app-tor-shield_1.0.0-r10_all.ipk",
"size": 22766,
"size": 22764,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -810,7 +822,7 @@
"name": "luci-app-traffic-shaper",
"version": "0.4.0-r2",
"filename": "luci-app-traffic-shaper_0.4.0-r2_all.ipk",
"size": 14591,
"size": 14587,
"category": "network",
"icon": "filter",
"description": "Traffic shaping and QoS",
@ -822,7 +834,7 @@
"name": "luci-app-vhost-manager",
"version": "0.5.0-r5",
"filename": "luci-app-vhost-manager_0.5.0-r5_all.ipk",
"size": 26284,
"size": 26283,
"category": "network",
"icon": "server",
"description": "Virtual host management",
@ -834,7 +846,7 @@
"name": "luci-app-voip",
"version": "1.0.0-r1",
"filename": "luci-app-voip_1.0.0-r1_all.ipk",
"size": 11046,
"size": 11043,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -846,7 +858,7 @@
"name": "luci-app-vortex-dns",
"version": "1.0.0-r1",
"filename": "luci-app-vortex-dns_1.0.0-r1_all.ipk",
"size": 6078,
"size": 6079,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -858,7 +870,7 @@
"name": "luci-app-vortex-firewall",
"version": "1.0.0-r1",
"filename": "luci-app-vortex-firewall_1.0.0-r1_all.ipk",
"size": 5451,
"size": 5450,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -870,7 +882,7 @@
"name": "luci-app-wazuh",
"version": "1.0.0-r1",
"filename": "luci-app-wazuh_1.0.0-r1_all.ipk",
"size": 11072,
"size": 11073,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -882,7 +894,7 @@
"name": "luci-app-wireguard-dashboard",
"version": "0.7.0-r5",
"filename": "luci-app-wireguard-dashboard_0.7.0-r5_all.ipk",
"size": 42291,
"size": 42286,
"category": "vpn",
"icon": "shield",
"description": "WireGuard VPN dashboard",
@ -894,7 +906,7 @@
"name": "luci-app-zigbee2mqtt",
"version": "1.0.0-r2",
"filename": "luci-app-zigbee2mqtt_1.0.0-r2_all.ipk",
"size": 6591,
"size": 6592,
"category": "iot",
"icon": "radio",
"description": "Zigbee device management",
@ -906,7 +918,7 @@
"name": "luci-theme-secubox",
"version": "0.4.7-r1",
"filename": "luci-theme-secubox_0.4.7-r1_all.ipk",
"size": 110239,
"size": 110238,
"category": "theme",
"icon": "palette",
"description": "LuCI theme",
@ -918,7 +930,7 @@
"name": "secubox-app",
"version": "1.0.0-r2",
"filename": "secubox-app_1.0.0-r2_all.ipk",
"size": 11182,
"size": 11183,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -930,7 +942,7 @@
"name": "secubox-app-adguardhome",
"version": "1.0.0-r2",
"filename": "secubox-app-adguardhome_1.0.0-r2_all.ipk",
"size": 2883,
"size": 2880,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -942,7 +954,7 @@
"name": "secubox-app-auth-logger",
"version": "1.2.2-r1",
"filename": "secubox-app-auth-logger_1.2.2-r1_all.ipk",
"size": 9377,
"size": 9374,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -966,7 +978,7 @@
"name": "secubox-app-cs-firewall-bouncer",
"version": "0.0.31-r4_aarch64",
"filename": "secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk",
"size": 5049325,
"size": 5049324,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -978,7 +990,7 @@
"name": "secubox-app-cyberfeed",
"version": "0.2.1-r1",
"filename": "secubox-app-cyberfeed_0.2.1-r1_all.ipk",
"size": 12453,
"size": 12450,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -990,7 +1002,7 @@
"name": "secubox-app-device-intel",
"version": "1.0.0-r1",
"filename": "secubox-app-device-intel_1.0.0-r1_all.ipk",
"size": 13108,
"size": 13104,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1002,7 +1014,7 @@
"name": "secubox-app-dns-provider",
"version": "1.0.0-r1",
"filename": "secubox-app-dns-provider_1.0.0-r1_all.ipk",
"size": 8261,
"size": 8257,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1014,7 +1026,7 @@
"name": "secubox-app-domoticz",
"version": "1.0.0-r4",
"filename": "secubox-app-domoticz_1.0.0-r4_all.ipk",
"size": 7503,
"size": 7507,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1026,7 +1038,7 @@
"name": "secubox-app-exposure",
"version": "1.0.0-r1",
"filename": "secubox-app-exposure_1.0.0-r1_all.ipk",
"size": 9147,
"size": 9145,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1038,7 +1050,7 @@
"name": "secubox-app-gitea",
"version": "1.0.0-r5",
"filename": "secubox-app-gitea_1.0.0-r5_all.ipk",
"size": 9439,
"size": 9441,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1050,7 +1062,7 @@
"name": "secubox-app-gk2hub",
"version": "0.1.0-r1",
"filename": "secubox-app-gk2hub_0.1.0-r1_all.ipk",
"size": 4064,
"size": 4059,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1062,7 +1074,7 @@
"name": "secubox-app-glances",
"version": "1.0.0-r1",
"filename": "secubox-app-glances_1.0.0-r1_all.ipk",
"size": 6142,
"size": 6137,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1074,7 +1086,7 @@
"name": "secubox-app-guacamole",
"version": "1.0.0-r1",
"filename": "secubox-app-guacamole_1.0.0-r1_all.ipk",
"size": 6948,
"size": 6945,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1086,7 +1098,7 @@
"name": "secubox-app-haproxy",
"version": "1.0.0-r24",
"filename": "secubox-app-haproxy_1.0.0-r24_all.ipk",
"size": 22012,
"size": 22008,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1098,7 +1110,7 @@
"name": "secubox-app-hexojs",
"version": "1.0.0-r8",
"filename": "secubox-app-hexojs_1.0.0-r8_all.ipk",
"size": 94937,
"size": 100060,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1110,7 +1122,7 @@
"name": "secubox-app-jabber",
"version": "1.0.0-r1",
"filename": "secubox-app-jabber_1.0.0-r1_all.ipk",
"size": 13277,
"size": 13276,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1122,7 +1134,7 @@
"name": "secubox-app-jellyfin",
"version": "3.0.0-r1",
"filename": "secubox-app-jellyfin_3.0.0-r1_all.ipk",
"size": 4748,
"size": 4755,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1134,7 +1146,7 @@
"name": "secubox-app-jitsi",
"version": "1.0.0-r1",
"filename": "secubox-app-jitsi_1.0.0-r1_all.ipk",
"size": 8927,
"size": 8924,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1146,7 +1158,7 @@
"name": "secubox-app-localai",
"version": "3.9.0-r1",
"filename": "secubox-app-localai_3.9.0-r1_all.ipk",
"size": 5848,
"size": 5841,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1170,7 +1182,7 @@
"name": "secubox-app-lyrion",
"version": "2.0.2-r1",
"filename": "secubox-app-lyrion_2.0.2-r1_all.ipk",
"size": 8130,
"size": 8124,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1182,7 +1194,7 @@
"name": "secubox-app-mac-guardian",
"version": "0.5.0-r1",
"filename": "secubox-app-mac-guardian_0.5.0-r1_all.ipk",
"size": 12098,
"size": 12097,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1206,7 +1218,7 @@
"name": "secubox-app-mailinabox",
"version": "2.0.0-r1",
"filename": "secubox-app-mailinabox_2.0.0-r1_all.ipk",
"size": 7569,
"size": 7574,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1218,7 +1230,7 @@
"name": "secubox-app-mailserver",
"version": "2.0.0-r1",
"filename": "secubox-app-mailserver_2.0.0-r1_all.ipk",
"size": 5697,
"size": 5693,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1242,7 +1254,7 @@
"name": "secubox-app-metabolizer",
"version": "1.0.0-r3",
"filename": "secubox-app-metabolizer_1.0.0-r3_all.ipk",
"size": 13980,
"size": 13975,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1254,7 +1266,7 @@
"name": "secubox-app-mitmproxy",
"version": "0.5.0-r19",
"filename": "secubox-app-mitmproxy_0.5.0-r19_all.ipk",
"size": 22958,
"size": 22957,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1266,7 +1278,7 @@
"name": "secubox-app-mmpm",
"version": "0.2.0-r5",
"filename": "secubox-app-mmpm_0.2.0-r5_all.ipk",
"size": 3978,
"size": 3979,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1290,7 +1302,7 @@
"name": "secubox-app-ollama",
"version": "0.1.0-r1",
"filename": "secubox-app-ollama_0.1.0-r1_all.ipk",
"size": 5735,
"size": 5736,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1314,7 +1326,7 @@
"name": "secubox-app-rustdesk",
"version": "1.0.0-r1",
"filename": "secubox-app-rustdesk_1.0.0-r1_all.ipk",
"size": 4469,
"size": 4467,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1326,7 +1338,7 @@
"name": "secubox-app-simplex",
"version": "1.0.0-r1",
"filename": "secubox-app-simplex_1.0.0-r1_all.ipk",
"size": 9369,
"size": 9367,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1338,7 +1350,7 @@
"name": "secubox-app-smbfs",
"version": "1.0.0-r1",
"filename": "secubox-app-smbfs_1.0.0-r1_all.ipk",
"size": 5269,
"size": 5268,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1350,7 +1362,7 @@
"name": "secubox-app-streamlit",
"version": "1.0.0-r5",
"filename": "secubox-app-streamlit_1.0.0-r5_all.ipk",
"size": 16517,
"size": 16519,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1362,7 +1374,7 @@
"name": "secubox-app-tor",
"version": "1.0.0-r1",
"filename": "secubox-app-tor_1.0.0-r1_all.ipk",
"size": 7372,
"size": 7370,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1374,7 +1386,7 @@
"name": "secubox-app-voip",
"version": "1.0.0-r1",
"filename": "secubox-app-voip_1.0.0-r1_all.ipk",
"size": 11955,
"size": 11954,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1386,7 +1398,7 @@
"name": "secubox-app-webapp",
"version": "1.5.0-r7",
"filename": "secubox-app-webapp_1.5.0-r7_all.ipk",
"size": 39176,
"size": 39177,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1398,7 +1410,7 @@
"name": "secubox-app-zigbee2mqtt",
"version": "1.0.0-r3",
"filename": "secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk",
"size": 5533,
"size": 5539,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -1410,7 +1422,7 @@
"name": "secubox-config-advisor",
"version": "0.1.0-r1",
"filename": "secubox-config-advisor_0.1.0-r1_all.ipk",
"size": 14850,
"size": 14849,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1422,7 +1434,7 @@
"name": "secubox-content-pkg",
"version": "1.0.0-r1",
"filename": "secubox-content-pkg_1.0.0-r1_all.ipk",
"size": 3914,
"size": 3904,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1434,7 +1446,7 @@
"name": "secubox-cookie-tracker",
"version": "1.0.0-r1",
"filename": "secubox-cookie-tracker_1.0.0-r1_all.ipk",
"size": 10645,
"size": 10641,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1446,7 +1458,7 @@
"name": "secubox-core",
"version": "0.10.0-r16",
"filename": "secubox-core_0.10.0-r16_all.ipk",
"size": 123047,
"size": 123045,
"category": "system",
"icon": "box",
"description": "SecuBox core components",
@ -1458,7 +1470,7 @@
"name": "secubox-cve-triage",
"version": "1.0.0-r1",
"filename": "secubox-cve-triage_1.0.0-r1_all.ipk",
"size": 11826,
"size": 11827,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1470,7 +1482,7 @@
"name": "secubox-dns-guard",
"version": "1.0.0-r1",
"filename": "secubox-dns-guard_1.0.0-r1_all.ipk",
"size": 12488,
"size": 12485,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1482,7 +1494,7 @@
"name": "secubox-identity",
"version": "0.1.0-r1",
"filename": "secubox-identity_0.1.0-r1_all.ipk",
"size": 8089,
"size": 8088,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1494,7 +1506,7 @@
"name": "secubox-iot-guard",
"version": "1.0.0-r1",
"filename": "secubox-iot-guard_1.0.0-r1_all.ipk",
"size": 13374,
"size": 13365,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1506,7 +1518,7 @@
"name": "secubox-localrecall",
"version": "1.0.0-r1",
"filename": "secubox-localrecall_1.0.0-r1_all.ipk",
"size": 7802,
"size": 7796,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1518,7 +1530,7 @@
"name": "secubox-master-link",
"version": "1.0.0-r1",
"filename": "secubox-master-link_1.0.0-r1_all.ipk",
"size": 15038,
"size": 15034,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1530,7 +1542,7 @@
"name": "secubox-mcp-server",
"version": "1.0.0-r1",
"filename": "secubox-mcp-server_1.0.0-r1_all.ipk",
"size": 11433,
"size": 11427,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1542,7 +1554,7 @@
"name": "secubox-mirrornet",
"version": "0.1.0-r1",
"filename": "secubox-mirrornet_0.1.0-r1_all.ipk",
"size": 15306,
"size": 15303,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1554,7 +1566,7 @@
"name": "secubox-network-anomaly",
"version": "1.0.0-r1",
"filename": "secubox-network-anomaly_1.0.0-r1_all.ipk",
"size": 6170,
"size": 6163,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1566,7 +1578,7 @@
"name": "secubox-p2p",
"version": "0.6.0-r3",
"filename": "secubox-p2p_0.6.0-r3_all.ipk",
"size": 47872,
"size": 47860,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1578,7 +1590,7 @@
"name": "secubox-p2p-intel",
"version": "0.1.0-r1",
"filename": "secubox-p2p-intel_0.1.0-r1_all.ipk",
"size": 9798,
"size": 9799,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1590,7 +1602,7 @@
"name": "secubox-threat-analyst",
"version": "1.0.0-r1",
"filename": "secubox-threat-analyst_1.0.0-r1_all.ipk",
"size": 9867,
"size": 9864,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1602,7 +1614,7 @@
"name": "secubox-vortex-dns",
"version": "1.0.0-r1",
"filename": "secubox-vortex-dns_1.0.0-r1_all.ipk",
"size": 5443,
"size": 5439,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -1614,7 +1626,7 @@
"name": "secubox-vortex-firewall",
"version": "1.0.0-r1",
"filename": "secubox-vortex-firewall_1.0.0-r1_all.ipk",
"size": 8898,
"size": 8892,
"category": "utility",
"icon": "package",
"description": "SecuBox package",

Some files were not shown because too many files have changed in this diff Show More