From db847ba1cdcf213e649e6ed2f77b1779994b2add Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 2 Feb 2026 12:53:41 +0100 Subject: [PATCH] feat(simplex): Add SimpleX Chat self-hosted messaging servers Integrate SimpleX Chat SMP and XFTP servers for privacy-focused messaging: - secubox-app-simplex: Backend with LXC container management - SMP server for message relay (port 5223) - XFTP server for encrypted file sharing (port 443) - Auto-download of SimpleX binaries for aarch64/x86_64 - TLS certificate generation (self-signed or Let's Encrypt) - Firewall and HAProxy integration - luci-app-simplex: LuCI dashboard with: - Service status monitoring - Server address display with copy-to-clipboard - Full configuration forms for SMP, XFTP, and TLS - Install/certificate management actions Co-Authored-By: Claude Opus 4.5 --- package/secubox/luci-app-simplex/Makefile | 29 + .../resources/view/simplex/overview.js | 458 +++++++ .../root/usr/libexec/rpcd/luci.simplex | 187 +++ .../share/luci/menu.d/luci-app-simplex.json | 14 + .../share/rpcd/acl.d/luci-app-simplex.json | 26 + .../root/www/secubox-feed/Packages | 1220 +++++++++++++++++ .../root/www/secubox-feed/Packages.gz | Bin 8402 -> 8623 bytes .../root/www/secubox-feed/apps-local.json | 172 ++- .../luci-app-simplex_1.0.0-r1_all.ipk | Bin 0 -> 6999 bytes .../secubox-app-simplex_1.0.0-r1_all.ipk | Bin 0 -> 9235 bytes package/secubox/secubox-app-simplex/Makefile | 83 ++ .../files/etc/config/simplex | 32 + .../files/etc/init.d/simplex | 77 ++ .../usr/lib/secubox/haproxy.d/simplex.cfg | 32 + .../files/usr/sbin/simplexctl | 985 +++++++++++++ 15 files changed, 3241 insertions(+), 74 deletions(-) create mode 100644 package/secubox/luci-app-simplex/Makefile create mode 100644 package/secubox/luci-app-simplex/htdocs/luci-static/resources/view/simplex/overview.js create mode 100644 package/secubox/luci-app-simplex/root/usr/libexec/rpcd/luci.simplex create mode 100644 package/secubox/luci-app-simplex/root/usr/share/luci/menu.d/luci-app-simplex.json create mode 100644 package/secubox/luci-app-simplex/root/usr/share/rpcd/acl.d/luci-app-simplex.json create mode 100644 package/secubox/secubox-app-bonus/root/www/secubox-feed/luci-app-simplex_1.0.0-r1_all.ipk create mode 100644 package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-app-simplex_1.0.0-r1_all.ipk create mode 100644 package/secubox/secubox-app-simplex/Makefile create mode 100644 package/secubox/secubox-app-simplex/files/etc/config/simplex create mode 100644 package/secubox/secubox-app-simplex/files/etc/init.d/simplex create mode 100644 package/secubox/secubox-app-simplex/files/usr/lib/secubox/haproxy.d/simplex.cfg create mode 100644 package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl diff --git a/package/secubox/luci-app-simplex/Makefile b/package/secubox/luci-app-simplex/Makefile new file mode 100644 index 00000000..403769fe --- /dev/null +++ b/package/secubox/luci-app-simplex/Makefile @@ -0,0 +1,29 @@ +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI SimpleX Chat Server Configuration +LUCI_DEPENDS:=+secubox-app-simplex +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-simplex +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=CyberMind Studio +PKG_LICENSE:=Apache-2.0 + +include $(TOPDIR)/feeds/luci/luci.mk + +define Package/luci-app-simplex/install + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-simplex.json $(1)/usr/share/luci/menu.d/ + + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-simplex.json $(1)/usr/share/rpcd/acl.d/ + + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/simplex + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/simplex/*.js $(1)/www/luci-static/resources/view/simplex/ + + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.simplex $(1)/usr/libexec/rpcd/ +endef + +$(eval $(call BuildPackage,luci-app-simplex)) diff --git a/package/secubox/luci-app-simplex/htdocs/luci-static/resources/view/simplex/overview.js b/package/secubox/luci-app-simplex/htdocs/luci-static/resources/view/simplex/overview.js new file mode 100644 index 00000000..6ff57c7a --- /dev/null +++ b/package/secubox/luci-app-simplex/htdocs/luci-static/resources/view/simplex/overview.js @@ -0,0 +1,458 @@ +'use strict'; +'require view'; +'require form'; +'require uci'; +'require rpc'; +'require poll'; +'require ui'; + +var callSimplexStatus = rpc.declare({ + object: 'luci.simplex', + method: 'status', + expect: { '': {} } +}); + +var callSimplexStart = rpc.declare({ + object: 'luci.simplex', + method: 'start' +}); + +var callSimplexStop = rpc.declare({ + object: 'luci.simplex', + method: 'stop' +}); + +var callSimplexRestart = rpc.declare({ + object: 'luci.simplex', + method: 'restart' +}); + +var callSimplexInstall = rpc.declare({ + object: 'luci.simplex', + method: 'install' +}); + +var callSimplexGetAddresses = rpc.declare({ + object: 'luci.simplex', + method: 'get_addresses', + expect: { '': {} } +}); + +var callSimplexGetStats = rpc.declare({ + object: 'luci.simplex', + method: 'get_stats', + expect: { '': {} } +}); + +var callSimplexInitCerts = rpc.declare({ + object: 'luci.simplex', + method: 'init_certs', + params: ['hostname'] +}); + +function formatBytes(bytes, decimals) { + if (bytes === 0) return '0 B'; + var k = 1024; + var dm = decimals || 2; + var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} + +function copyToClipboard(text) { + if (navigator.clipboard) { + navigator.clipboard.writeText(text).then(function() { + ui.addNotification(null, E('p', _('Address copied to clipboard')), 'success'); + }); + } else { + var textarea = document.createElement('textarea'); + textarea.value = text; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); + ui.addNotification(null, E('p', _('Address copied to clipboard')), 'success'); + } +} + +return view.extend({ + load: function() { + return Promise.all([ + uci.load('simplex'), + callSimplexStatus(), + callSimplexGetAddresses(), + callSimplexGetStats() + ]); + }, + + render: function(data) { + var status = data[1]; + var addresses = data[2]; + var stats = data[3]; + var m, s, o; + + m = new form.Map('simplex', _('SimpleX Chat Server'), + _('Privacy-focused self-hosted messaging infrastructure with SMP (message relay) and XFTP (file transfer) servers.')); + + // ========================================== + // Status Section + // ========================================== + s = m.section(form.NamedSection, 'main', 'simplex', _('Service Status')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_status', _('Status')); + o.rawhtml = true; + o.cfgvalue = function() { + var html = '
'; + + // LXC/Container status + html += '
'; + html += '

Container

'; + + if (!status.lxc_available) { + html += '

LXC not installed

'; + html += '

Install: opkg install lxc lxc-common

'; + } else if (!status.container_exists) { + html += '

Not installed

'; + html += '

Click "Install" below

'; + } else { + var containerColor = status.container_status === 'running' ? '#080' : '#c60'; + html += '

Status: ' + status.container_status + '

'; + } + html += '
'; + + // SMP Server status + if (status.smp_enabled) { + html += '
'; + html += '

SMP Server

'; + var smpColor = status.smp_status === 'running' ? '#080' : '#c00'; + html += '

Status: ' + status.smp_status + '

'; + html += '

Port: ' + status.smp_port + '/tcp

'; + if (status.smp_hostname) { + html += '

Host: ' + status.smp_hostname + '

'; + } + html += '
'; + } + + // XFTP Server status + if (status.xftp_enabled) { + html += '
'; + html += '

XFTP Server

'; + var xftpColor = status.xftp_status === 'running' ? '#080' : '#c00'; + html += '

Status: ' + status.xftp_status + '

'; + html += '

Port: ' + status.xftp_port + '/tcp

'; + if (status.xftp_hostname) { + html += '

Host: ' + status.xftp_hostname + '

'; + } + html += '
'; + } + + // Storage stats + if (stats) { + html += '
'; + html += '

Storage

'; + html += '

Used: ' + formatBytes(stats.storage_used || 0) + '

'; + html += '

Quota: ' + formatBytes(stats.storage_quota || 0) + '

'; + html += '

Files: ' + (stats.file_count || 0) + '

'; + if (stats.storage_quota > 0) { + var pct = Math.round((stats.storage_used / stats.storage_quota) * 100); + var barColor = pct > 80 ? '#c00' : (pct > 60 ? '#c60' : '#080'); + html += '
'; + html += '
'; + html += '
'; + html += '' + pct + '% used'; + } + html += '
'; + } + + html += '
'; + return html; + }; + + // Control buttons + o = s.option(form.Button, '_start', _('Start')); + o.inputtitle = _('Start'); + o.inputstyle = 'apply'; + o.onclick = function() { + return callSimplexStart().then(function() { + window.location.reload(); + }); + }; + + o = s.option(form.Button, '_stop', _('Stop')); + o.inputtitle = _('Stop'); + o.inputstyle = 'remove'; + o.onclick = function() { + return callSimplexStop().then(function() { + window.location.reload(); + }); + }; + + o = s.option(form.Button, '_restart', _('Restart')); + o.inputtitle = _('Restart'); + o.inputstyle = 'reload'; + o.onclick = function() { + return callSimplexRestart().then(function() { + window.location.reload(); + }); + }; + + // ========================================== + // Server Addresses Section + // ========================================== + s = m.section(form.NamedSection, 'main', 'simplex', _('Server Addresses')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_addresses', _('Addresses')); + o.rawhtml = true; + o.cfgvalue = function() { + var html = '
'; + + if (!addresses.smp_address && !addresses.xftp_address) { + html += '

Server addresses will appear here after installation and configuration.

'; + html += '

Set hostnames in the configuration below, then restart the service.

'; + } else { + html += '

Add these addresses to your SimpleX Chat app under Settings > Network & Servers

'; + + if (addresses.smp_address) { + html += '
'; + html += ''; + html += '
'; + html += '' + addresses.smp_address + ''; + html += ''; + html += '
'; + html += '
'; + } + + if (addresses.xftp_address) { + html += '
'; + html += ''; + html += '
'; + html += '' + addresses.xftp_address + ''; + html += ''; + html += '
'; + html += '
'; + } + } + + html += '
'; + + // Add copyToClipboard function to window + html += ''; + + return html; + }; + + // ========================================== + // General Configuration + // ========================================== + s = m.section(form.NamedSection, 'main', 'simplex', _('General Configuration')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('Enabled'), + _('Enable SimpleX Chat servers')); + o.rmempty = false; + + o = s.option(form.Value, 'data_path', _('Data Path'), + _('Path for server data and configuration')); + o.default = '/srv/simplex'; + o.placeholder = '/srv/simplex'; + + o = s.option(form.Value, 'memory_limit', _('Memory Limit'), + _('Container memory limit')); + o.default = '256M'; + o.placeholder = '256M'; + + // ========================================== + // SMP Server Configuration + // ========================================== + s = m.section(form.NamedSection, 'smp', 'smp', _('SMP Server (Message Relay)')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('Enabled'), + _('Enable SMP server for message relay')); + o.default = '1'; + + o = s.option(form.Value, 'hostname', _('Hostname'), + _('Public hostname or IP address for clients to connect')); + o.placeholder = 'smp.example.com'; + o.rmempty = false; + + o = s.option(form.Value, 'port', _('Port'), + _('TCP port for SMP connections')); + o.datatype = 'port'; + o.default = '5223'; + + o = s.option(form.Value, 'control_port', _('Control Port'), + _('Local control port (admin API)')); + o.datatype = 'port'; + o.default = '5224'; + + o = s.option(form.Flag, 'store_log', _('Store Message Log'), + _('Enable message store for offline delivery')); + o.default = '1'; + + o = s.option(form.Flag, 'daily_stats', _('Daily Statistics'), + _('Collect daily usage statistics')); + o.default = '1'; + + o = s.option(form.Value, 'queue_password', _('Queue Password'), + _('Optional: require password to create new message queues')); + o.password = true; + o.optional = true; + + // ========================================== + // XFTP Server Configuration + // ========================================== + s = m.section(form.NamedSection, 'xftp', 'xftp', _('XFTP Server (File Transfer)')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('Enabled'), + _('Enable XFTP server for encrypted file sharing')); + o.default = '1'; + + o = s.option(form.Value, 'hostname', _('Hostname'), + _('Public hostname or IP address for clients to connect')); + o.placeholder = 'xftp.example.com'; + o.rmempty = false; + + o = s.option(form.Value, 'port', _('Port'), + _('TCP port for XFTP connections')); + o.datatype = 'port'; + o.default = '443'; + + o = s.option(form.Value, 'control_port', _('Control Port'), + _('Local control port (admin API)')); + o.datatype = 'port'; + o.default = '5225'; + + o = s.option(form.Value, 'storage_quota', _('Storage Quota'), + _('Maximum storage for files (e.g., 10G, 500M)')); + o.default = '10G'; + o.placeholder = '10G'; + + o = s.option(form.Value, 'file_expiry', _('File Expiry'), + _('Time before files are deleted (e.g., 48h, 7d)')); + o.default = '48h'; + o.placeholder = '48h'; + + o = s.option(form.Value, 'create_password', _('Upload Password'), + _('Optional: require password to upload files')); + o.password = true; + o.optional = true; + + // ========================================== + // TLS Configuration + // ========================================== + s = m.section(form.NamedSection, 'tls', 'tls', _('TLS Certificates')); + s.anonymous = true; + + o = s.option(form.Flag, 'use_letsencrypt', _('Use Let\'s Encrypt'), + _('Automatically obtain certificates from Let\'s Encrypt')); + o.default = '0'; + + o = s.option(form.Value, 'domain', _('Domain'), + _('Domain for TLS certificates')); + o.placeholder = 'simplex.example.com'; + o.depends('use_letsencrypt', '1'); + + o = s.option(form.Value, 'email', _('Email'), + _('Email for Let\'s Encrypt notifications')); + o.placeholder = 'admin@example.com'; + o.depends('use_letsencrypt', '1'); + + o = s.option(form.Value, 'cert_path', _('Certificate Path'), + _('Path to store TLS certificates')); + o.default = '/srv/simplex/certs'; + + // ========================================== + // Actions Section + // ========================================== + s = m.section(form.NamedSection, 'main', 'simplex', _('Actions')); + s.anonymous = true; + + o = s.option(form.Button, '_install', _('Install Servers')); + o.inputtitle = _('Install'); + o.inputstyle = 'apply'; + o.onclick = function() { + if (confirm(_('This will download SimpleX binaries and create the LXC container. Continue?'))) { + ui.showModal(_('Installing SimpleX'), [ + E('p', { 'class': 'spinning' }, _('Downloading binaries and setting up container...')) + ]); + return callSimplexInstall().then(function(res) { + ui.hideModal(); + if (res.success) { + ui.addNotification(null, E('p', _('Installation completed successfully')), 'success'); + } else { + ui.addNotification(null, E('p', _('Installation failed: ') + (res.output || 'Unknown error')), 'error'); + } + window.location.reload(); + }).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', _('Installation failed: ') + err), 'error'); + }); + } + }; + + o = s.option(form.Button, '_init_certs', _('Generate Certificates')); + o.inputtitle = _('Generate TLS Certs'); + o.inputstyle = 'reload'; + o.onclick = function() { + var hostname = uci.get('simplex', 'smp', 'hostname') || uci.get('simplex', 'tls', 'domain'); + if (!hostname) { + ui.addNotification(null, E('p', _('Please set a hostname first')), 'warning'); + return; + } + return callSimplexInitCerts(hostname).then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', _('Certificates generated successfully')), 'success'); + } else { + ui.addNotification(null, E('p', _('Certificate generation failed: ') + (res.output || 'Unknown error')), 'error'); + } + }); + }; + + // ========================================== + // Help Section + // ========================================== + s = m.section(form.NamedSection, 'main', 'simplex', _('About SimpleX')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_help'); + o.rawhtml = true; + o.cfgvalue = function() { + return '
' + + '

What is SimpleX?

' + + '

SimpleX Chat is a privacy-focused messaging platform with no user identifiers. ' + + 'Self-hosting your own servers ensures your messages never pass through third-party infrastructure.

' + + '

Server Types:

' + + '
    ' + + '
  • SMP Server - Message relay using Simple Messaging Protocol
  • ' + + '
  • XFTP Server - Encrypted file transfer storage
  • ' + + '
' + + '

Setup Steps:

' + + '
    ' + + '
  1. Set hostnames for SMP and XFTP servers above
  2. ' + + '
  3. Click "Install Servers" to download binaries and create container
  4. ' + + '
  5. Enable the service and save configuration
  6. ' + + '
  7. Open firewall ports (5223 for SMP, 443 for XFTP)
  8. ' + + '
  9. Add server addresses to your SimpleX Chat mobile app
  10. ' + + '
' + + '

Resources:

' + + '' + + '

CLI Commands:

' + + '' + + 'simplexctl status # Show server status
' + + 'simplexctl get-address # Show server addresses
' + + 'simplexctl logs smp # View SMP logs
' + + 'simplexctl shell # Access container shell' + + '
' + + '
'; + }; + + return m.render(); + } +}); diff --git a/package/secubox/luci-app-simplex/root/usr/libexec/rpcd/luci.simplex b/package/secubox/luci-app-simplex/root/usr/libexec/rpcd/luci.simplex new file mode 100644 index 00000000..7f872ee0 --- /dev/null +++ b/package/secubox/luci-app-simplex/root/usr/libexec/rpcd/luci.simplex @@ -0,0 +1,187 @@ +#!/bin/sh + +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +SIMPLEX_DIR="/srv/simplex" +SMP_DIR="$SIMPLEX_DIR/smp" +XFTP_DIR="$SIMPLEX_DIR/xftp" + +case "$1" in + list) + echo '{"status":{},"start":{},"stop":{},"restart":{},"install":{},"get_addresses":{},"init_certs":{"hostname":"str"},"logs":{"service":"str","lines":"int"},"get_stats":{}}' + ;; + call) + case "$2" in + status) + json_init + + # Get configuration + enabled=$(uci -q get simplex.main.enabled) + smp_enabled=$(uci -q get simplex.smp.enabled) + xftp_enabled=$(uci -q get simplex.xftp.enabled) + smp_hostname=$(uci -q get simplex.smp.hostname) + xftp_hostname=$(uci -q get simplex.xftp.hostname) + smp_port=$(uci -q get simplex.smp.port || echo "5223") + xftp_port=$(uci -q get simplex.xftp.port || echo "443") + + json_add_boolean "enabled" ${enabled:-0} + json_add_boolean "smp_enabled" ${smp_enabled:-1} + json_add_boolean "xftp_enabled" ${xftp_enabled:-1} + json_add_string "smp_hostname" "$smp_hostname" + json_add_string "xftp_hostname" "$xftp_hostname" + json_add_int "smp_port" $smp_port + json_add_int "xftp_port" $xftp_port + + # Check LXC + if command -v lxc-info >/dev/null 2>&1; then + json_add_boolean "lxc_available" 1 + + # Check container status + if lxc-info -n simplex >/dev/null 2>&1; then + json_add_boolean "container_exists" 1 + if lxc-info -n simplex -s 2>/dev/null | grep -q "RUNNING"; then + json_add_string "container_status" "running" + else + json_add_string "container_status" "stopped" + fi + else + json_add_boolean "container_exists" 0 + json_add_string "container_status" "not_installed" + fi + else + json_add_boolean "lxc_available" 0 + json_add_boolean "container_exists" 0 + json_add_string "container_status" "lxc_not_installed" + fi + + # Check server processes + if pgrep smp-server >/dev/null 2>&1; then + json_add_string "smp_status" "running" + else + json_add_string "smp_status" "stopped" + fi + + if pgrep xftp-server >/dev/null 2>&1; then + json_add_string "xftp_status" "running" + else + json_add_string "xftp_status" "stopped" + fi + + # Check binaries exist + if [ -x "$SIMPLEX_DIR/bin/smp-server" ]; then + json_add_boolean "smp_binary_exists" 1 + else + json_add_boolean "smp_binary_exists" 0 + fi + + if [ -x "$SIMPLEX_DIR/bin/xftp-server" ]; then + json_add_boolean "xftp_binary_exists" 1 + else + json_add_boolean "xftp_binary_exists" 0 + fi + + json_dump + ;; + + start) + /etc/init.d/simplex start >/dev/null 2>&1 + echo '{"success":true}' + ;; + + stop) + /etc/init.d/simplex stop >/dev/null 2>&1 + echo '{"success":true}' + ;; + + restart) + /etc/init.d/simplex restart >/dev/null 2>&1 + echo '{"success":true}' + ;; + + install) + output=$(/usr/sbin/simplexctl install 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + get_addresses) + json_init + + # SMP address + smp_enabled=$(uci -q get simplex.smp.enabled) + if [ "$smp_enabled" = "1" ] && [ -f "$SMP_DIR/fingerprint" ]; then + smp_fp=$(cat "$SMP_DIR/fingerprint" 2>/dev/null) + smp_host=$(uci -q get simplex.smp.hostname) + smp_port=$(uci -q get simplex.smp.port || echo "5223") + [ -n "$smp_fp" ] && [ -n "$smp_host" ] && \ + json_add_string "smp_address" "smp://${smp_fp}@${smp_host}:${smp_port}" + json_add_string "smp_fingerprint" "$smp_fp" + fi + + # XFTP address + xftp_enabled=$(uci -q get simplex.xftp.enabled) + if [ "$xftp_enabled" = "1" ] && [ -f "$XFTP_DIR/fingerprint" ]; then + xftp_fp=$(cat "$XFTP_DIR/fingerprint" 2>/dev/null) + xftp_host=$(uci -q get simplex.xftp.hostname) + xftp_port=$(uci -q get simplex.xftp.port || echo "443") + [ -n "$xftp_fp" ] && [ -n "$xftp_host" ] && \ + json_add_string "xftp_address" "xftp://${xftp_fp}@${xftp_host}:${xftp_port}" + json_add_string "xftp_fingerprint" "$xftp_fp" + fi + + json_dump + ;; + + init_certs) + read -r input + hostname=$(echo "$input" | jsonfilter -e '@.hostname' 2>/dev/null) + output=$(/usr/sbin/simplexctl init-certs "$hostname" 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + logs) + read -r input + service=$(echo "$input" | jsonfilter -e '@.service' 2>/dev/null) + lines=$(echo "$input" | jsonfilter -e '@.lines' 2>/dev/null) + [ -z "$lines" ] && lines=50 + + logs=$(/usr/sbin/simplexctl logs "$service" "$lines" 2>&1 | tail -100) + json_init + json_add_string "logs" "$logs" + json_dump + ;; + + get_stats) + json_init + + # Storage stats + if [ -d "$XFTP_DIR/files" ]; then + storage_used=$(du -sb "$XFTP_DIR/files" 2>/dev/null | cut -f1) + file_count=$(find "$XFTP_DIR/files" -type f 2>/dev/null | wc -l) + json_add_int "storage_used" ${storage_used:-0} + json_add_int "file_count" ${file_count:-0} + else + json_add_int "storage_used" 0 + json_add_int "file_count" 0 + fi + + # Parse storage quota + quota=$(uci -q get simplex.xftp.storage_quota || echo "10G") + quota_bytes=$(echo "$quota" | sed -e 's/G/*1024*1024*1024/' -e 's/M/*1024*1024/' -e 's/K/*1024/' | bc 2>/dev/null || echo "10737418240") + json_add_int "storage_quota" $quota_bytes + + json_dump + ;; + esac + ;; +esac + +exit 0 diff --git a/package/secubox/luci-app-simplex/root/usr/share/luci/menu.d/luci-app-simplex.json b/package/secubox/luci-app-simplex/root/usr/share/luci/menu.d/luci-app-simplex.json new file mode 100644 index 00000000..66cf4dd7 --- /dev/null +++ b/package/secubox/luci-app-simplex/root/usr/share/luci/menu.d/luci-app-simplex.json @@ -0,0 +1,14 @@ +{ + "admin/services/simplex": { + "title": "SimpleX Chat", + "order": 65, + "action": { + "type": "view", + "path": "simplex/overview" + }, + "depends": { + "acl": ["luci-app-simplex"], + "uci": {"simplex": true} + } + } +} diff --git a/package/secubox/luci-app-simplex/root/usr/share/rpcd/acl.d/luci-app-simplex.json b/package/secubox/luci-app-simplex/root/usr/share/rpcd/acl.d/luci-app-simplex.json new file mode 100644 index 00000000..7a58133c --- /dev/null +++ b/package/secubox/luci-app-simplex/root/usr/share/rpcd/acl.d/luci-app-simplex.json @@ -0,0 +1,26 @@ +{ + "luci-app-simplex": { + "description": "Grant access to SimpleX Chat Server configuration", + "read": { + "file": { + "/etc/config/simplex": ["read"], + "/srv/simplex/smp/fingerprint": ["read"], + "/srv/simplex/xftp/fingerprint": ["read"] + }, + "ubus": { + "file": ["read", "stat"], + "luci.simplex": ["*"] + }, + "uci": ["simplex"] + }, + "write": { + "file": { + "/etc/config/simplex": ["write"] + }, + "ubus": { + "luci.simplex": ["*"] + }, + "uci": ["simplex"] + } + } +} diff --git a/package/secubox/secubox-app-bonus/root/www/secubox-feed/Packages b/package/secubox/secubox-app-bonus/root/www/secubox-feed/Packages index e69de29b..d586eae1 100644 --- a/package/secubox/secubox-app-bonus/root/www/secubox-feed/Packages +++ b/package/secubox/secubox-app-bonus/root/www/secubox-feed/Packages @@ -0,0 +1,1220 @@ +Package: luci-app-auth-guardian +Version: 0.4.0-r3 +Depends: luci-base, rpcd, nodogsplash +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +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: 11736 + +Package: luci-app-bandwidth-manager +Version: 0.5.0-r2 +Depends: luci-base, rpcd, tc, kmod-sched-core, kmod-sched-cake, kmod-ifb, sqm-scripts, iptables, iptables-mod-conntrack-extra, ip-full +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 337920 +Description: Advanced bandwidth management with QoS rules, client quotas, and SQM integration +Filename: luci-app-bandwidth-manager_0.5.0-r2_all.ipk +Size: 61538 + +Package: luci-app-cdn-cache +Version: 0.5.0-r3 +Depends: luci-base, rpcd +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 23183 + +Package: luci-app-client-guardian +Version: 0.4.0-r7 +Depends: luci-base, luci-app-secubox, luci-lib-jsonc, rpcd, rpcd-mod-luci, luci-lib-nixio, dnsmasq-full, iptables, uhttpd +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 286720 +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: 54537 + +Package: luci-app-crowdsec-dashboard +Version: 0.7.0-r32 +Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, crowdsec +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 174080 +Description: Real-time security monitoring dashboard for CrowdSec on OpenWrt +Filename: luci-app-crowdsec-dashboard_0.7.0-r32_all.ipk +Size: 32627 + +Package: luci-app-cyberfeed +Version: 0.1.1-r1 +Depends: secubox-app-cyberfeed, luci-base, luci-compat +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 71680 +Description: Cyberpunk-themed RSS feed aggregator dashboard with social media support +Filename: luci-app-cyberfeed_0.1.1-r1_all.ipk +Size: 12839 + +Package: luci-app-dnsguard +Version: 1.0.0-r1 +Depends: luci-base +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 40960 +Description: SecuBox DNS Guard - Privacy DNS Manager +Filename: luci-app-dnsguard_1.0.0-r1_all.ipk +Size: 7551 + +Package: luci-app-exposure +Version: 1.0.0-r3 +Depends: luci-base, secubox-app-exposure +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 153600 +Description: LuCI SecuBox Service Exposure Manager +Filename: luci-app-exposure_1.0.0-r3_all.ipk +Size: 20533 + +Package: luci-app-gitea +Version: 1.0.0-r2 +Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-gitea +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 81920 +Description: Modern dashboard for Gitea Platform management on OpenWrt +Filename: luci-app-gitea_1.0.0-r2_all.ipk +Size: 15298 + +Package: luci-app-glances +Version: 1.0.0-r2 +Depends: luci-base, luci-app-secubox, secubox-app-glances +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 40960 +Description: Modern dashboard for Glances system monitoring with SecuBox theme +Filename: luci-app-glances_1.0.0-r2_all.ipk +Size: 6968 + +Package: luci-app-haproxy +Version: 1.0.0-r8 +Depends: secubox-app-haproxy, luci-base, luci-compat +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 204800 +Description: Web interface for managing HAProxy load balancer with vhosts, SSL certificates, and backend routing +Filename: luci-app-haproxy_1.0.0-r8_all.ipk +Size: 34561 + +Package: luci-app-hexojs +Version: 1.0.0-r3 +Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-hexojs +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 184320 +Description: Modern dashboard for Hexo static site generator on OpenWrt +Filename: luci-app-hexojs_1.0.0-r3_all.ipk +Size: 30308 + +Package: luci-app-jitsi +Version: 1.0.0-r1 +Depends: secubox-app-jitsi +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 30720 +Description: LuCI Jitsi Meet Configuration +Filename: luci-app-jitsi_1.0.0-r1_all.ipk +Size: 5141 + +Package: luci-app-ksm-manager +Version: 0.4.0-r2 +Depends: luci-base, rpcd, openssl-util, gnupg2, nitropy, yubikey-manager, opensc, libccid, pcscd, kmod-usb-core, kmod-usb2, kmod-usb3 +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 18724 + +Package: luci-app-localai +Version: 0.1.0-r15 +Depends: luci-base, luci-app-secubox, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-localai +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 71680 +Description: Modern dashboard for LocalAI LLM management on OpenWrt +Filename: luci-app-localai_0.1.0-r15_all.ipk +Size: 13183 + +Package: luci-app-lyrion +Version: 1.0.0-r1 +Depends: luci-base +License: GPL-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 40960 +Description: LuCI support for Lyrion Music Server +Filename: luci-app-lyrion_1.0.0-r1_all.ipk +Size: 6724 + +Package: luci-app-magicmirror2 +Version: 0.4.0-r6 +Depends: luci-base, luci-app-secubox, secubox-app-magicmirror2, jq +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 12279 + +Package: luci-app-mailinabox +Version: 1.0.0-r1 +Depends: luci-base +License: GPL-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 30720 +Description: LuCI support for Mail-in-a-Box +Filename: luci-app-mailinabox_1.0.0-r1_all.ipk +Size: 5481 + +Package: luci-app-media-flow +Version: 0.6.4-r1 +Depends: luci-base, rpcd, jq +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 25420 + +Package: luci-app-metablogizer +Version: 1.0.0-r5 +Depends: luci-base, git +License: GPL-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 122880 +Description: LuCI support for MetaBlogizer Static Site Publisher +Filename: luci-app-metablogizer_1.0.0-r5_all.ipk +Size: 23348 + +Package: luci-app-metabolizer +Version: 1.0.0-r2 +Depends: luci-base, secubox-app-metabolizer +License: GPL-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 30720 +Description: LuCI support for Metabolizer CMS +Filename: luci-app-metabolizer_1.0.0-r2_all.ipk +Size: 4757 + +Package: luci-app-mitmproxy +Version: 0.5.0-r2 +Depends: luci-base, luci-app-secubox, secubox-app-mitmproxy, jq +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 61440 +Description: Modern dashboard for mitmproxy HTTPS traffic inspection with SecuBox theme +Filename: luci-app-mitmproxy_0.5.0-r2_all.ipk +Size: 10527 + +Package: luci-app-mmpm +Version: 0.2.0-r3 +Depends: luci-base, luci-app-secubox, secubox-app-mmpm +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 51200 +Description: Web interface for MMPM - MagicMirror Package Manager +Filename: luci-app-mmpm_0.2.0-r3_all.ipk +Size: 7904 + +Package: luci-app-mqtt-bridge +Version: 0.4.0-r4 +Depends: luci-base, luci-lib-jsonc, luci-lua-runtime +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 22778 + +Package: luci-app-ndpid +Version: 1.1.2-r2 +Depends: luci-base, luci-app-secubox, ndpid, socat, jq +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 22652 + +Package: luci-app-netdata-dashboard +Version: 0.5.0-r2 +Depends: luci-base, luci-app-secubox, luci-lib-jsonc, rpcd, rpcd-mod-luci +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 20490 + +Package: luci-app-network-modes +Version: 0.5.0-r3 +Depends: luci-base, luci-app-secubox, luci-lib-jsonc, rpcd, rpcd-mod-luci, luci-lib-nixio +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 286720 +Description: Configure OpenWrt for different network modes: Sniffer, Access Point, Relay, Router +Filename: luci-app-network-modes_0.5.0-r3_all.ipk +Size: 54150 + +Package: luci-app-network-tweaks +Version: 1.0.0-r7 +Depends: luci-base, rpcd, luci-app-vhost-manager, dnsmasq +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 14966 + +Package: luci-app-nextcloud +Version: 1.0.0-r1 +Depends: luci-base +License: GPL-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 30720 +Description: LuCI support for Nextcloud +Filename: luci-app-nextcloud_1.0.0-r1_all.ipk +Size: 6487 + +Package: luci-app-ollama +Version: 0.1.0-r1 +Depends: luci-base, luci-app-secubox, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-ollama +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 61440 +Description: Modern dashboard for Ollama LLM management on OpenWrt +Filename: luci-app-ollama_0.1.0-r1_all.ipk +Size: 12351 + +Package: luci-app-picobrew +Version: 1.0.0-r1 +Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-picobrew +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 9456 + +Package: luci-app-secubox +Version: 0.7.1-r4 +Depends: luci-base, rpcd, curl, jq, secubox-core +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 419840 +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: 77680 + +Package: luci-app-secubox-admin +Version: 1.0.0-r19 +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +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: 57247 + +Package: luci-app-secubox-crowdsec +Version: 1.0.0-r3 +Depends: luci-base, crowdsec, secubox-app-cs-firewall-bouncer +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 81920 +Description: LuCI SecuBox CrowdSec Dashboard +Filename: luci-app-secubox-crowdsec_1.0.0-r3_all.ipk +Size: 13922 + +Package: luci-app-secubox-netdiag +Version: 1.0.0-r1 +Depends: luci-base, ethtool +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +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: 15306 + +Package: luci-app-secubox-netifyd +Version: 1.2.1-r1 +Depends: luci-base, rpcd, netifyd, jq, secubox-core, ipset, kmod-nft-compat +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 194560 +Description: Complete LuCI interface for netifyd DPI engine with real-time flow monitoring, application detection, network analytics, and flow action plugins +Filename: luci-app-secubox-netifyd_1.2.1-r1_all.ipk +Size: 36545 + +Package: luci-app-secubox-p2p +Version: 0.1.0-r1 +Depends: secubox-p2p, luci-base +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 215040 +Description: LuCI SecuBox P2P Hub +Filename: luci-app-secubox-p2p_0.1.0-r1_all.ipk +Size: 39234 + +Package: luci-app-secubox-portal +Version: 0.7.0-r2 +Depends: luci-base, luci-theme-secubox +License: GPL-3.0-or-later +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 122880 +Description: Unified entry point for all SecuBox applications with tabbed navigation +Filename: luci-app-secubox-portal_0.7.0-r2_all.ipk +Size: 24644 + +Package: luci-app-secubox-security-threats +Version: 1.0.0-r4 +Depends: luci-base, rpcd, netifyd, crowdsec, jq, jsonfilter +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 102400 +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: 21314 + +Package: luci-app-service-registry +Version: 1.0.0-r1 +Depends: secubox-core, luci-base, luci-compat +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +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: 39826 + +Package: luci-app-simplex +Version: 1.0.0-r1 +Depends: secubox-app-simplex +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 40960 +Description: LuCI SimpleX Chat Server Configuration +Filename: luci-app-simplex_1.0.0-r1_all.ipk +Size: 6999 + +Package: luci-app-streamlit +Version: 1.0.0-r11 +Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-streamlit +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 81920 +Description: Multi-instance Streamlit management with Gitea integration +Filename: luci-app-streamlit_1.0.0-r11_all.ipk +Size: 14750 + +Package: luci-app-system-hub +Version: 0.5.1-r4 +Depends: luci-base, rpcd, coreutils, coreutils-base64 +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 317440 +Description: Central system control with monitoring, services, logs, and backup +Filename: luci-app-system-hub_0.5.1-r4_all.ipk +Size: 61103 + +Package: luci-app-tor-shield +Version: 1.0.0-r10 +Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-tor +License: MIT +Section: luci +Maintainer: OpenWrt LuCI community +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: 22363 + +Package: luci-app-traffic-shaper +Version: 0.4.0-r2 +Depends: luci-base, rpcd, tc, kmod-sched-core, kmod-sched-cake +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 14533 + +Package: luci-app-vhost-manager +Version: 0.5.0-r5 +Depends: luci-base, rpcd, nginx-ssl, acme, curl +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 26186 + +Package: luci-app-wireguard-dashboard +Version: 0.7.0-r5 +Depends: luci-base, luci-app-secubox, luci-lib-jsonc, rpcd, rpcd-mod-luci, wireguard-tools +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 204800 +Description: Modern dashboard for WireGuard VPN monitoring on OpenWrt +Filename: luci-app-wireguard-dashboard_0.7.0-r5_all.ipk +Size: 39608 + +Package: luci-app-zigbee2mqtt +Version: 1.0.0-r2 +Depends: luci-base, luci-lib-jsonc, secubox-app-zigbee2mqtt, luci-lua-runtime +License: GPL-3.0-or-later +Section: luci +Maintainer: OpenWrt LuCI community +Architecture: all +Installed-Size: 40960 +Description: Graphical interface for managing the Zigbee2MQTT docker application. +Filename: luci-app-zigbee2mqtt_1.0.0-r2_all.ipk +Size: 6816 + +Package: luci-theme-secubox +Version: 0.4.7-r1 +Depends: luci-base +License: Apache-2.0 +Section: luci +Maintainer: OpenWrt LuCI community +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: 110242 + +Package: secubox-app +Version: 1.0.0-r2 +Depends: jsonfilter +Section: utils +Architecture: all +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: 11186 + +Package: secubox-app-adguardhome +Version: 1.0.0-r2 +Depends: dockerd, docker, containerd +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: Installer, configuration, and service manager for running AdGuard Home + 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: 2884 + +Package: secubox-app-auth-logger +Version: 1.2.2-r1 +Depends: rpcd, uhttpd +Provides: secubox-auth-logger +License: Apache-2.0 +Section: secubox +Maintainer: CyberMind +Architecture: all +Installed-Size: 51200 +Description: Logs authentication failures from LuCI/rpcd and Dropbear SSH + for CrowdSec detection. Includes: + - SSH failure monitoring (OpenSSH/Dropbear) + - LuCI web interface auth failure logging via CGI hook + - JavaScript hook to intercept login failures + - CrowdSec parser and bruteforce scenario +Filename: secubox-app-auth-logger_1.2.2-r1_all.ipk +Size: 9376 + +Package: secubox-app-crowdsec-custom +Version: 1.1.0-r1 +Depends: crowdsec, crowdsec-firewall-bouncer +Provides: secubox-crowdsec-custom +License: Apache-2.0 +Section: secubox +Maintainer: CyberMind +Architecture: all +Installed-Size: 40960 +Description: Custom CrowdSec configurations for SecuBox web interface protection. + Includes: + - HTTP authentication bruteforce detection + - Path scanning/enumeration detection + - LuCI/uhttpd auth failure monitoring + - Nginx reverse proxy monitoring (if used) + - HAProxy backend protection and auth monitoring + - Gitea web/SSH/API bruteforce detection + - Streamlit app flooding and auth protection + - Webapp generic auth bruteforce protection + - Whitelist for trusted networks +Filename: secubox-app-crowdsec-custom_1.1.0-r1_all.ipk +Size: 5762 + +Package: secubox-app-cs-firewall-bouncer +Version: 0.0.31-r4 +Depends: nftables +Conflicts: crowdsec-firewall-bouncer +Provides: crowdsec-firewall-bouncer +License: MIT +LicenseFiles: LICENSE +Section: net +Maintainer: CyberMind +Architecture: aarch64_cortex-a72 +Installed-Size: 13803520 +Description: SecuBox CrowdSec Firewall Bouncer for OpenWrt. + + Fetches decisions from CrowdSec Local API and enforces them + using nftables. Supports both IPv4 and IPv6 blocking with + timeout-based set entries for automatic expiration. + + Features: + - Native nftables integration + - IPv4 and IPv6 support + - Input and forward chain filtering + - Interface-based filtering + - Automatic restart on firewall reload + - procd service management +Filename: secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk +Size: 5049323 + +Package: secubox-app-cyberfeed +Version: 0.2.1-r1 +Depends: wget-ssl, jsonfilter, coreutils-stat +License: MIT +Section: secubox +Maintainer: CyberMind +Architecture: all +Installed-Size: 51200 +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: 12452 + +Package: secubox-app-domoticz +Version: 1.0.0-r2 +Depends: dockerd, docker, containerd +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 10240 +Description: Installer, configuration, and service manager for running Domoticz + inside Docker on SecuBox-powered OpenWrt systems. +Filename: secubox-app-domoticz_1.0.0-r2_all.ipk +Size: 2547 + +Package: secubox-app-exposure +Version: 1.0.0-r1 +Depends: secubox-core +License: MIT +Section: secubox +Maintainer: SecuBox Team +Architecture: all +Installed-Size: 40960 +Description: Unified service exposure manager for SecuBox. + - Port conflict detection and resolution + - Dynamic Tor hidden service management + - HAProxy SSL reverse proxy configuration +Filename: secubox-app-exposure_1.0.0-r1_all.ipk +Size: 6936 + +Package: secubox-app-gitea +Version: 1.0.0-r5 +Depends: jsonfilter, wget-ssl, tar, lxc, lxc-common, git +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 40960 +Description: Gitea Git Platform - Self-hosted lightweight Git service + + Features: + - Run Gitea in LXC container + - Git HTTP and SSH support + - Repository management + - User management with web UI + - SQLite database (embedded) + - Backup and restore + + Runs in LXC container with Alpine Linux. + Configure in /etc/config/gitea. +Filename: secubox-app-gitea_1.0.0-r5_all.ipk +Size: 9407 + +Package: secubox-app-glances +Version: 1.0.0-r1 +Depends: wget, tar +License: LGPL-3.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: Glances - Cross-platform system monitoring tool for SecuBox. + + Features: + - Real-time CPU, memory, disk, network monitoring + - Process list with resource usage + - Docker/Podman container monitoring + - Web-based UI accessible from any device + - RESTful JSON API for integrations + - Alert system for thresholds + + Runs in LXC container for isolation and security. + Configure in /etc/config/glances. +Filename: secubox-app-glances_1.0.0-r1_all.ipk +Size: 5536 + +Package: secubox-app-haproxy +Version: 1.0.0-r23 +Depends: lxc, lxc-common, openssl-util, wget-ssl, tar, jsonfilter, acme, acme-acmesh, socat, uhttpd +License: MIT +Section: secubox +Maintainer: CyberMind +Architecture: all +Installed-Size: 71680 +Description: HAProxy load balancer and reverse proxy running in an LXC container. + Features: + - Virtual hosts with SNI routing + - Multi-certificate SSL/TLS termination + - Let's Encrypt auto-renewal via ACME + - Backend health checks + - URL-based routing and redirections + - Stats dashboard + - Rate limiting and ACLs +Filename: secubox-app-haproxy_1.0.0-r23_all.ipk +Size: 15682 + +Package: secubox-app-hexojs +Version: 1.0.0-r8 +Depends: jsonfilter, wget-ssl, tar, lxc, lxc-common, git, rsync +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 501760 +Description: Hexo CMS - Self-hosted static blog generator for OpenWrt + + Features: + - Hexo 8.x static site generator with Node.js 22 LTS + - CyberMind theme with dark mode and modern design + - Post and page management with Markdown editor + - Media library for images and files + - GitHub Pages deployment support + - Preview server for local testing + - Categories, tags, and apps portfolio + + Runs in LXC container with Alpine Linux. + Configure in /etc/config/hexojs. +Filename: secubox-app-hexojs_1.0.0-r8_all.ipk +Size: 94939 + +Package: secubox-app-jitsi +Version: 1.0.0-r1 +Depends: docker, docker-compose, wget, openssl-util +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 40960 +Description: Jitsi Meet - Secure, fully featured video conferencing for SecuBox. + + Features: + - End-to-end encrypted video conferences + - No account required for guests + - Screen sharing and recording + - Chat, reactions, and virtual backgrounds + - Mobile app support (iOS/Android) + - WebRTC-based, works in any browser + - Self-hosted for complete privacy + + Runs via Docker containers for easy deployment. + Integrates with HAProxy for SSL termination. + Configure in /etc/config/jitsi. +Filename: secubox-app-jitsi_1.0.0-r1_all.ipk +Size: 8911 + +Package: secubox-app-localai +Version: 2.25.0-r1 +Depends: libstdcpp6, libpthread, wget-ssl, ca-certificates +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 30720 +Description: LocalAI native binary package for OpenWrt. + + Features: + - OpenAI-compatible REST API + - GGUF model support (LLaMA, Mistral, Phi, TinyLlama, etc.) + - Controller CLI (localaictl) + - Automatic binary download from GitHub + + The binary is downloaded on first run via 'localaictl install'. + + API: http://:8081/v1 +Filename: secubox-app-localai_2.25.0-r1_all.ipk +Size: 5724 + +Package: secubox-app-localai-wb +Version: 2.25.0-r1 +Depends: libstdcpp6, libpthread, wget-ssl, ca-certificates +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 30720 +Description: LocalAI native binary package for OpenWrt. + + This package provides: + - Configuration files and init scripts + - Controller CLI (localai-wb-ctl) + - Automatic binary download on first start + + The binary is downloaded from GitHub releases on first run. + For full backend support (llama-cpp, whisper, etc.), consider: + - secubox-app-localai (Docker/LXC based) + - secubox-app-ollama (recommended for ARM64) + + API: http://:8080/v1 +Filename: secubox-app-localai-wb_2.25.0-r1_all.ipk +Size: 7953 + +Package: secubox-app-lyrion +Version: 2.0.2-r1 +Depends: wget, tar +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 30720 +Description: Lyrion Media Server (formerly Logitech Media Server / Squeezebox Server) + for SecuBox-powered OpenWrt systems. + + Supports multiple container runtimes: + - Docker (if dockerd is installed) + - LXC (if lxc packages are installed) + + 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: 7287 + +Package: secubox-app-magicmirror2 +Version: 0.4.0-r8 +Depends: wget, tar, jq, zstd +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 40960 +Description: MagicMirror² - Open source modular smart mirror platform for SecuBox. + + Features: + - Modular architecture with hundreds of available modules + - Built-in module manager for easy installation + - Weather, calendar, news, and custom widgets + - Web-based configuration interface + - Kiosk mode for dedicated displays + + Runs in LXC container for isolation and security. + Configure in /etc/config/magicmirror2. +Filename: secubox-app-magicmirror2_0.4.0-r8_all.ipk +Size: 9254 + +Package: secubox-app-mailinabox +Version: 2.0.0-r1 +License: CC0-1.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 30720 +Description: Complete email server solution using docker-mailserver for SecuBox. + + Features: + - Full email server (SMTP, IMAP, POP3) + - User account management (add/remove/list) + - Email aliases support + - SpamAssassin spam filtering + - ClamAV antivirus (optional) + - Fail2ban intrusion prevention + - Let's Encrypt SSL certificates + - Backup and restore functionality + - DNS configuration verification + - Health monitoring and diagnostics + + Commands: mailinaboxctl --help +Filename: secubox-app-mailinabox_2.0.0-r1_all.ipk +Size: 7572 + +Package: secubox-app-metabolizer +Version: 1.0.0-r3 +Depends: secubox-app-gitea, secubox-app-streamlit, secubox-app-hexojs, rsync, git +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 71680 +Description: Metabolizer Blog Pipeline - Integrated CMS with Git-based workflow + + Features: + - Gitea: Mirror GitHub repos, store content + - Streamlit: CMS with markdown editor + - HexoJS: Static site generator + - Webhooks: Auto-rebuild on push + - Portal: Static blog access + + Pipeline: Edit in Streamlit -> Push to Gitea -> Build with Hexo -> Publish +Filename: secubox-app-metabolizer_1.0.0-r3_all.ipk +Size: 13979 + +Package: secubox-app-mitmproxy +Version: 0.5.0-r19 +Depends: wget, tar +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 92160 +Description: mitmproxy - Interactive HTTPS proxy for SecuBox-powered OpenWrt systems. + + Features: + - Intercept and inspect HTTP/HTTPS traffic + - Modify requests and responses on the fly + - Web interface (mitmweb) for easy analysis + - Export traffic for offline analysis + - Enhanced threat detection addon (v2.0): + * SQL injection, XSS, command injection + * Path traversal, SSRF, XXE, LDAP injection + * Log4Shell and known CVE detection + * Rate limiting and suspicious header detection + * CrowdSec integration for blocking + + Runs in LXC container for isolation and security. + Configure in /etc/config/mitmproxy. +Filename: secubox-app-mitmproxy_0.5.0-r19_all.ipk +Size: 22956 + +Package: secubox-app-mmpm +Version: 0.2.0-r5 +Depends: secubox-app-magicmirror2 +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: MMPM (MagicMirror Package Manager) for SecuBox. + + Features: + - Web-based GUI for module management + - Search MagicMirror module registry + - Install, update, remove modules easily + - Automatic dependency handling + - Module configuration interface + + Runs inside the MagicMirror2 LXC container. +Filename: secubox-app-mmpm_0.2.0-r5_all.ipk +Size: 3976 + +Package: secubox-app-nextcloud +Version: 1.0.0-r2 +Depends: dockerd, docker, containerd +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: Installer, configuration, and service manager for running Nextcloud + inside Docker on SecuBox-powered OpenWrt systems. Self-hosted file + sync and share with calendar, contacts, and collaboration. +Filename: secubox-app-nextcloud_1.0.0-r2_all.ipk +Size: 2963 + +Package: secubox-app-ollama +Version: 0.1.0-r1 +Depends: jsonfilter, wget-ssl +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: Ollama - Simple local LLM runtime for SecuBox-powered OpenWrt systems. + + Features: + - Easy model management (ollama pull, ollama run) + - OpenAI-compatible API + - Native ARM64 support with backends included + - Lightweight compared to LocalAI + - Support for LLaMA, Mistral, Phi, Gemma models + + Runs in Docker/Podman container. + Configure in /etc/config/ollama. +Filename: secubox-app-ollama_0.1.0-r1_all.ipk +Size: 5739 + +Package: secubox-app-picobrew +Version: 1.0.0-r7 +Depends: jsonfilter, wget-ssl, tar, lxc, lxc-common, git +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: PicoBrew Server - Self-hosted brewing controller for PicoBrew devices + + Features: + - Control PicoBrew Zymatic, Z, Pico C, and Pico Pro devices + - Recipe management and import from BeerSmith + - Real-time brewing session monitoring + - Temperature and step control + - Session history and logging + + Runs in LXC container with Python/Flask backend. + Configure in /etc/config/picobrew. +Filename: secubox-app-picobrew_1.0.0-r7_all.ipk +Size: 5539 + +Package: secubox-app-simplex +Version: 1.0.0-r1 +Depends: lxc, lxc-common, wget, openssl-util, tar +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 40960 +Description: SimpleX Chat self-hosted messaging infrastructure for SecuBox. + + Features: + - SMP Server (Simple Messaging Protocol) for message relay + - XFTP Server for encrypted file sharing + - No user identifiers or metadata collection + - End-to-end encryption with post-quantum algorithms + - Runs in lightweight Alpine Linux LXC container + - Automatic TLS certificate generation + - HAProxy integration for SSL termination + + Privacy-first messaging relay that you control. + Configure in /etc/config/simplex. +Filename: secubox-app-simplex_1.0.0-r1_all.ipk +Size: 9235 + +Package: secubox-app-streamlit +Version: 1.0.0-r5 +Depends: jsonfilter, wget-ssl, tar, lxc, lxc-common, git +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 51200 +Description: Streamlit App Platform - Self-hosted Python data app platform + + Features: + - Folder-based app structure (app.py, requirements.txt, .streamlit/) + - Multi-instance support (multiple apps on different ports) + - Gitea integration for app deployment and updates + - Python 3.12 with Streamlit in LXC container + - Auto-install requirements.txt with hash-based caching + - HAProxy publish wizard for vhost routing + - Web dashboard integration + + App folder structure: + /srv/streamlit/apps// + app.py, requirements.txt, .streamlit/ + + Configure in /etc/config/streamlit. +Filename: secubox-app-streamlit_1.0.0-r5_all.ipk +Size: 11725 + +Package: secubox-app-tor +Version: 1.0.0-r1 +Depends: iptables, curl, jsonfilter, socat +License: MIT +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 30720 +Description: SecuBox Tor Shield - One-click Tor anonymization for OpenWrt + + Features: + - Transparent proxy mode (route all traffic through Tor) + - SOCKS proxy mode (selective app routing) + - DNS over Tor (prevent DNS leaks) + - Kill switch (block non-Tor traffic) + - Hidden services (.onion) management + - Bridge support (obfs4, snowflake) for censored networks + - Circuit visualization and identity management + + Configure in /etc/config/tor-shield. +Filename: secubox-app-tor_1.0.0-r1_all.ipk +Size: 7369 + +Package: secubox-app-webapp +Version: 1.5.0-r7 +Depends: uhttpd, uhttpd-mod-ubus, rpcd, rpcd-mod-file +License: MIT +Section: secubox +Maintainer: CyberMind.FR +Architecture: all +Installed-Size: 256000 +Description: SecuBox Control Center Dashboard - A web-based dashboard for monitoring + and managing SecuBox/OpenWrt systems. Features include: + - Native OpenWrt authentication via rpcd/ubus + - Real-time system monitoring (CPU, RAM, Disk, Network) + - CrowdSec security integration + - Service management + - Network interface control +Filename: secubox-app-webapp_1.5.0-r7_all.ipk +Size: 39175 + +Package: secubox-app-zigbee2mqtt +Version: 1.0.0-r3 +Depends: kmod-usb-acm, dockerd, docker, containerd +License: Apache-2.0 +Section: utils +Maintainer: CyberMind Studio +Architecture: all +Installed-Size: 20480 +Description: Installer, configuration, and service manager for running Zigbee2MQTT + inside Docker on SecuBox-powered OpenWrt systems. +Filename: secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk +Size: 3542 + +Package: secubox-core +Version: 0.10.0-r11 +Depends: jq, jsonfilter +License: GPL-2.0 +Section: admin +Maintainer: SecuBox Team +Architecture: all +Installed-Size: 481280 +Description: SecuBox Core Framework provides the foundational infrastructure for the + modular SecuBox system including: + - Module/AppStore management + - Component state management system + - Component registry and dependency tracking + - Profile and template engine + - Diagnostics and health checks + - Unified CLI interface + - ubus RPC backend +Filename: secubox-core_0.10.0-r11_all.ipk +Size: 87975 + +Package: secubox-p2p +Version: 0.6.0-r3 +Depends: jsonfilter, curl, avahi-daemon, avahi-utils, uhttpd +License: MIT +Section: secubox +Maintainer: SecuBox Team +Architecture: all +Installed-Size: 204800 +Description: SecuBox P2P Hub backend providing peer discovery, mesh networking + DNS federation, and distributed service management. Includes mDNS + service announcement, REST API on port 7331 for mesh visibility + SecuBox Factory unified dashboard with Ed25519 signed Merkle + snapshots for cryptographic configuration validation, distributed + mesh services panel for aggregated service discovery across all nodes + 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: 42016 + diff --git a/package/secubox/secubox-app-bonus/root/www/secubox-feed/Packages.gz b/package/secubox/secubox-app-bonus/root/www/secubox-feed/Packages.gz index ba3d444f437a28d7912a5cb3260bea9fe58fef24..cf8597bca88c268edd9a53495e7637bfe5986bdd 100644 GIT binary patch delta 8583 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 Ne*j?%IS9Ox004+~m&O19 delta 8352 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 literal 0 HcmV?d00001 diff --git a/package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-app-simplex_1.0.0-r1_all.ipk b/package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-app-simplex_1.0.0-r1_all.ipk new file mode 100644 index 0000000000000000000000000000000000000000..968c54e58e2aef14f1e26d617eb9f51a601bac99 GIT binary patch literal 9235 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 p@ZrOU4<9~!`0(MwhYue +PKG_LICENSE:=Apache-2.0 + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-app-simplex + SECTION:=utils + CATEGORY:=Utilities + PKGARCH:=all + SUBMENU:=SecuBox Apps + TITLE:=SecuBox SimpleX Chat Server + DEPENDS:=+lxc +lxc-common +wget +openssl-util +tar +endef + +define Package/secubox-app-simplex/description +SimpleX Chat self-hosted messaging infrastructure for SecuBox. + +Features: +- SMP Server (Simple Messaging Protocol) for message relay +- XFTP Server for encrypted file sharing +- No user identifiers or metadata collection +- End-to-end encryption with post-quantum algorithms +- Runs in lightweight Alpine Linux LXC container +- Automatic TLS certificate generation +- HAProxy integration for SSL termination + +Privacy-first messaging relay that you control. +Configure in /etc/config/simplex. +endef + +define Package/secubox-app-simplex/conffiles +/etc/config/simplex +endef + +define Build/Compile +endef + +define Package/secubox-app-simplex/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/simplex $(1)/etc/config/simplex + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/simplex $(1)/etc/init.d/simplex + + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./files/usr/sbin/simplexctl $(1)/usr/sbin/simplexctl + + $(INSTALL_DIR) $(1)/usr/lib/secubox/haproxy.d + $(INSTALL_DATA) ./files/usr/lib/secubox/haproxy.d/simplex.cfg $(1)/usr/lib/secubox/haproxy.d/ +endef + +define Package/secubox-app-simplex/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + echo "" + echo "============================================" + echo " SimpleX Chat Server Installed" + echo "============================================" + echo "" + echo "Quick Start:" + echo " 1. Install container: simplexctl install" + echo " 2. Set hostname: uci set simplex.smp.hostname='smp.example.com'" + echo " 3. Commit: uci commit simplex" + echo " 4. Start: /etc/init.d/simplex start" + echo "" + echo "Control commands:" + echo " simplexctl status - Show service status" + echo " simplexctl get-address - Get server addresses for clients" + echo " simplexctl logs - View server logs" + echo "" + echo "Ports: SMP=5223, XFTP=443" + echo "" +} +exit 0 +endef + +$(eval $(call BuildPackage,secubox-app-simplex)) diff --git a/package/secubox/secubox-app-simplex/files/etc/config/simplex b/package/secubox/secubox-app-simplex/files/etc/config/simplex new file mode 100644 index 00000000..652389c0 --- /dev/null +++ b/package/secubox/secubox-app-simplex/files/etc/config/simplex @@ -0,0 +1,32 @@ +# SimpleX Chat Server Configuration + +config simplex 'main' + option enabled '0' + option data_path '/srv/simplex' + option memory_limit '256M' + option container_type 'lxc' + +config smp 'smp' + option enabled '1' + option port '5223' + option control_port '5224' + option hostname '' + option store_log '1' + option daily_stats '1' + option log_stats '0' + option queue_password '' + +config xftp 'xftp' + option enabled '1' + option port '443' + option control_port '5225' + option hostname '' + option storage_quota '10G' + option file_expiry '48h' + option create_password '' + +config tls 'tls' + option use_letsencrypt '0' + option cert_path '/srv/simplex/certs' + option domain '' + option email '' diff --git a/package/secubox/secubox-app-simplex/files/etc/init.d/simplex b/package/secubox/secubox-app-simplex/files/etc/init.d/simplex new file mode 100644 index 00000000..8e0b49b9 --- /dev/null +++ b/package/secubox/secubox-app-simplex/files/etc/init.d/simplex @@ -0,0 +1,77 @@ +#!/bin/sh /etc/rc.common +# SimpleX Chat Server Service + +START=95 +STOP=15 +USE_PROCD=1 + +SIMPLEX_DIR="/srv/simplex" +CONTAINER_NAME="simplex" + +start_service() { + local enabled=$(uci -q get simplex.main.enabled) + [ "$enabled" != "1" ] && return 0 + + # Check LXC + if ! command -v lxc-start >/dev/null 2>&1; then + logger -t simplex "LXC not available" + return 1 + fi + + # Check if container exists + if ! lxc-info -n "$CONTAINER_NAME" >/dev/null 2>&1; then + logger -t simplex "Container not installed. Run: simplexctl install" + return 1 + fi + + # Start container if not running + if ! lxc-info -n "$CONTAINER_NAME" -s 2>/dev/null | grep -q "RUNNING"; then + logger -t simplex "Starting SimpleX container..." + /usr/sbin/simplexctl start-container + fi + + # Start SMP server + local smp_enabled=$(uci -q get simplex.smp.enabled) + if [ "$smp_enabled" = "1" ]; then + procd_open_instance smp + procd_set_param command /usr/sbin/simplexctl service-run smp + procd_set_param respawn 3600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance + fi + + # Start XFTP server + local xftp_enabled=$(uci -q get simplex.xftp.enabled) + if [ "$xftp_enabled" = "1" ]; then + procd_open_instance xftp + procd_set_param command /usr/sbin/simplexctl service-run xftp + procd_set_param respawn 3600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance + fi + + logger -t simplex "SimpleX Chat Server started" +} + +stop_service() { + # Stop services (procd handles the instances) + /usr/sbin/simplexctl stop-services 2>/dev/null + + # Optionally stop container + local keep_container=$(uci -q get simplex.main.keep_container_running) + if [ "$keep_container" != "1" ]; then + /usr/sbin/simplexctl stop-container 2>/dev/null + fi + + logger -t simplex "SimpleX Chat Server stopped" +} + +reload_service() { + /usr/sbin/simplexctl reload-config +} + +status() { + /usr/sbin/simplexctl status +} diff --git a/package/secubox/secubox-app-simplex/files/usr/lib/secubox/haproxy.d/simplex.cfg b/package/secubox/secubox-app-simplex/files/usr/lib/secubox/haproxy.d/simplex.cfg new file mode 100644 index 00000000..1dde432a --- /dev/null +++ b/package/secubox/secubox-app-simplex/files/usr/lib/secubox/haproxy.d/simplex.cfg @@ -0,0 +1,32 @@ +# SimpleX Chat Server HAProxy Configuration +# +# This file provides HAProxy configuration snippets for SimpleX servers. +# SimpleX uses TLS passthrough (TCP mode) as it handles TLS internally. +# +# Note: SMP and XFTP servers require direct TLS connections. +# HAProxy can be used for: +# - TCP load balancing (if running multiple instances) +# - Port mapping +# - Connection limiting +# +# For most setups, direct port forwarding is recommended. + +# Example frontend for SMP (if needed): +# frontend simplex_smp_frontend +# bind *:5223 +# mode tcp +# default_backend simplex_smp_backend +# +# backend simplex_smp_backend +# mode tcp +# server simplex-smp 127.0.0.1:15223 check + +# Example frontend for XFTP: +# frontend simplex_xftp_frontend +# bind *:443 +# mode tcp +# default_backend simplex_xftp_backend +# +# backend simplex_xftp_backend +# mode tcp +# server simplex-xftp 127.0.0.1:1443 check diff --git a/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl b/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl new file mode 100644 index 00000000..630632f9 --- /dev/null +++ b/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl @@ -0,0 +1,985 @@ +#!/bin/sh +# SimpleX Chat Server Control Script for SecuBox +# Manages SMP and XFTP servers in LXC container + +VERSION="1.0.0" +SIMPLEX_DIR="/srv/simplex" +CONTAINER_NAME="simplex" +CONTAINER_PATH="/var/lib/lxc/$CONTAINER_NAME" +SMP_DIR="$SIMPLEX_DIR/smp" +XFTP_DIR="$SIMPLEX_DIR/xftp" +CERTS_DIR="$SIMPLEX_DIR/certs" + +# SimpleX release information +SIMPLEX_VERSION="v6.0.4" +SIMPLEX_BASE_URL="https://github.com/simplex-chat/simplexmq/releases/download" + +# Colors for terminal output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +log() { echo -e "${GREEN}[SIMPLEX]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# ============================================================================ +# UCI Configuration Helpers +# ============================================================================ + +uci_get() { uci -q get simplex.$1; } +uci_set() { uci -q set simplex.$1="$2" && uci commit simplex; } + +load_config() { + # Main settings + ENABLED=$(uci_get main.enabled || echo "0") + DATA_PATH=$(uci_get main.data_path || echo "$SIMPLEX_DIR") + MEMORY_LIMIT=$(uci_get main.memory_limit || echo "256M") + + # SMP settings + SMP_ENABLED=$(uci_get smp.enabled || echo "1") + SMP_PORT=$(uci_get smp.port || echo "5223") + SMP_CONTROL_PORT=$(uci_get smp.control_port || echo "5224") + SMP_HOSTNAME=$(uci_get smp.hostname) + SMP_STORE_LOG=$(uci_get smp.store_log || echo "1") + SMP_QUEUE_PASSWORD=$(uci_get smp.queue_password) + + # XFTP settings + XFTP_ENABLED=$(uci_get xftp.enabled || echo "1") + XFTP_PORT=$(uci_get xftp.port || echo "443") + XFTP_CONTROL_PORT=$(uci_get xftp.control_port || echo "5225") + XFTP_HOSTNAME=$(uci_get xftp.hostname) + XFTP_STORAGE_QUOTA=$(uci_get xftp.storage_quota || echo "10G") + XFTP_FILE_EXPIRY=$(uci_get xftp.file_expiry || echo "48h") + XFTP_CREATE_PASSWORD=$(uci_get xftp.create_password) + + # TLS settings + USE_LETSENCRYPT=$(uci_get tls.use_letsencrypt || echo "0") + CERT_PATH=$(uci_get tls.cert_path || echo "$CERTS_DIR") + TLS_DOMAIN=$(uci_get tls.domain) + TLS_EMAIL=$(uci_get tls.email) + + # Use hostname from TLS domain if not set specifically + [ -z "$SMP_HOSTNAME" ] && SMP_HOSTNAME="$TLS_DOMAIN" + [ -z "$XFTP_HOSTNAME" ] && XFTP_HOSTNAME="$TLS_DOMAIN" +} + +# ============================================================================ +# Architecture Detection +# ============================================================================ + +detect_arch() { + local machine=$(uname -m) + case "$machine" in + aarch64|arm64) + echo "aarch64" + ;; + x86_64|amd64) + echo "x86_64" + ;; + *) + error "Unsupported architecture: $machine" + return 1 + ;; + esac +} + +# ============================================================================ +# LXC Container Management +# ============================================================================ + +container_exists() { + lxc-info -n "$CONTAINER_NAME" >/dev/null 2>&1 +} + +container_running() { + lxc-info -n "$CONTAINER_NAME" -s 2>/dev/null | grep -q "RUNNING" +} + +create_container() { + log "Creating Alpine Linux LXC container..." + + # Create container directory + mkdir -p "$CONTAINER_PATH/rootfs" + + # Download Alpine minirootfs + local arch=$(detect_arch) + local alpine_version="3.19" + local alpine_url="https://dl-cdn.alpinelinux.org/alpine/v${alpine_version}/releases/${arch}/alpine-minirootfs-${alpine_version}.0-${arch}.tar.gz" + + log "Downloading Alpine Linux minirootfs..." + if ! wget -q -O /tmp/alpine-rootfs.tar.gz "$alpine_url"; then + error "Failed to download Alpine rootfs" + return 1 + fi + + # Extract rootfs + tar -xzf /tmp/alpine-rootfs.tar.gz -C "$CONTAINER_PATH/rootfs" + rm -f /tmp/alpine-rootfs.tar.gz + + # Create LXC config + cat > "$CONTAINER_PATH/config" << EOF +# SimpleX Chat LXC Container Configuration + +lxc.uts.name = $CONTAINER_NAME +lxc.arch = linux64 + +# Rootfs +lxc.rootfs.path = dir:$CONTAINER_PATH/rootfs + +# Network - use host networking for direct port access +lxc.net.0.type = none + +# Resource limits +lxc.cgroup2.memory.max = $MEMORY_LIMIT + +# Mount points for persistent data +lxc.mount.entry = $DATA_PATH srv/simplex none bind,create=dir 0 0 + +# Capabilities +lxc.cap.drop = sys_admin sys_module mac_admin mac_override + +# Console +lxc.tty.max = 1 +lxc.pty.max = 1 + +# Auto start +lxc.start.auto = 0 +EOF + + # Setup container init + cat > "$CONTAINER_PATH/rootfs/etc/inittab" << 'EOF' +::sysinit:/sbin/openrc sysinit +::sysinit:/sbin/openrc boot +::wait:/sbin/openrc default +::ctrlaltdel:/sbin/reboot +::shutdown:/sbin/openrc shutdown +EOF + + # Configure networking in container + cat > "$CONTAINER_PATH/rootfs/etc/resolv.conf" << EOF +nameserver 127.0.0.1 +nameserver 1.1.1.1 +EOF + + # Install required packages in container + cat > "$CONTAINER_PATH/rootfs/tmp/setup.sh" << 'SETUP' +#!/bin/sh +apk update +apk add --no-cache ca-certificates openssl curl +mkdir -p /srv/simplex/smp /srv/simplex/xftp /srv/simplex/certs +SETUP + chmod +x "$CONTAINER_PATH/rootfs/tmp/setup.sh" + + log "Container created successfully" +} + +start_container() { + if ! container_exists; then + error "Container does not exist. Run: simplexctl install" + return 1 + fi + + if container_running; then + log "Container already running" + return 0 + fi + + log "Starting container..." + lxc-start -n "$CONTAINER_NAME" -d + + # Wait for container to be ready + local timeout=30 + while [ $timeout -gt 0 ]; do + if container_running; then + log "Container started" + return 0 + fi + sleep 1 + timeout=$((timeout - 1)) + done + + error "Container failed to start" + return 1 +} + +stop_container() { + if container_running; then + log "Stopping container..." + lxc-stop -n "$CONTAINER_NAME" -t 10 + fi +} + +# ============================================================================ +# SimpleX Binary Management +# ============================================================================ + +download_binaries() { + log "Downloading SimpleX binaries (version $SIMPLEX_VERSION)..." + + local arch=$(detect_arch) + local bin_suffix="" + + case "$arch" in + aarch64) bin_suffix="ubuntu-20_04-aarch64" ;; + x86_64) bin_suffix="ubuntu-20_04-x86-64" ;; + esac + + local smp_url="$SIMPLEX_BASE_URL/$SIMPLEX_VERSION/smp-server-$bin_suffix" + local xftp_url="$SIMPLEX_BASE_URL/$SIMPLEX_VERSION/xftp-server-$bin_suffix" + + mkdir -p "$DATA_PATH/bin" + + # Download SMP server + log "Downloading SMP server..." + if ! wget -q --show-progress -O "$DATA_PATH/bin/smp-server" "$smp_url"; then + error "Failed to download SMP server" + return 1 + fi + chmod +x "$DATA_PATH/bin/smp-server" + + # Download XFTP server + log "Downloading XFTP server..." + if ! wget -q --show-progress -O "$DATA_PATH/bin/xftp-server" "$xftp_url"; then + error "Failed to download XFTP server" + return 1 + fi + chmod +x "$DATA_PATH/bin/xftp-server" + + log "Binaries downloaded successfully" +} + +# ============================================================================ +# TLS Certificate Management +# ============================================================================ + +generate_self_signed_certs() { + local hostname="$1" + [ -z "$hostname" ] && hostname="simplex.local" + + log "Generating self-signed TLS certificates for $hostname..." + + mkdir -p "$CERT_PATH" + + # Generate CA key and cert + openssl genrsa -out "$CERT_PATH/ca.key" 4096 2>/dev/null + openssl req -new -x509 -days 3650 -key "$CERT_PATH/ca.key" \ + -out "$CERT_PATH/ca.crt" -subj "/CN=SimpleX CA" 2>/dev/null + + # Generate server key and CSR + openssl genrsa -out "$CERT_PATH/server.key" 4096 2>/dev/null + openssl req -new -key "$CERT_PATH/server.key" \ + -out "$CERT_PATH/server.csr" -subj "/CN=$hostname" 2>/dev/null + + # Create server certificate + cat > "$CERT_PATH/server.ext" << EOF +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = DNS:$hostname +EOF + + openssl x509 -req -in "$CERT_PATH/server.csr" -CA "$CERT_PATH/ca.crt" \ + -CAkey "$CERT_PATH/ca.key" -CAcreateserial -out "$CERT_PATH/server.crt" \ + -days 365 -extfile "$CERT_PATH/server.ext" 2>/dev/null + + # Cleanup + rm -f "$CERT_PATH/server.csr" "$CERT_PATH/server.ext" + + log "Certificates generated at $CERT_PATH" +} + +init_letsencrypt() { + local domain="$1" + local email="$2" + + if [ -z "$domain" ]; then + error "Domain required for Let's Encrypt" + return 1 + fi + + log "Initializing Let's Encrypt for $domain..." + + # Use acme.sh if available, or certbot + if command -v acme.sh >/dev/null 2>&1; then + acme.sh --issue -d "$domain" --standalone --keylength 4096 \ + --cert-file "$CERT_PATH/server.crt" \ + --key-file "$CERT_PATH/server.key" \ + --fullchain-file "$CERT_PATH/fullchain.crt" + else + error "acme.sh not found. Install with: opkg install acme" + return 1 + fi +} + +# ============================================================================ +# Server Configuration +# ============================================================================ + +init_smp_server() { + log "Initializing SMP server..." + + mkdir -p "$SMP_DIR" + + # Initialize server if not already done + if [ ! -f "$SMP_DIR/smp-server.ini" ]; then + local hostname="${SMP_HOSTNAME:-$(hostname)}" + + # Create SMP config + cat > "$SMP_DIR/smp-server.ini" << EOF +[STORE_LOG] +enable: ${SMP_STORE_LOG} +restore_messages: on + +[AUTH] +new_queues: ${SMP_QUEUE_PASSWORD:+on} + +[TRANSPORT] +host: $hostname +port: $SMP_PORT +control_port: ${SMP_CONTROL_PORT} +websockets: off + +[SERVER] +information: SimpleX SMP Server +source_code: https://github.com/simplex-chat/simplexmq +EOF + + # Add password if set + if [ -n "$SMP_QUEUE_PASSWORD" ]; then + echo "create_password: $SMP_QUEUE_PASSWORD" >> "$SMP_DIR/smp-server.ini" + fi + fi + + # Generate server keys if not present + if [ ! -f "$SMP_DIR/server.key" ]; then + log "Generating SMP server keys..." + if [ -x "$DATA_PATH/bin/smp-server" ]; then + cd "$SMP_DIR" + "$DATA_PATH/bin/smp-server" init --store-log + fi + fi + + log "SMP server initialized" +} + +init_xftp_server() { + log "Initializing XFTP server..." + + mkdir -p "$XFTP_DIR/files" + + # Parse storage quota + local quota_bytes=$(echo "$XFTP_STORAGE_QUOTA" | \ + sed -e 's/G/*1024*1024*1024/' -e 's/M/*1024*1024/' -e 's/K/*1024/' | bc) + + # Create XFTP config + if [ ! -f "$XFTP_DIR/xftp-server.ini" ]; then + local hostname="${XFTP_HOSTNAME:-$(hostname)}" + + cat > "$XFTP_DIR/xftp-server.ini" << EOF +[STORE_LOG] +enable: on +file_path: $XFTP_DIR/files + +[AUTH] +new_files: ${XFTP_CREATE_PASSWORD:+on} + +[TRANSPORT] +host: $hostname +port: $XFTP_PORT +control_port: ${XFTP_CONTROL_PORT} + +[SERVER] +information: SimpleX XFTP Server +source_code: https://github.com/simplex-chat/simplexmq + +[QUOTA] +storage_quota: $quota_bytes +file_expiration: $XFTP_FILE_EXPIRY +EOF + + # Add password if set + if [ -n "$XFTP_CREATE_PASSWORD" ]; then + echo "create_password: $XFTP_CREATE_PASSWORD" >> "$XFTP_DIR/xftp-server.ini" + fi + fi + + # Generate server keys if not present + if [ ! -f "$XFTP_DIR/server.key" ]; then + log "Generating XFTP server keys..." + if [ -x "$DATA_PATH/bin/xftp-server" ]; then + cd "$XFTP_DIR" + "$DATA_PATH/bin/xftp-server" init + fi + fi + + log "XFTP server initialized" +} + +# ============================================================================ +# Service Control +# ============================================================================ + +service_run() { + local service="$1" + load_config + + case "$service" in + smp) + if [ -x "$DATA_PATH/bin/smp-server" ]; then + cd "$SMP_DIR" + exec "$DATA_PATH/bin/smp-server" start + else + error "SMP server binary not found" + exit 1 + fi + ;; + xftp) + if [ -x "$DATA_PATH/bin/xftp-server" ]; then + cd "$XFTP_DIR" + exec "$DATA_PATH/bin/xftp-server" start + else + error "XFTP server binary not found" + exit 1 + fi + ;; + *) + error "Unknown service: $service" + exit 1 + ;; + esac +} + +stop_services() { + # Send SIGTERM to any running servers + pgrep smp-server && pkill smp-server + pgrep xftp-server && pkill xftp-server +} + +reload_config() { + load_config + init_smp_server + init_xftp_server + log "Configuration reloaded" +} + +# ============================================================================ +# Server Addresses +# ============================================================================ + +get_server_fingerprint() { + local key_file="$1" + if [ -f "$key_file" ]; then + # Extract base64-encoded fingerprint from key + openssl x509 -in "$key_file" -noout -fingerprint -sha256 2>/dev/null | \ + sed 's/.*=//' | tr -d ':' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=' + fi +} + +show_addresses() { + load_config + + echo "" + echo "==========================================" + echo " SimpleX Server Addresses" + echo "==========================================" + echo "" + + # SMP address + if [ "$SMP_ENABLED" = "1" ] && [ -f "$SMP_DIR/fingerprint" ]; then + local smp_fp=$(cat "$SMP_DIR/fingerprint" 2>/dev/null) + local smp_host="${SMP_HOSTNAME:-$(hostname)}" + echo "SMP Server:" + echo " smp://${smp_fp}@${smp_host}:${SMP_PORT}" + echo "" + fi + + # XFTP address + if [ "$XFTP_ENABLED" = "1" ] && [ -f "$XFTP_DIR/fingerprint" ]; then + local xftp_fp=$(cat "$XFTP_DIR/fingerprint" 2>/dev/null) + local xftp_host="${XFTP_HOSTNAME:-$(hostname)}" + echo "XFTP Server:" + echo " xftp://${xftp_fp}@${xftp_host}:${XFTP_PORT}" + echo "" + fi + + echo "Add these addresses to your SimpleX Chat app" + echo "under Settings > Network & Servers" + echo "" +} + +# ============================================================================ +# Firewall Configuration +# ============================================================================ + +configure_firewall() { + load_config + log "Configuring firewall..." + + # SMP port + if [ "$SMP_ENABLED" = "1" ]; then + if ! uci show firewall 2>/dev/null | grep -q "SimpleX-SMP"; then + uci add firewall rule + uci set firewall.@rule[-1].name='SimpleX-SMP' + uci set firewall.@rule[-1].src='wan' + uci set firewall.@rule[-1].dest_port="$SMP_PORT" + uci set firewall.@rule[-1].proto='tcp' + uci set firewall.@rule[-1].target='ACCEPT' + uci set firewall.@rule[-1].enabled='1' + fi + fi + + # XFTP port + if [ "$XFTP_ENABLED" = "1" ]; then + if ! uci show firewall 2>/dev/null | grep -q "SimpleX-XFTP"; then + uci add firewall rule + uci set firewall.@rule[-1].name='SimpleX-XFTP' + uci set firewall.@rule[-1].src='wan' + uci set firewall.@rule[-1].dest_port="$XFTP_PORT" + uci set firewall.@rule[-1].proto='tcp' + uci set firewall.@rule[-1].target='ACCEPT' + uci set firewall.@rule[-1].enabled='1' + fi + fi + + uci commit firewall + /etc/init.d/firewall reload 2>/dev/null + + log "Firewall configured" +} + +# ============================================================================ +# HAProxy Integration +# ============================================================================ + +configure_haproxy() { + load_config + + if [ ! -x /usr/sbin/haproxyctl ]; then + warn "HAProxy not installed, skipping integration" + return 0 + fi + + log "Configuring HAProxy..." + + local domain="${TLS_DOMAIN:-simplex.local}" + + # Check if vhost already exists + if uci show haproxy 2>/dev/null | grep -q "\.domain='$domain'"; then + warn "HAProxy vhost for $domain already exists" + return 0 + fi + + # Add SMP backend (TCP mode for TLS passthrough) + uci -q add haproxy backend + uci -q set haproxy.@backend[-1].name='simplex_smp' + uci -q set haproxy.@backend[-1].mode='tcp' + uci -q add_list haproxy.@backend[-1].server="simplex-smp 127.0.0.1:$SMP_PORT check" + + uci commit haproxy + /etc/init.d/haproxy reload 2>/dev/null + + log "HAProxy configured" +} + +# ============================================================================ +# Status Display +# ============================================================================ + +show_status() { + load_config + + echo "" + echo "==========================================" + echo " SimpleX Chat Server Status v$VERSION" + echo "==========================================" + echo "" + + echo "Configuration:" + echo -e " Enabled: $([ "$ENABLED" = "1" ] && echo "${GREEN}Yes${NC}" || echo "${RED}No${NC}")" + echo " Data Path: $DATA_PATH" + echo "" + + # Container status + echo "Container:" + if container_exists; then + if container_running; then + echo -e " Status: ${GREEN}Running${NC}" + else + echo -e " Status: ${YELLOW}Stopped${NC}" + fi + else + echo -e " Status: ${RED}Not installed${NC}" + echo "" + echo "Run 'simplexctl install' to set up the server" + return + fi + echo "" + + # SMP server status + echo "SMP Server:" + echo -e " Enabled: $([ "$SMP_ENABLED" = "1" ] && echo "${GREEN}Yes${NC}" || echo "${RED}No${NC}")" + if [ "$SMP_ENABLED" = "1" ]; then + echo " Port: $SMP_PORT" + echo " Hostname: ${SMP_HOSTNAME:-}" + if pgrep smp-server >/dev/null 2>&1; then + echo -e " Status: ${GREEN}Running${NC}" + else + echo -e " Status: ${RED}Stopped${NC}" + fi + if [ -f "$SMP_DIR/fingerprint" ]; then + echo " Fingerprint: $(cat "$SMP_DIR/fingerprint" | head -c 20)..." + fi + fi + echo "" + + # XFTP server status + echo "XFTP Server:" + echo -e " Enabled: $([ "$XFTP_ENABLED" = "1" ] && echo "${GREEN}Yes${NC}" || echo "${RED}No${NC}")" + if [ "$XFTP_ENABLED" = "1" ]; then + echo " Port: $XFTP_PORT" + echo " Hostname: ${XFTP_HOSTNAME:-}" + echo " Quota: $XFTP_STORAGE_QUOTA" + echo " File Expiry: $XFTP_FILE_EXPIRY" + if pgrep xftp-server >/dev/null 2>&1; then + echo -e " Status: ${GREEN}Running${NC}" + else + echo -e " Status: ${RED}Stopped${NC}" + fi + fi + echo "" + + # Storage usage + if [ -d "$XFTP_DIR/files" ]; then + local storage_used=$(du -sh "$XFTP_DIR/files" 2>/dev/null | cut -f1) + echo "Storage:" + echo " Used: ${storage_used:-0}" + echo " Quota: $XFTP_STORAGE_QUOTA" + fi + echo "" +} + +# ============================================================================ +# Logs +# ============================================================================ + +show_logs() { + local service="${1:-all}" + local lines="${2:-50}" + + case "$service" in + smp) + if [ -f "$SMP_DIR/smp-server-store.log" ]; then + tail -n "$lines" "$SMP_DIR/smp-server-store.log" + else + echo "No SMP logs found" + fi + ;; + xftp) + if [ -f "$XFTP_DIR/xftp-server-store.log" ]; then + tail -n "$lines" "$XFTP_DIR/xftp-server-store.log" + else + echo "No XFTP logs found" + fi + ;; + all) + echo "=== SMP Logs ===" + show_logs smp "$lines" + echo "" + echo "=== XFTP Logs ===" + show_logs xftp "$lines" + ;; + *) + echo "Usage: simplexctl logs [smp|xftp|all] [lines]" + ;; + esac +} + +# ============================================================================ +# Installation +# ============================================================================ + +install_simplex() { + log "Installing SimpleX Chat Server..." + + # Check LXC + if ! command -v lxc-start >/dev/null 2>&1; then + error "LXC is required. Install with: opkg install lxc lxc-common" + return 1 + fi + + load_config + + # Create directories + mkdir -p "$DATA_PATH" "$SMP_DIR" "$XFTP_DIR" "$CERT_PATH" + + # Create LXC container + if ! container_exists; then + create_container || return 1 + fi + + # Start container + start_container || return 1 + + # Run setup inside container + lxc-attach -n "$CONTAINER_NAME" -- /tmp/setup.sh + + # Download binaries + download_binaries || return 1 + + # Generate certificates + local hostname="${TLS_DOMAIN:-$(hostname)}" + if [ "$USE_LETSENCRYPT" = "1" ] && [ -n "$TLS_DOMAIN" ]; then + init_letsencrypt "$TLS_DOMAIN" "$TLS_EMAIL" + else + generate_self_signed_certs "$hostname" + fi + + # Initialize servers + init_smp_server + init_xftp_server + + # Configure firewall + configure_firewall + + # Configure HAProxy if available + configure_haproxy + + log "" + log "SimpleX Chat Server installed successfully!" + log "" + log "Next steps:" + log " 1. Set hostnames: uci set simplex.smp.hostname='smp.example.com'" + log " 2. Commit: uci commit simplex" + log " 3. Enable: uci set simplex.main.enabled=1 && uci commit simplex" + log " 4. Start: /etc/init.d/simplex start" + log "" + log "Get server addresses: simplexctl get-address" + log "" +} + +uninstall_simplex() { + log "Uninstalling SimpleX Chat Server..." + + # Stop services + /etc/init.d/simplex stop 2>/dev/null + + # Stop and destroy container + if container_exists; then + stop_container + log "Removing container..." + lxc-destroy -n "$CONTAINER_NAME" + fi + + # Remove firewall rules + local rules=$(uci show firewall 2>/dev/null | grep "SimpleX-" | cut -d'.' -f2 | cut -d'=' -f1 | sort -ru) + for rule in $rules; do + uci delete firewall.@rule[$rule] + done + uci commit firewall 2>/dev/null + + warn "Data preserved at $DATA_PATH" + warn "Remove manually if desired: rm -rf $DATA_PATH" + + log "Uninstall complete" +} + +# ============================================================================ +# Backup / Restore +# ============================================================================ + +backup() { + local backup_file="${1:-/tmp/simplex-backup-$(date +%Y%m%d-%H%M%S).tar.gz}" + + load_config + log "Creating backup..." + + tar -czf "$backup_file" \ + -C / \ + etc/config/simplex \ + "$SMP_DIR" \ + "$XFTP_DIR" \ + "$CERT_PATH" \ + 2>/dev/null + + if [ -f "$backup_file" ]; then + local size=$(ls -lh "$backup_file" | awk '{print $5}') + log "Backup created: $backup_file ($size)" + else + error "Backup failed" + return 1 + fi +} + +restore() { + local backup_file="$1" + + if [ -z "$backup_file" ] || [ ! -f "$backup_file" ]; then + echo "Usage: simplexctl restore " + return 1 + fi + + log "Restoring from $backup_file..." + + # Stop services + /etc/init.d/simplex stop 2>/dev/null + + # Extract backup + tar -xzf "$backup_file" -C / + + # Restart + /etc/init.d/simplex start + + log "Restore complete" +} + +# ============================================================================ +# Shell Access +# ============================================================================ + +container_shell() { + if ! container_running; then + start_container || return 1 + fi + + log "Entering container shell..." + lxc-attach -n "$CONTAINER_NAME" +} + +# ============================================================================ +# Main +# ============================================================================ + +show_help() { + cat << EOF +SimpleX Chat Server Control v$VERSION + +Usage: simplexctl [options] + +Installation: + install Install SimpleX servers in LXC container + uninstall Remove container (preserves data) + update Update SimpleX binaries to latest version + +Service Control: + start Start SMP and XFTP servers + stop Stop servers + restart Restart servers + status Show service status + +Server Addresses: + get-address Display server addresses for clients + +Certificates: + init-certs Generate self-signed TLS certificates + init-letsencrypt Request Let's Encrypt certificates + +Logs & Debug: + logs [smp|xftp] View server logs + shell Access container shell + +Backup: + backup [file] Create configuration backup + restore Restore from backup + +Configuration: + configure-fw Configure firewall rules + configure-haproxy Add HAProxy integration + reload-config Reload configuration + +Internal (used by init script): + service-run Run service in foreground + start-container Start LXC container + stop-container Stop LXC container + stop-services Stop server processes + +Examples: + simplexctl install + simplexctl status + simplexctl get-address + simplexctl logs smp 100 + +EOF +} + +case "$1" in + install) + install_simplex + ;; + uninstall) + uninstall_simplex + ;; + update) + load_config + download_binaries + ;; + start) + start_container + /etc/init.d/simplex start + ;; + stop) + /etc/init.d/simplex stop + ;; + restart) + /etc/init.d/simplex restart + ;; + status) + show_status + ;; + get-address|get-addresses) + show_addresses + ;; + init-certs) + load_config + generate_self_signed_certs "${2:-$TLS_DOMAIN}" + ;; + init-letsencrypt) + load_config + init_letsencrypt "${2:-$TLS_DOMAIN}" "${3:-$TLS_EMAIL}" + ;; + logs) + show_logs "$2" "$3" + ;; + shell) + container_shell + ;; + backup) + backup "$2" + ;; + restore) + restore "$2" + ;; + configure-fw) + configure_firewall + ;; + configure-haproxy) + configure_haproxy + ;; + reload-config) + reload_config + ;; + service-run) + service_run "$2" + ;; + start-container) + start_container + ;; + stop-container) + stop_container + ;; + stop-services) + stop_services + ;; + -h|--help|help) + show_help + ;; + *) + show_help + exit 1 + ;; +esac + +exit 0