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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-02 12:53:41 +01:00
parent ec31cdba12
commit db847ba1cd
15 changed files with 3241 additions and 74 deletions

View File

@ -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 <contact@cybermind.fr>
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))

View File

@ -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 = '<div class="simplex-status" style="display:flex;gap:30px;flex-wrap:wrap;align-items:flex-start;">';
// LXC/Container status
html += '<div class="status-card" style="min-width:200px;">';
html += '<h4 style="margin:0 0 10px 0;border-bottom:1px solid #ddd;padding-bottom:5px;">Container</h4>';
if (!status.lxc_available) {
html += '<p style="color:#c00;"><b>LXC not installed</b></p>';
html += '<p><small>Install: opkg install lxc lxc-common</small></p>';
} else if (!status.container_exists) {
html += '<p style="color:#888;"><b>Not installed</b></p>';
html += '<p><small>Click "Install" below</small></p>';
} else {
var containerColor = status.container_status === 'running' ? '#080' : '#c60';
html += '<p><b>Status:</b> <span style="color:' + containerColor + '">' + status.container_status + '</span></p>';
}
html += '</div>';
// SMP Server status
if (status.smp_enabled) {
html += '<div class="status-card" style="min-width:200px;">';
html += '<h4 style="margin:0 0 10px 0;border-bottom:1px solid #ddd;padding-bottom:5px;">SMP Server</h4>';
var smpColor = status.smp_status === 'running' ? '#080' : '#c00';
html += '<p><b>Status:</b> <span style="color:' + smpColor + '">' + status.smp_status + '</span></p>';
html += '<p><b>Port:</b> ' + status.smp_port + '/tcp</p>';
if (status.smp_hostname) {
html += '<p><b>Host:</b> ' + status.smp_hostname + '</p>';
}
html += '</div>';
}
// XFTP Server status
if (status.xftp_enabled) {
html += '<div class="status-card" style="min-width:200px;">';
html += '<h4 style="margin:0 0 10px 0;border-bottom:1px solid #ddd;padding-bottom:5px;">XFTP Server</h4>';
var xftpColor = status.xftp_status === 'running' ? '#080' : '#c00';
html += '<p><b>Status:</b> <span style="color:' + xftpColor + '">' + status.xftp_status + '</span></p>';
html += '<p><b>Port:</b> ' + status.xftp_port + '/tcp</p>';
if (status.xftp_hostname) {
html += '<p><b>Host:</b> ' + status.xftp_hostname + '</p>';
}
html += '</div>';
}
// Storage stats
if (stats) {
html += '<div class="status-card" style="min-width:200px;">';
html += '<h4 style="margin:0 0 10px 0;border-bottom:1px solid #ddd;padding-bottom:5px;">Storage</h4>';
html += '<p><b>Used:</b> ' + formatBytes(stats.storage_used || 0) + '</p>';
html += '<p><b>Quota:</b> ' + formatBytes(stats.storage_quota || 0) + '</p>';
html += '<p><b>Files:</b> ' + (stats.file_count || 0) + '</p>';
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 += '<div style="background:#ddd;border-radius:3px;height:8px;margin-top:5px;">';
html += '<div style="background:' + barColor + ';width:' + Math.min(pct, 100) + '%;height:100%;border-radius:3px;"></div>';
html += '</div>';
html += '<small>' + pct + '% used</small>';
}
html += '</div>';
}
html += '</div>';
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 = '<div class="simplex-addresses" style="background:#f8f8f8;padding:15px;border-radius:5px;">';
if (!addresses.smp_address && !addresses.xftp_address) {
html += '<p style="color:#888;">Server addresses will appear here after installation and configuration.</p>';
html += '<p><small>Set hostnames in the configuration below, then restart the service.</small></p>';
} else {
html += '<p style="margin-bottom:10px;"><small>Add these addresses to your SimpleX Chat app under Settings &gt; Network &amp; Servers</small></p>';
if (addresses.smp_address) {
html += '<div style="margin-bottom:15px;">';
html += '<label style="font-weight:bold;display:block;margin-bottom:5px;">SMP Server (messaging):</label>';
html += '<div style="display:flex;gap:10px;align-items:center;">';
html += '<code style="flex:1;background:#fff;padding:8px;border:1px solid #ddd;border-radius:3px;word-break:break-all;font-size:12px;">' + addresses.smp_address + '</code>';
html += '<button class="cbi-button" onclick="copyToClipboard(\'' + addresses.smp_address + '\')">Copy</button>';
html += '</div>';
html += '</div>';
}
if (addresses.xftp_address) {
html += '<div>';
html += '<label style="font-weight:bold;display:block;margin-bottom:5px;">XFTP Server (file transfer):</label>';
html += '<div style="display:flex;gap:10px;align-items:center;">';
html += '<code style="flex:1;background:#fff;padding:8px;border:1px solid #ddd;border-radius:3px;word-break:break-all;font-size:12px;">' + addresses.xftp_address + '</code>';
html += '<button class="cbi-button" onclick="copyToClipboard(\'' + addresses.xftp_address + '\')">Copy</button>';
html += '</div>';
html += '</div>';
}
}
html += '</div>';
// Add copyToClipboard function to window
html += '<script>window.copyToClipboard = ' + copyToClipboard.toString() + '</script>';
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 '<div style="background:#f5f5f5;padding:15px;border-radius:5px;">' +
'<h4>What is SimpleX?</h4>' +
'<p>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.</p>' +
'<h4>Server Types:</h4>' +
'<ul>' +
'<li><b>SMP Server</b> - Message relay using Simple Messaging Protocol</li>' +
'<li><b>XFTP Server</b> - Encrypted file transfer storage</li>' +
'</ul>' +
'<h4>Setup Steps:</h4>' +
'<ol>' +
'<li>Set hostnames for SMP and XFTP servers above</li>' +
'<li>Click "Install Servers" to download binaries and create container</li>' +
'<li>Enable the service and save configuration</li>' +
'<li>Open firewall ports (5223 for SMP, 443 for XFTP)</li>' +
'<li>Add server addresses to your SimpleX Chat mobile app</li>' +
'</ol>' +
'<h4>Resources:</h4>' +
'<ul>' +
'<li><a href="https://simplex.chat" target="_blank">SimpleX Chat Website</a></li>' +
'<li><a href="https://github.com/simplex-chat/simplexmq" target="_blank">SimpleX Server Documentation</a></li>' +
'</ul>' +
'<h4>CLI Commands:</h4>' +
'<code style="display:block;background:#fff;padding:10px;margin-top:10px;">' +
'simplexctl status # Show server status<br>' +
'simplexctl get-address # Show server addresses<br>' +
'simplexctl logs smp # View SMP logs<br>' +
'simplexctl shell # Access container shell' +
'</code>' +
'</div>';
};
return m.render();
}
});

View File

@ -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

View File

@ -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}
}
}
}

View File

@ -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"]
}
}
}

View File

@ -1,12 +1,12 @@
{
"feed_url": "/secubox-feed",
"generated": "2026-02-02T11:00:39+01:00",
"generated": "2026-02-02T12:51:55+01:00",
"packages": [
{
"name": "luci-app-auth-guardian",
"version": "0.4.0-r3",
"filename": "luci-app-auth-guardian_0.4.0-r3_all.ipk",
"size": 11737,
"size": 11736,
"category": "security",
"icon": "key",
"description": "Authentication management",
@ -18,7 +18,7 @@
"name": "luci-app-bandwidth-manager",
"version": "0.5.0-r2",
"filename": "luci-app-bandwidth-manager_0.5.0-r2_all.ipk",
"size": 61540,
"size": 61538,
"category": "network",
"icon": "activity",
"description": "Bandwidth monitoring and control",
@ -30,7 +30,7 @@
"name": "luci-app-cdn-cache",
"version": "0.5.0-r3",
"filename": "luci-app-cdn-cache_0.5.0-r3_all.ipk",
"size": 23184,
"size": 23183,
"category": "network",
"icon": "globe",
"description": "CDN caching",
@ -42,7 +42,7 @@
"name": "luci-app-client-guardian",
"version": "0.4.0-r7",
"filename": "luci-app-client-guardian_0.4.0-r7_all.ipk",
"size": 54534,
"size": 54537,
"category": "network",
"icon": "users",
"description": "Client management and monitoring",
@ -54,7 +54,7 @@
"name": "luci-app-crowdsec-dashboard",
"version": "0.7.0-r32",
"filename": "luci-app-crowdsec-dashboard_0.7.0-r32_all.ipk",
"size": 32630,
"size": 32627,
"category": "security",
"icon": "shield",
"description": "CrowdSec security monitoring",
@ -66,7 +66,7 @@
"name": "luci-app-cyberfeed",
"version": "0.1.1-r1",
"filename": "luci-app-cyberfeed_0.1.1-r1_all.ipk",
"size": 12838,
"size": 12839,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -78,7 +78,7 @@
"name": "luci-app-dnsguard",
"version": "1.0.0-r1",
"filename": "luci-app-dnsguard_1.0.0-r1_all.ipk",
"size": 7546,
"size": 7551,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -90,7 +90,7 @@
"name": "luci-app-exposure",
"version": "1.0.0-r3",
"filename": "luci-app-exposure_1.0.0-r3_all.ipk",
"size": 20536,
"size": 20533,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -114,7 +114,7 @@
"name": "luci-app-glances",
"version": "1.0.0-r2",
"filename": "luci-app-glances_1.0.0-r2_all.ipk",
"size": 6962,
"size": 6968,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -126,7 +126,7 @@
"name": "luci-app-haproxy",
"version": "1.0.0-r8",
"filename": "luci-app-haproxy_1.0.0-r8_all.ipk",
"size": 34557,
"size": 34561,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -138,7 +138,7 @@
"name": "luci-app-hexojs",
"version": "1.0.0-r3",
"filename": "luci-app-hexojs_1.0.0-r3_all.ipk",
"size": 30303,
"size": 30308,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -150,7 +150,7 @@
"name": "luci-app-jitsi",
"version": "1.0.0-r1",
"filename": "luci-app-jitsi_1.0.0-r1_all.ipk",
"size": 5139,
"size": 5141,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -162,7 +162,7 @@
"name": "luci-app-ksm-manager",
"version": "0.4.0-r2",
"filename": "luci-app-ksm-manager_0.4.0-r2_all.ipk",
"size": 18718,
"size": 18724,
"category": "system",
"icon": "cpu",
"description": "Kernel memory management",
@ -174,7 +174,7 @@
"name": "luci-app-localai",
"version": "0.1.0-r15",
"filename": "luci-app-localai_0.1.0-r15_all.ipk",
"size": 13181,
"size": 13183,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -198,7 +198,7 @@
"name": "luci-app-magicmirror2",
"version": "0.4.0-r6",
"filename": "luci-app-magicmirror2_0.4.0-r6_all.ipk",
"size": 12277,
"size": 12279,
"category": "iot",
"icon": "monitor",
"description": "Smart mirror display",
@ -210,7 +210,7 @@
"name": "luci-app-mailinabox",
"version": "1.0.0-r1",
"filename": "luci-app-mailinabox_1.0.0-r1_all.ipk",
"size": 5484,
"size": 5481,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -222,7 +222,7 @@
"name": "luci-app-media-flow",
"version": "0.6.4-r1",
"filename": "luci-app-media-flow_0.6.4-r1_all.ipk",
"size": 25414,
"size": 25420,
"category": "media",
"icon": "film",
"description": "Media streaming",
@ -234,7 +234,7 @@
"name": "luci-app-metablogizer",
"version": "1.0.0-r5",
"filename": "luci-app-metablogizer_1.0.0-r5_all.ipk",
"size": 23343,
"size": 23348,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -246,7 +246,7 @@
"name": "luci-app-metabolizer",
"version": "1.0.0-r2",
"filename": "luci-app-metabolizer_1.0.0-r2_all.ipk",
"size": 4759,
"size": 4757,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -258,7 +258,7 @@
"name": "luci-app-mitmproxy",
"version": "0.5.0-r2",
"filename": "luci-app-mitmproxy_0.5.0-r2_all.ipk",
"size": 10520,
"size": 10527,
"category": "security",
"icon": "lock",
"description": "HTTPS proxy and traffic inspection",
@ -270,7 +270,7 @@
"name": "luci-app-mmpm",
"version": "0.2.0-r3",
"filename": "luci-app-mmpm_0.2.0-r3_all.ipk",
"size": 7901,
"size": 7904,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -282,7 +282,7 @@
"name": "luci-app-mqtt-bridge",
"version": "0.4.0-r4",
"filename": "luci-app-mqtt-bridge_0.4.0-r4_all.ipk",
"size": 22775,
"size": 22778,
"category": "iot",
"icon": "message-square",
"description": "MQTT bridge",
@ -294,7 +294,7 @@
"name": "luci-app-ndpid",
"version": "1.1.2-r2",
"filename": "luci-app-ndpid_1.1.2-r2_all.ipk",
"size": 22651,
"size": 22652,
"category": "security",
"icon": "eye",
"description": "Deep packet inspection",
@ -306,7 +306,7 @@
"name": "luci-app-netdata-dashboard",
"version": "0.5.0-r2",
"filename": "luci-app-netdata-dashboard_0.5.0-r2_all.ipk",
"size": 20483,
"size": 20490,
"category": "monitoring",
"icon": "bar-chart-2",
"description": "System monitoring dashboard",
@ -318,7 +318,7 @@
"name": "luci-app-network-modes",
"version": "0.5.0-r3",
"filename": "luci-app-network-modes_0.5.0-r3_all.ipk",
"size": 54147,
"size": 54150,
"category": "network",
"icon": "wifi",
"description": "Network configuration",
@ -330,7 +330,7 @@
"name": "luci-app-network-tweaks",
"version": "1.0.0-r7",
"filename": "luci-app-network-tweaks_1.0.0-r7_all.ipk",
"size": 14955,
"size": 14966,
"category": "network",
"icon": "wifi",
"description": "Network configuration",
@ -342,7 +342,7 @@
"name": "luci-app-nextcloud",
"version": "1.0.0-r1",
"filename": "luci-app-nextcloud_1.0.0-r1_all.ipk",
"size": 6486,
"size": 6487,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -354,7 +354,7 @@
"name": "luci-app-ollama",
"version": "0.1.0-r1",
"filename": "luci-app-ollama_0.1.0-r1_all.ipk",
"size": 12349,
"size": 12351,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -366,7 +366,7 @@
"name": "luci-app-picobrew",
"version": "1.0.0-r1",
"filename": "luci-app-picobrew_1.0.0-r1_all.ipk",
"size": 9455,
"size": 9456,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -378,7 +378,7 @@
"name": "luci-app-secubox",
"version": "0.7.1-r4",
"filename": "luci-app-secubox_0.7.1-r4_all.ipk",
"size": 77677,
"size": 77680,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -390,7 +390,7 @@
"name": "luci-app-secubox-admin",
"version": "1.0.0-r19",
"filename": "luci-app-secubox-admin_1.0.0-r19_all.ipk",
"size": 57246,
"size": 57247,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -402,7 +402,7 @@
"name": "luci-app-secubox-crowdsec",
"version": "1.0.0-r3",
"filename": "luci-app-secubox-crowdsec_1.0.0-r3_all.ipk",
"size": 13914,
"size": 13922,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -414,7 +414,7 @@
"name": "luci-app-secubox-netdiag",
"version": "1.0.0-r1",
"filename": "luci-app-secubox-netdiag_1.0.0-r1_all.ipk",
"size": 15307,
"size": 15306,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -426,7 +426,7 @@
"name": "luci-app-secubox-netifyd",
"version": "1.2.1-r1",
"filename": "luci-app-secubox-netifyd_1.2.1-r1_all.ipk",
"size": 36538,
"size": 36545,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -450,7 +450,7 @@
"name": "luci-app-secubox-portal",
"version": "0.7.0-r2",
"filename": "luci-app-secubox-portal_0.7.0-r2_all.ipk",
"size": 24643,
"size": 24644,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -462,7 +462,7 @@
"name": "luci-app-secubox-security-threats",
"version": "1.0.0-r4",
"filename": "luci-app-secubox-security-threats_1.0.0-r4_all.ipk",
"size": 21311,
"size": 21314,
"category": "system",
"icon": "box",
"description": "SecuBox system component",
@ -474,7 +474,19 @@
"name": "luci-app-service-registry",
"version": "1.0.0-r1",
"filename": "luci-app-service-registry_1.0.0-r1_all.ipk",
"size": 39824,
"size": 39826,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
"installed": false,
"luci_app": null
}
,
{
"name": "luci-app-simplex",
"version": "1.0.0-r1",
"filename": "luci-app-simplex_1.0.0-r1_all.ipk",
"size": 6999,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -486,7 +498,7 @@
"name": "luci-app-streamlit",
"version": "1.0.0-r11",
"filename": "luci-app-streamlit_1.0.0-r11_all.ipk",
"size": 14747,
"size": 14750,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -498,7 +510,7 @@
"name": "luci-app-system-hub",
"version": "0.5.1-r4",
"filename": "luci-app-system-hub_0.5.1-r4_all.ipk",
"size": 61102,
"size": 61103,
"category": "system",
"icon": "settings",
"description": "System management",
@ -510,7 +522,7 @@
"name": "luci-app-tor-shield",
"version": "1.0.0-r10",
"filename": "luci-app-tor-shield_1.0.0-r10_all.ipk",
"size": 22360,
"size": 22363,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -522,7 +534,7 @@
"name": "luci-app-traffic-shaper",
"version": "0.4.0-r2",
"filename": "luci-app-traffic-shaper_0.4.0-r2_all.ipk",
"size": 14532,
"size": 14533,
"category": "network",
"icon": "filter",
"description": "Traffic shaping and QoS",
@ -534,7 +546,7 @@
"name": "luci-app-vhost-manager",
"version": "0.5.0-r5",
"filename": "luci-app-vhost-manager_0.5.0-r5_all.ipk",
"size": 26184,
"size": 26186,
"category": "network",
"icon": "server",
"description": "Virtual host management",
@ -546,7 +558,7 @@
"name": "luci-app-wireguard-dashboard",
"version": "0.7.0-r5",
"filename": "luci-app-wireguard-dashboard_0.7.0-r5_all.ipk",
"size": 39604,
"size": 39608,
"category": "vpn",
"icon": "shield",
"description": "WireGuard VPN dashboard",
@ -558,7 +570,7 @@
"name": "luci-app-zigbee2mqtt",
"version": "1.0.0-r2",
"filename": "luci-app-zigbee2mqtt_1.0.0-r2_all.ipk",
"size": 6810,
"size": 6816,
"category": "iot",
"icon": "radio",
"description": "Zigbee device management",
@ -570,7 +582,7 @@
"name": "luci-theme-secubox",
"version": "0.4.7-r1",
"filename": "luci-theme-secubox_0.4.7-r1_all.ipk",
"size": 110239,
"size": 110242,
"category": "theme",
"icon": "palette",
"description": "LuCI theme",
@ -582,7 +594,7 @@
"name": "secubox-app",
"version": "1.0.0-r2",
"filename": "secubox-app_1.0.0-r2_all.ipk",
"size": 11182,
"size": 11186,
"category": "utility",
"icon": "package",
"description": "SecuBox package",
@ -594,7 +606,7 @@
"name": "secubox-app-adguardhome",
"version": "1.0.0-r2",
"filename": "secubox-app-adguardhome_1.0.0-r2_all.ipk",
"size": 2877,
"size": 2884,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -618,7 +630,7 @@
"name": "secubox-app-crowdsec-custom",
"version": "1.1.0-r1",
"filename": "secubox-app-crowdsec-custom_1.1.0-r1_all.ipk",
"size": 5761,
"size": 5762,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -630,7 +642,7 @@
"name": "secubox-app-cs-firewall-bouncer",
"version": "0.0.31-r4_aarch64",
"filename": "secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk",
"size": 5049325,
"size": 5049323,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -642,7 +654,7 @@
"name": "secubox-app-cyberfeed",
"version": "0.2.1-r1",
"filename": "secubox-app-cyberfeed_0.2.1-r1_all.ipk",
"size": 12446,
"size": 12452,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -654,7 +666,7 @@
"name": "secubox-app-domoticz",
"version": "1.0.0-r2",
"filename": "secubox-app-domoticz_1.0.0-r2_all.ipk",
"size": 2546,
"size": 2547,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -666,7 +678,7 @@
"name": "secubox-app-exposure",
"version": "1.0.0-r1",
"filename": "secubox-app-exposure_1.0.0-r1_all.ipk",
"size": 6933,
"size": 6936,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -678,7 +690,7 @@
"name": "secubox-app-gitea",
"version": "1.0.0-r5",
"filename": "secubox-app-gitea_1.0.0-r5_all.ipk",
"size": 9400,
"size": 9407,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -690,7 +702,7 @@
"name": "secubox-app-glances",
"version": "1.0.0-r1",
"filename": "secubox-app-glances_1.0.0-r1_all.ipk",
"size": 5535,
"size": 5536,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -702,7 +714,7 @@
"name": "secubox-app-haproxy",
"version": "1.0.0-r23",
"filename": "secubox-app-haproxy_1.0.0-r23_all.ipk",
"size": 15676,
"size": 15682,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -714,7 +726,7 @@
"name": "secubox-app-hexojs",
"version": "1.0.0-r8",
"filename": "secubox-app-hexojs_1.0.0-r8_all.ipk",
"size": 94926,
"size": 94939,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -726,7 +738,7 @@
"name": "secubox-app-jitsi",
"version": "1.0.0-r1",
"filename": "secubox-app-jitsi_1.0.0-r1_all.ipk",
"size": 8906,
"size": 8911,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -738,7 +750,7 @@
"name": "secubox-app-localai",
"version": "2.25.0-r1",
"filename": "secubox-app-localai_2.25.0-r1_all.ipk",
"size": 5707,
"size": 5724,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -750,7 +762,7 @@
"name": "secubox-app-localai-wb",
"version": "2.25.0-r1",
"filename": "secubox-app-localai-wb_2.25.0-r1_all.ipk",
"size": 7946,
"size": 7953,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -762,7 +774,7 @@
"name": "secubox-app-lyrion",
"version": "2.0.2-r1",
"filename": "secubox-app-lyrion_2.0.2-r1_all.ipk",
"size": 7283,
"size": 7287,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -774,7 +786,7 @@
"name": "secubox-app-magicmirror2",
"version": "0.4.0-r8",
"filename": "secubox-app-magicmirror2_0.4.0-r8_all.ipk",
"size": 9245,
"size": 9254,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -786,7 +798,7 @@
"name": "secubox-app-mailinabox",
"version": "2.0.0-r1",
"filename": "secubox-app-mailinabox_2.0.0-r1_all.ipk",
"size": 7563,
"size": 7572,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -798,7 +810,7 @@
"name": "secubox-app-metabolizer",
"version": "1.0.0-r3",
"filename": "secubox-app-metabolizer_1.0.0-r3_all.ipk",
"size": 13971,
"size": 13979,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -810,7 +822,7 @@
"name": "secubox-app-mitmproxy",
"version": "0.5.0-r19",
"filename": "secubox-app-mitmproxy_0.5.0-r19_all.ipk",
"size": 22950,
"size": 22956,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -834,7 +846,7 @@
"name": "secubox-app-nextcloud",
"version": "1.0.0-r2",
"filename": "secubox-app-nextcloud_1.0.0-r2_all.ipk",
"size": 2943,
"size": 2963,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -846,7 +858,7 @@
"name": "secubox-app-ollama",
"version": "0.1.0-r1",
"filename": "secubox-app-ollama_0.1.0-r1_all.ipk",
"size": 5731,
"size": 5739,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -858,19 +870,31 @@
"name": "secubox-app-picobrew",
"version": "1.0.0-r7",
"filename": "secubox-app-picobrew_1.0.0-r7_all.ipk",
"size": 5537,
"size": 5539,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
"installed": false,
"luci_app": "luci-app-picobrew"
}
,
{
"name": "secubox-app-simplex",
"version": "1.0.0-r1",
"filename": "secubox-app-simplex_1.0.0-r1_all.ipk",
"size": 9235,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
"installed": false,
"luci_app": "luci-app-simplex"
}
,
{
"name": "secubox-app-streamlit",
"version": "1.0.0-r5",
"filename": "secubox-app-streamlit_1.0.0-r5_all.ipk",
"size": 11716,
"size": 11725,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -882,7 +906,7 @@
"name": "secubox-app-tor",
"version": "1.0.0-r1",
"filename": "secubox-app-tor_1.0.0-r1_all.ipk",
"size": 7360,
"size": 7369,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -894,7 +918,7 @@
"name": "secubox-app-webapp",
"version": "1.5.0-r7",
"filename": "secubox-app-webapp_1.5.0-r7_all.ipk",
"size": 39169,
"size": 39175,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -906,7 +930,7 @@
"name": "secubox-app-zigbee2mqtt",
"version": "1.0.0-r3",
"filename": "secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk",
"size": 3537,
"size": 3542,
"category": "secubox",
"icon": "package",
"description": "SecuBox backend service",
@ -918,7 +942,7 @@
"name": "secubox-core",
"version": "0.10.0-r11",
"filename": "secubox-core_0.10.0-r11_all.ipk",
"size": 87971,
"size": 87975,
"category": "system",
"icon": "box",
"description": "SecuBox core components",
@ -930,7 +954,7 @@
"name": "secubox-p2p",
"version": "0.6.0-r3",
"filename": "secubox-p2p_0.6.0-r3_all.ipk",
"size": 42010,
"size": 42016,
"category": "utility",
"icon": "package",
"description": "SecuBox package",

View File

@ -0,0 +1,83 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-app-simplex
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_ARCH:=all
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
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))

View File

@ -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 ''

View File

@ -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
}

View File

@ -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

View File

@ -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:-<not set>}"
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:-<not set>}"
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 <backup_file>"
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 <command> [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 <file> 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 <svc> 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