secubox-openwrt/package/secubox/secubox-app-matrix/files/usr/sbin/matrixctl
CyberMind-FR b6747c197e feat(security): Add instant ban feature and user management
- Add enhanced instant ban for critical threats (SQL injection, CVE exploits, RCE)
  - CrowdSec trigger scenario for single-hit bans on severity=critical
  - Instant ban daemon (10s polling) for rapid response
  - UCI options: instant_ban_enabled, instant_ban_duration (48h default)
  - WAF addon updated to route critical threats to instant-ban.log

- Add centralized user management (secubox-core-users, luci-app-secubox-users)
  - CLI tool: secubox-users add/del/passwd/list/sync/status
  - LuCI dashboard under System > SecuBox Users
  - Unified user provisioning across Nextcloud, PeerTube, Matrix, Jabber, Email

- Add Matrix/Conduit integration (secubox-app-matrix, luci-app-matrix)
  - LXC-based Conduit homeserver deployment
  - Full RPCD handler with user/room management
  - HAProxy integration for federation

- Add provision-users.sh script for bulk user creation
- Update secubox-feed with new IPKs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-19 20:17:28 +01:00

1279 lines
31 KiB
Bash

#!/bin/sh
# SecuBox Matrix Manager - LXC Debian container with Conduit Matrix Server
CONFIG="matrix"
LXC_NAME="matrix"
LXC_PATH="/srv/lxc"
LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs"
LXC_CONF="$LXC_PATH/$LXC_NAME/config"
DATA_PATH_DEFAULT="/srv/matrix"
CONDUIT_VERSION="0.8.0"
OPKG_UPDATED=0
usage() {
cat <<'USAGE'
Usage: matrixctl <command>
Installation:
install Create LXC container with Conduit Matrix server
uninstall Remove container (preserves data)
update Update Conduit to latest version
check Run prerequisite checks
Service:
start Start Matrix server (via init)
stop Stop Matrix server
restart Restart Matrix server
status Show container and service status
logs [N] Show last N lines of logs (default: 50)
shell Open interactive shell in container
Users:
user add <mxid> [password] Create user (e.g. @user:server)
user del <mxid> Delete/deactivate user
user passwd <mxid> [password] Change password
user list List all users
Rooms:
room list List all rooms
room create <alias> Create room (via admin API)
room delete <room_id> Delete room
Federation:
federation test <server> Test federation with server
federation status Show federation status
Exposure:
configure-haproxy Setup HAProxy vhost for HTTPS
emancipate <domain> Full exposure (HAProxy + ACME + .well-known)
Identity:
identity link <mxid> Link Matrix user to node DID
identity unlink <mxid> Unlink Matrix user from DID
identity status Show identity linkage
Mesh:
mesh publish Publish to P2P service registry
mesh unpublish Remove from P2P service registry
Backup:
backup [path] Backup database and config
restore <path> Restore from backup
Internal:
service-run Run container via procd
service-stop Stop container
USAGE
}
# ---------- helpers ----------
require_root() { [ "$(id -u)" -eq 0 ]; }
uci_get() {
local key="$1"
local section="${2:-main}"
uci -q get ${CONFIG}.${section}.$key
}
uci_set() {
local key="$1"
local value="$2"
local section="${3:-main}"
uci set ${CONFIG}.${section}.$key="$value"
}
log_info() { echo "[INFO] $*"; logger -t matrixctl "$*"; }
log_warn() { echo "[WARN] $*"; logger -t matrixctl -p warning "$*"; }
log_error() { echo "[ERROR] $*" >&2; logger -t matrixctl -p err "$*"; }
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
ensure_packages() {
for pkg in "$@"; do
if ! opkg status "$pkg" 2>/dev/null | grep -q "Status:.*installed"; then
if [ "$OPKG_UPDATED" -eq 0 ]; then
opkg update || return 1
OPKG_UPDATED=1
fi
opkg install "$pkg" || return 1
fi
done
}
defaults() {
data_path="$(uci_get data_path || echo $DATA_PATH_DEFAULT)"
memory_limit="$(uci_get memory_limit || echo 512)"
hostname="$(uci_get hostname server || echo matrix.local)"
port="$(uci_get port server || echo 8448)"
http_port="$(uci_get http_port server || echo 8008)"
registration_enabled="$(uci_get registration_enabled server || echo 0)"
federation_enabled="$(uci_get enabled federation || echo 1)"
}
detect_arch() {
case "$(uname -m)" in
aarch64) echo "aarch64" ;;
armv7l) echo "armv7" ;;
x86_64) echo "x86_64" ;;
*) echo "x86_64" ;;
esac
}
generate_password() {
head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16
}
# ---------- LXC helpers ----------
lxc_running() {
lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING"
}
lxc_exists() {
[ -f "$LXC_CONF" ] && [ -d "$LXC_ROOTFS" ]
}
lxc_exec() {
lxc-attach -n "$LXC_NAME" -- "$@"
}
lxc_stop() {
if lxc_running; then
lxc-stop -n "$LXC_NAME" -k 2>/dev/null || true
sleep 2
fi
}
# ---------- Conduit admin API ----------
conduit_api() {
local endpoint="$1"
local method="${2:-GET}"
local data="$3"
local url="http://127.0.0.1:${http_port}/_matrix/client/v3${endpoint}"
if [ -n "$data" ]; then
wget -q -O - --method="$method" \
--header="Content-Type: application/json" \
--body-data="$data" "$url" 2>/dev/null
else
wget -q -O - "$url" 2>/dev/null
fi
}
# ---------- rootfs creation ----------
lxc_create_rootfs() {
local arch=$(detect_arch)
# Map to Debian architecture names
local debian_arch
case "$arch" in
aarch64) debian_arch="arm64" ;;
armv7) debian_arch="armhf" ;;
x86_64) debian_arch="amd64" ;;
*) debian_arch="amd64" ;;
esac
ensure_dir "$LXC_ROOTFS"
# Minimal Debian rootfs via tarball from LXC image server
local rootfs_url="https://images.linuxcontainers.org/images/debian/bookworm/${debian_arch}/default/"
log_info "Downloading Debian bookworm rootfs for ${debian_arch}..."
# Get latest build directory
local latest_path
latest_path=$(wget -q -O - "$rootfs_url" 2>/dev/null | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | tail -1)
if [ -z "$latest_path" ]; then
log_error "Failed to find latest Debian rootfs build"
return 1
fi
local tarball="/tmp/debian-matrix.tar.xz"
local tarball_url="${rootfs_url}${latest_path}/rootfs.tar.xz"
wget -q -O "$tarball" "$tarball_url" || {
log_error "Failed to download Debian rootfs from $tarball_url"
return 1
}
tar -xJf "$tarball" -C "$LXC_ROOTFS" || {
log_error "Failed to extract Debian rootfs"
return 1
}
rm -f "$tarball"
# DNS
cp /etc/resolv.conf "$LXC_ROOTFS/etc/resolv.conf" 2>/dev/null || \
echo "nameserver 8.8.8.8" > "$LXC_ROOTFS/etc/resolv.conf"
# Create minimal /dev for chroot operations
mkdir -p "$LXC_ROOTFS/dev"
[ -c "$LXC_ROOTFS/dev/null" ] || mknod -m 666 "$LXC_ROOTFS/dev/null" c 1 3 2>/dev/null
[ -c "$LXC_ROOTFS/dev/zero" ] || mknod -m 666 "$LXC_ROOTFS/dev/zero" c 1 5 2>/dev/null
[ -c "$LXC_ROOTFS/dev/random" ] || mknod -m 666 "$LXC_ROOTFS/dev/random" c 1 8 2>/dev/null
[ -c "$LXC_ROOTFS/dev/urandom" ] || mknod -m 666 "$LXC_ROOTFS/dev/urandom" c 1 9 2>/dev/null
# Configure apt sources
cat > "$LXC_ROOTFS/etc/apt/sources.list" <<'SOURCES'
deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
SOURCES
# Install minimal packages
log_info "Installing base packages..."
chroot "$LXC_ROOTFS" /bin/sh -c "
export DEBIAN_FRONTEND=noninteractive
apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
openssl \
procps \
curl
" || {
log_error "Failed to install base packages"
return 1
}
# Download Conduit binary
download_conduit_binary || return 1
# Create directories
mkdir -p "$LXC_ROOTFS/var/lib/conduit"
mkdir -p "$LXC_ROOTFS/etc/conduit"
# Create startup script
create_startup_script
# Clean up apt cache
chroot "$LXC_ROOTFS" /bin/sh -c "
apt-get clean
rm -rf /var/lib/apt/lists/*
"
log_info "Rootfs created successfully"
}
download_conduit_binary() {
local arch=$(detect_arch)
local conduit_arch
case "$arch" in
aarch64) conduit_arch="aarch64-unknown-linux-musl" ;;
x86_64) conduit_arch="x86_64-unknown-linux-musl" ;;
*)
log_error "Unsupported architecture: $arch"
return 1
;;
esac
log_info "Downloading Conduit Matrix server for ${arch}..."
# Try GitLab CI artifacts first (static binary)
local artifact_url="https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/master/raw/${conduit_arch}?job=artifacts"
wget -q -O "$LXC_ROOTFS/usr/local/bin/conduit" "$artifact_url" || {
# Fallback: try GitHub releases
log_warn "GitLab artifact not available, trying GitHub..."
local github_url="https://github.com/famedly/conduit/releases/latest/download/conduit-${conduit_arch}"
wget -q -O "$LXC_ROOTFS/usr/local/bin/conduit" "$github_url" || {
log_error "Failed to download Conduit binary"
return 1
}
}
chmod +x "$LXC_ROOTFS/usr/local/bin/conduit"
# Verify binary works
if ! chroot "$LXC_ROOTFS" /usr/local/bin/conduit --version >/dev/null 2>&1; then
log_warn "Binary verification failed, might need different build"
fi
log_info "Conduit binary installed"
}
create_startup_script() {
defaults
cat > "$LXC_ROOTFS/opt/start-matrix.sh" <<'STARTUP'
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# Get config from environment
MATRIX_SERVER_NAME="${MATRIX_SERVER_NAME:-matrix.local}"
MATRIX_PORT="${MATRIX_PORT:-8008}"
MATRIX_FEDERATION_PORT="${MATRIX_FEDERATION_PORT:-8448}"
MATRIX_REGISTRATION="${MATRIX_REGISTRATION:-false}"
MATRIX_FEDERATION="${MATRIX_FEDERATION:-true}"
MATRIX_DB_CACHE="${MATRIX_DB_CACHE:-300}"
CONDUIT_CONFIG="/etc/conduit/conduit.toml"
DATA_DIR="/var/lib/conduit"
# Generate conduit.toml if not exists or server_name changed
generate_config() {
cat > "$CONDUIT_CONFIG" << EOF
[global]
# Server name (domain)
server_name = "${MATRIX_SERVER_NAME}"
# Database
database_backend = "rocksdb"
database_path = "/var/lib/conduit"
# Network - listen on all interfaces
port = ${MATRIX_PORT}
address = "0.0.0.0"
# Limits
max_request_size = 20_000_000
max_concurrent_requests = 100
db_cache_capacity_mb = ${MATRIX_DB_CACHE}
# Registration
allow_registration = ${MATRIX_REGISTRATION}
registration_token = "${MATRIX_REGISTRATION_TOKEN:-}"
# Federation
allow_federation = ${MATRIX_FEDERATION}
trusted_servers = ["matrix.org"]
# Logging
log = "info"
# .well-known (Conduit can serve these)
[global.well_known]
client = "https://${MATRIX_SERVER_NAME}/"
server = "${MATRIX_SERVER_NAME}:443"
EOF
}
# Check if config needs regeneration
if [ ! -f "$CONDUIT_CONFIG" ]; then
echo "[MATRIX] Generating initial configuration..."
generate_config
elif [ -f "/tmp/matrix_regenerate_config" ]; then
echo "[MATRIX] Regenerating configuration..."
generate_config
rm -f /tmp/matrix_regenerate_config
fi
# Ensure data directory exists and has correct permissions
mkdir -p "$DATA_DIR"
chmod 700 "$DATA_DIR"
echo "[MATRIX] Starting Conduit server..."
echo "[MATRIX] Server name: ${MATRIX_SERVER_NAME}"
echo "[MATRIX] Client port: ${MATRIX_PORT}"
echo "[MATRIX] Federation: ${MATRIX_FEDERATION}"
# Run Conduit
export CONDUIT_CONFIG
exec /usr/local/bin/conduit
STARTUP
chmod +x "$LXC_ROOTFS/opt/start-matrix.sh"
}
lxc_create_config() {
defaults
local mem_bytes=$((memory_limit * 1024 * 1024))
ensure_dir "$LXC_PATH/$LXC_NAME"
ensure_dir "$data_path"
ensure_dir "$data_path/data"
ensure_dir "$data_path/config"
cat > "$LXC_CONF" <<EOF
# Matrix LXC Container (Conduit)
lxc.uts.name = $LXC_NAME
lxc.rootfs.path = dir:$LXC_ROOTFS
lxc.arch = $(detect_arch)
# Network: share host network
lxc.net.0.type = none
# Auto-mounts
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
# Bind mounts for persistent data
lxc.mount.entry = $data_path/data var/lib/conduit none bind,create=dir 0 0
lxc.mount.entry = $data_path/config etc/conduit none bind,create=dir 0 0
# Environment
lxc.environment = MATRIX_SERVER_NAME=$hostname
lxc.environment = MATRIX_PORT=$http_port
lxc.environment = MATRIX_FEDERATION_PORT=$port
lxc.environment = MATRIX_REGISTRATION=$([ "$registration_enabled" = "1" ] && echo "true" || echo "false")
lxc.environment = MATRIX_FEDERATION=$([ "$federation_enabled" = "1" ] && echo "true" || echo "false")
lxc.environment = MATRIX_DB_CACHE=$(uci_get cache_capacity database || echo 300)
# Resource limits
lxc.cgroup2.memory.max = $mem_bytes
# Security
lxc.cap.drop = sys_module mac_admin mac_override sys_time
# TTY/PTY for cgroup2
lxc.tty.max = 4
lxc.pty.max = 16
lxc.cgroup2.devices.allow = c 1:3 rwm
lxc.cgroup2.devices.allow = c 1:5 rwm
lxc.cgroup2.devices.allow = c 1:7 rwm
lxc.cgroup2.devices.allow = c 1:8 rwm
lxc.cgroup2.devices.allow = c 1:9 rwm
lxc.cgroup2.devices.allow = c 5:0 rwm
lxc.cgroup2.devices.allow = c 5:1 rwm
lxc.cgroup2.devices.allow = c 5:2 rwm
lxc.cgroup2.devices.allow = c 136:* rwm
# Startup command
lxc.init.cmd = /opt/start-matrix.sh
EOF
log_info "LXC config created"
}
# ---------- commands ----------
cmd_install() {
require_root || { log_error "Must run as root"; return 1; }
log_info "Installing Matrix homeserver (Conduit)..."
# Check prerequisites
ensure_packages lxc lxc-common wget tar || return 1
# Create LXC rootfs
if ! lxc_exists; then
lxc_create_rootfs || return 1
else
log_info "Container already exists, skipping rootfs creation"
fi
# Create LXC config
lxc_create_config
# Generate initial admin password if not set
local admin_pass=$(uci_get initial_password admin)
if [ -z "$admin_pass" ]; then
admin_pass=$(generate_password)
uci_set initial_password "$admin_pass" admin
uci commit "$CONFIG"
fi
# Enable and start
uci_set enabled '1'
uci commit "$CONFIG"
/etc/init.d/matrix enable
/etc/init.d/matrix start
# Wait for container to start
log_info "Waiting for Conduit to start..."
sleep 8
# Create admin user
defaults
local admin_user=$(uci_get initial_user admin || echo admin)
if lxc_running; then
log_info "Creating admin user: @${admin_user}:${hostname}"
cmd_user_add "@${admin_user}:${hostname}" "$admin_pass" 2>/dev/null || true
fi
local lan_ip=$(uci -q get network.lan.ipaddr || echo '192.168.255.1')
log_info ""
log_info "=============================================="
log_info " Matrix Homeserver installed!"
log_info "=============================================="
log_info ""
log_info " Server: $hostname"
log_info " Client API: http://${lan_ip}:$http_port"
log_info " Fed Port: $port"
log_info ""
log_info " Admin User: @${admin_user}:${hostname}"
log_info " Password: $admin_pass"
log_info ""
log_info " Clients: Element (Web/Desktop/Mobile)"
log_info " FluffyChat, Nheko, SchildiChat"
log_info ""
log_info " Homeserver URL: http://${lan_ip}:$http_port"
log_info ""
log_info " Expose externally:"
log_info " matrixctl emancipate matrix.example.com"
log_info ""
}
cmd_uninstall() {
require_root || { log_error "Must run as root"; return 1; }
log_info "Uninstalling Matrix homeserver..."
# Stop and disable
/etc/init.d/matrix stop 2>/dev/null
/etc/init.d/matrix disable 2>/dev/null
lxc_stop
# Remove container but keep data
rm -rf "$LXC_ROOTFS" "$LXC_CONF"
uci_set enabled '0'
uci commit "$CONFIG"
defaults
log_info "Container removed. Data preserved in $data_path"
}
cmd_update() {
require_root || { log_error "Must run as root"; return 1; }
log_info "Updating Conduit..."
if lxc_running; then
lxc_stop
fi
# Re-download Conduit binary
download_conduit_binary || return 1
# Restart
cmd_start
log_info "Conduit updated successfully"
}
cmd_check() {
echo "Matrix Homeserver Prerequisites Check"
echo "======================================"
# LXC
if command -v lxc-start >/dev/null 2>&1; then
echo "[OK] LXC installed"
else
echo "[FAIL] LXC not installed"
fi
# Container exists
if lxc_exists; then
echo "[OK] Container exists"
else
echo "[--] Container not created"
fi
# Container running
if lxc_running; then
echo "[OK] Container running"
else
echo "[--] Container not running"
fi
# Matrix ports
defaults
for p in $http_port; do
if netstat -tln 2>/dev/null | grep -q ":${p} " || \
grep -q ":$(printf '%04X' $p) " /proc/net/tcp 2>/dev/null; then
echo "[OK] Port $p listening"
else
echo "[--] Port $p not listening"
fi
done
# Conduit process
if lxc_running; then
if lxc_exec pgrep conduit >/dev/null 2>&1; then
echo "[OK] Conduit process running"
else
echo "[FAIL] Conduit process not running"
fi
fi
# API check
if wget -q -O /dev/null "http://127.0.0.1:${http_port}/_matrix/client/versions" 2>/dev/null; then
echo "[OK] Matrix API responding"
else
echo "[--] Matrix API not responding"
fi
}
cmd_status() {
defaults
# JSON output for RPCD
if [ "$1" = "--json" ]; then
local running=0
local conduit_proc=0
local container_state="not_installed"
local user_count=0
local version=""
if lxc_exists; then
container_state="stopped"
if lxc_running; then
container_state="running"
running=1
lxc_exec pgrep conduit >/dev/null 2>&1 && conduit_proc=1
version=$(lxc_exec /usr/local/bin/conduit --version 2>/dev/null | head -1)
fi
fi
cat <<EOF
{
"enabled": $(uci_get enabled || echo 0),
"running": $running,
"container_state": "$container_state",
"hostname": "$hostname",
"http_port": $http_port,
"port": $port,
"data_path": "$data_path",
"memory_limit": $memory_limit,
"conduit": $conduit_proc,
"federation_enabled": $(uci_get enabled federation || echo 1),
"registration_enabled": $(uci_get registration_enabled server || echo 0),
"haproxy": $(uci_get haproxy network || echo 0),
"domain": "$(uci_get domain network)",
"did_linked": $(uci_get did_linked identity || echo 0),
"mesh_published": $(uci_get published mesh || echo 0),
"version": "$version"
}
EOF
return
fi
echo "Matrix Homeserver Status"
echo "========================"
echo ""
echo "Configuration:"
echo " Enabled: $(uci_get enabled || echo '0')"
echo " Hostname: $hostname"
echo " Client Port: $http_port"
echo " Fed Port: $port"
echo " Memory: ${memory_limit}M"
echo " Data Path: $data_path"
echo " Federation: $([ "$(uci_get enabled federation)" = "1" ] && echo "enabled" || echo "disabled")"
echo ""
if lxc_exists; then
echo "Container: EXISTS"
else
echo "Container: NOT CREATED (run: matrixctl install)"
return 0
fi
if lxc_running; then
echo "State: RUNNING"
lxc-info -n "$LXC_NAME" | grep -E "PID|Memory" | sed 's/^/ /'
echo ""
echo "Services:"
lxc_exec pgrep conduit >/dev/null 2>&1 && echo " Conduit: UP" || echo " Conduit: DOWN"
# Version
local version=$(lxc_exec /usr/local/bin/conduit --version 2>/dev/null | head -1)
[ -n "$version" ] && echo " Version: $version"
else
echo "State: STOPPED"
fi
echo ""
local lan_ip=$(uci -q get network.lan.ipaddr || echo '192.168.255.1')
echo "Connection:"
echo " Homeserver: http://${lan_ip}:${http_port}"
echo " Client API: http://${lan_ip}:${http_port}/_matrix/client/"
echo " Admin Room: #admins:${hostname}"
}
cmd_logs() {
local lines="${1:-50}"
if lxc_running; then
echo "=== Conduit logs ==="
lxc_exec journalctl -n "$lines" 2>/dev/null || \
lxc_exec tail -n "$lines" /var/log/conduit.log 2>/dev/null || \
echo "No logs found (Conduit logs to stdout)"
else
echo "Container not running"
fi
}
cmd_shell() {
if lxc_running; then
lxc_exec /bin/bash || lxc_exec /bin/sh
else
log_error "Container not running"
return 1
fi
}
cmd_start() {
require_root || { log_error "Must run as root"; return 1; }
/etc/init.d/matrix start
}
cmd_stop() {
require_root || { log_error "Must run as root"; return 1; }
/etc/init.d/matrix stop
}
cmd_restart() {
require_root || { log_error "Must run as root"; return 1; }
/etc/init.d/matrix restart
}
# ---------- user management ----------
cmd_user() {
local subcmd="$1"
shift
case "$subcmd" in
add)
cmd_user_add "$@"
;;
del|delete)
cmd_user_del "$@"
;;
passwd|password)
cmd_user_passwd "$@"
;;
list)
cmd_user_list
;;
*)
echo "Usage: matrixctl user <add|del|passwd|list>"
return 1
;;
esac
}
cmd_user_add() {
local mxid="$1"
local password="$2"
[ -z "$mxid" ] && {
echo "Usage: matrixctl user add <@user:server> [password]"
return 1
}
lxc_running || { log_error "Container not running"; return 1; }
defaults
# Parse Matrix ID
local user=$(echo "$mxid" | sed 's/^@//' | cut -d: -f1)
local server=$(echo "$mxid" | cut -d: -f2)
[ -z "$server" ] && server="$hostname"
[ -z "$password" ] && password=$(generate_password)
# Register user via Matrix client API
local reg_data="{\"username\":\"$user\",\"password\":\"$password\",\"auth\":{\"type\":\"m.login.dummy\"}}"
local result=$(wget -q -O - --post-data="$reg_data" \
--header="Content-Type: application/json" \
"http://127.0.0.1:${http_port}/_matrix/client/v3/register" 2>/dev/null)
if echo "$result" | grep -q "user_id"; then
log_info "User created: @${user}:${server}"
log_info "Password: $password"
else
# Try with registration token if needed
local reg_token=$(uci_get registration_token server)
if [ -n "$reg_token" ]; then
reg_data="{\"username\":\"$user\",\"password\":\"$password\",\"auth\":{\"type\":\"m.login.registration_token\",\"token\":\"$reg_token\"}}"
result=$(wget -q -O - --post-data="$reg_data" \
--header="Content-Type: application/json" \
"http://127.0.0.1:${http_port}/_matrix/client/v3/register" 2>/dev/null)
fi
if echo "$result" | grep -q "user_id"; then
log_info "User created: @${user}:${server}"
log_info "Password: $password"
else
log_error "Failed to create user. Error: $result"
log_info "Try enabling registration: uci set matrix.server.registration_enabled=1"
return 1
fi
fi
}
cmd_user_del() {
local mxid="$1"
[ -z "$mxid" ] && {
echo "Usage: matrixctl user del <@user:server>"
return 1
}
lxc_running || { log_error "Container not running"; return 1; }
log_info "User deactivation requires admin API"
log_info "Join #admins:$hostname and use: !admin users deactivate $mxid"
}
cmd_user_passwd() {
local mxid="$1"
local password="$2"
[ -z "$mxid" ] && {
echo "Usage: matrixctl user passwd <@user:server> [password]"
return 1
}
lxc_running || { log_error "Container not running"; return 1; }
log_info "Password change requires login as user"
log_info "Use Matrix client to change password via account settings"
}
cmd_user_list() {
lxc_running || { log_error "Container not running"; return 1; }
defaults
echo "Matrix Users on $hostname"
echo "========================="
log_info "User listing requires admin room access"
log_info "Join #admins:$hostname and use: !admin users list"
}
# ---------- room management ----------
cmd_room() {
local subcmd="$1"
shift
case "$subcmd" in
list)
cmd_room_list
;;
create)
cmd_room_create "$@"
;;
delete)
cmd_room_delete "$@"
;;
*)
echo "Usage: matrixctl room <list|create|delete>"
return 1
;;
esac
}
cmd_room_list() {
lxc_running || { log_error "Container not running"; return 1; }
defaults
log_info "Room listing requires admin room access"
log_info "Join #admins:$hostname and use: !admin rooms list"
}
cmd_room_create() {
local alias="$1"
[ -z "$alias" ] && {
echo "Usage: matrixctl room create <alias>"
return 1
}
lxc_running || { log_error "Container not running"; return 1; }
defaults
log_info "Create room via Matrix client or admin room"
log_info "Admin room: #admins:$hostname"
}
cmd_room_delete() {
local room_id="$1"
[ -z "$room_id" ] && {
echo "Usage: matrixctl room delete <room_id>"
return 1
}
lxc_running || { log_error "Container not running"; return 1; }
defaults
log_info "Join #admins:$hostname and use: !admin rooms delete $room_id"
}
# ---------- federation ----------
cmd_federation() {
local subcmd="$1"
shift
case "$subcmd" in
test)
cmd_federation_test "$@"
;;
status)
cmd_federation_status
;;
*)
echo "Usage: matrixctl federation <test|status>"
return 1
;;
esac
}
cmd_federation_test() {
local server="$1"
[ -z "$server" ] && {
echo "Usage: matrixctl federation test <server>"
return 1
}
# Test .well-known lookup
log_info "Testing federation with $server..."
local well_known=$(wget -q -O - "https://$server/.well-known/matrix/server" 2>/dev/null)
if [ -n "$well_known" ]; then
log_info ".well-known/matrix/server: $well_known"
else
log_warn "No .well-known delegation found for $server"
fi
log_info ""
log_info "For comprehensive testing, use:"
log_info " https://federationtester.matrix.org/?server=$server"
}
cmd_federation_status() {
defaults
echo "Federation Status"
echo "================="
echo " Enabled: $([ "$(uci_get enabled federation)" = "1" ] && echo "yes" || echo "no")"
echo " Public rooms: $([ "$(uci_get allow_public_rooms federation)" = "1" ] && echo "allowed" || echo "denied")"
echo " Trusted servers: $(uci_get trusted_servers federation || echo "matrix.org")"
echo ""
if lxc_running; then
# Test our own .well-known
local our_server=$(wget -q -O - "http://127.0.0.1:${http_port}/.well-known/matrix/server" 2>/dev/null)
if [ -n "$our_server" ]; then
log_info "Our .well-known/matrix/server: $our_server"
fi
fi
}
# ---------- HAProxy integration ----------
cmd_configure_haproxy() {
require_root || { log_error "Must run as root"; return 1; }
defaults
local domain=$(uci_get domain network)
[ -z "$domain" ] && domain="$hostname"
# Create backend for Matrix client API
local backend_name="matrix_client"
uci set haproxy.${backend_name}=backend
uci set haproxy.${backend_name}.name="$backend_name"
uci set haproxy.${backend_name}.mode='http'
uci set haproxy.${backend_name}.balance='roundrobin'
uci set haproxy.${backend_name}.enabled='1'
uci set haproxy.${backend_name}.timeout_server='3600s'
uci set haproxy.${backend_name}.timeout_tunnel='3600s'
uci set haproxy.${backend_name}.server="matrix 127.0.0.1:${http_port} check"
# Create vhost
local vhost_name=$(echo "$domain" | tr '.-' '_')
uci set haproxy.${vhost_name}=vhost
uci set haproxy.${vhost_name}.domain="$domain"
uci set haproxy.${vhost_name}.backend="$backend_name"
uci set haproxy.${vhost_name}.ssl='1'
uci set haproxy.${vhost_name}.ssl_redirect='1'
uci set haproxy.${vhost_name}.acme='1'
uci set haproxy.${vhost_name}.enabled='1'
uci commit haproxy
# Update network config
uci_set haproxy '1' network
uci_set domain "$domain" network
uci commit "$CONFIG"
# Regenerate and reload
if command -v haproxyctl >/dev/null 2>&1; then
haproxyctl generate
/etc/init.d/haproxy reload
fi
log_info "HAProxy configured for $domain"
log_info "Matrix Client API: https://$domain/_matrix/client/"
log_info ".well-known: https://$domain/.well-known/matrix/"
}
cmd_emancipate() {
local domain="$1"
[ -z "$domain" ] && {
echo "Usage: matrixctl emancipate <domain>"
return 1
}
require_root || { log_error "Must run as root"; return 1; }
log_info "Emancipating Matrix at $domain..."
# Update hostname
uci_set hostname "$domain" server
uci_set domain "$domain" network
uci commit "$CONFIG"
# Update container environment and trigger config regeneration
if lxc_running; then
lxc_exec touch /tmp/matrix_regenerate_config
cmd_restart
fi
# Configure HAProxy
cmd_configure_haproxy
log_info ""
log_info "=============================================="
log_info " Matrix Homeserver Emancipated!"
log_info "=============================================="
log_info ""
log_info " Domain: $domain"
log_info " Client API: https://$domain/_matrix/client/"
log_info " Federation: https://$domain:443"
log_info ""
log_info " DNS Records needed:"
log_info " A $domain -> your-ip"
log_info " SRV _matrix._tcp.$domain -> $domain:443"
log_info ""
log_info " .well-known served automatically by Conduit"
log_info ""
}
# ---------- identity integration ----------
cmd_identity() {
local subcmd="$1"
shift
case "$subcmd" in
link)
cmd_identity_link "$@"
;;
unlink)
cmd_identity_unlink "$@"
;;
status)
cmd_identity_status
;;
*)
echo "Usage: matrixctl identity <link|unlink|status>"
return 1
;;
esac
}
cmd_identity_link() {
local mxid="$1"
[ -z "$mxid" ] && {
echo "Usage: matrixctl identity link <@user:server>"
return 1
}
require_root || { log_error "Must run as root"; return 1; }
# Get node DID
local did=""
if command -v identityctl >/dev/null 2>&1; then
did=$(identityctl did 2>/dev/null)
fi
[ -z "$did" ] && {
log_error "Node DID not configured. Run: identityctl keygen"
return 1
}
# Store DID-MXID mapping
uci_set did_linked '1' identity
uci_set did_user "$mxid" identity
uci set ${CONFIG}.identity._did="$did"
uci commit "$CONFIG"
log_info "Matrix user $mxid linked to DID: $did"
}
cmd_identity_unlink() {
local mxid="$1"
require_root || { log_error "Must run as root"; return 1; }
uci_set did_linked '0' identity
uci_set did_user '' identity
uci commit "$CONFIG"
log_info "Matrix identity unlinked"
}
cmd_identity_status() {
local linked=$(uci_get did_linked identity || echo '0')
local mxid=$(uci_get did_user identity)
local did=$(uci -q get ${CONFIG}.identity._did)
echo "Identity Status"
echo "==============="
echo " Linked: $([ "$linked" = "1" ] && echo "yes" || echo "no")"
[ -n "$mxid" ] && echo " Matrix ID: $mxid"
[ -n "$did" ] && echo " Node DID: $did"
}
# ---------- mesh integration ----------
cmd_mesh() {
local subcmd="$1"
shift
case "$subcmd" in
publish)
cmd_mesh_publish
;;
unpublish)
cmd_mesh_unpublish
;;
*)
echo "Usage: matrixctl mesh <publish|unpublish>"
return 1
;;
esac
}
cmd_mesh_publish() {
require_root || { log_error "Must run as root"; return 1; }
defaults
# Use secubox-p2p to publish service
if command -v secubox-p2p >/dev/null 2>&1; then
secubox-p2p publish matrix "$http_port" "Matrix Homeserver"
log_info "Matrix published to P2P mesh"
else
log_warn "secubox-p2p not installed, skipping mesh publication"
fi
uci_set published '1' mesh
uci commit "$CONFIG"
}
cmd_mesh_unpublish() {
require_root || { log_error "Must run as root"; return 1; }
if command -v secubox-p2p >/dev/null 2>&1; then
secubox-p2p unpublish matrix 2>/dev/null || true
log_info "Matrix removed from P2P mesh"
fi
uci_set published '0' mesh
uci commit "$CONFIG"
}
# ---------- backup/restore ----------
cmd_backup() {
local backup_path="${1:-/srv/matrix/backup}"
require_root || { log_error "Must run as root"; return 1; }
ensure_dir "$backup_path"
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$backup_path/matrix_${timestamp}.tar.gz"
log_info "Creating backup..."
# Stop container for consistent backup
local was_running=0
if lxc_running; then
was_running=1
lxc_stop
fi
# Create tarball with data
defaults
tar -czf "$backup_file" \
-C "$data_path" data config
# Restart if was running
[ "$was_running" = "1" ] && cmd_start
log_info "Backup created: $backup_file"
}
cmd_restore() {
local backup_file="$1"
[ -z "$backup_file" ] || [ ! -f "$backup_file" ] && {
echo "Usage: matrixctl restore <backup.tar.gz>"
return 1
}
require_root || { log_error "Must run as root"; return 1; }
log_info "Restoring from $backup_file..."
# Stop container
lxc_stop
# Restore data
defaults
tar -xzf "$backup_file" -C "$data_path"
# Start container
cmd_start
log_info "Restore complete."
}
# ---------- service management ----------
cmd_service_run() {
require_root || exit 1
defaults
# Verify container exists
lxc_exists || { log_error "Container not found. Run: matrixctl install"; exit 1; }
log_info "Starting Matrix container..."
# Start container in foreground
exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONF"
}
cmd_service_stop() {
log_info "Stopping Matrix container..."
lxc_stop
}
# ---------- main ----------
case "$1" in
install) cmd_install ;;
uninstall) cmd_uninstall ;;
update) cmd_update ;;
check) cmd_check ;;
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
status) shift; cmd_status "$@" ;;
logs) shift; cmd_logs "$@" ;;
shell) cmd_shell ;;
user) shift; cmd_user "$@" ;;
room) shift; cmd_room "$@" ;;
federation) shift; cmd_federation "$@" ;;
configure-haproxy) cmd_configure_haproxy ;;
emancipate) shift; cmd_emancipate "$@" ;;
identity) shift; cmd_identity "$@" ;;
mesh) shift; cmd_mesh "$@" ;;
backup) shift; cmd_backup "$@" ;;
restore) shift; cmd_restore "$@" ;;
service-run) cmd_service_run ;;
service-stop) cmd_service_stop ;;
*) usage; exit 1 ;;
esac