P2P App Store Emancipation: - secubox-p2p: Package distribution via mesh peers (CGI API, RPCD, CLI) - packages.js: LuCI view with LOCAL/PEER badges, fetch/install actions - devstatus.js: Dev Status widget with Gitea commits, v1.0 progress tracking - secubox-feed: sync-content command for auto-installing content packages - ACL fix for P2P feed RPCD methods Remote Access: - secubox-app-rustdesk: Native hbbs/hbbr relay server from GitHub releases - secubox-app-guacamole: LXC Debian container with guacd + Tomcat (partial) Content Distribution: - secubox-content-pkg: Auto-package Metablogizer/Streamlit as IPKs - Auto-publish hooks in metablogizerctl and streamlitctl Mesh Media: - secubox-app-ksmbd: In-kernel SMB3 server with ksmbdctl CLI - Pre-configured shares for Jellyfin, Lyrion, Backup UI Consistency: - client-guardian: Ported to sh-page-header chip layout - auth-guardian: Ported to sh-page-header chip layout Fixes: - services.js: RPC expect unwrapping bug fix - metablogizer: Chunked upload for uhttpd 64KB limit Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
321 lines
7.8 KiB
Bash
321 lines
7.8 KiB
Bash
#!/bin/sh
|
|
# SecuBox ksmbd Mesh Media Server Manager
|
|
|
|
CONFIG="secubox-ksmbd"
|
|
KSMBD_CONFIG="ksmbd"
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: ksmbdctl <command>
|
|
|
|
Commands:
|
|
enable Enable mesh media server
|
|
disable Disable mesh media server
|
|
status Show server and share status
|
|
apply Apply SecuBox shares to ksmbd
|
|
add-share <name> <path> [--guest] [--readonly]
|
|
Add a new share
|
|
remove-share <name> Remove a share
|
|
list-shares List configured shares
|
|
add-user <name> Add SMB user (prompts for password)
|
|
remove-user <name> Remove SMB user
|
|
list-users List SMB users
|
|
mesh-register Register with P2P mesh
|
|
restart Restart ksmbd service
|
|
USAGE
|
|
}
|
|
|
|
# ---------- helpers ----------
|
|
|
|
require_root() { [ "$(id -u)" -eq 0 ]; }
|
|
|
|
log_info() { echo "[INFO] $*"; logger -t ksmbdctl "$*"; }
|
|
log_error() { echo "[ERROR] $*" >&2; logger -t ksmbdctl -p err "$*"; }
|
|
|
|
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"
|
|
}
|
|
|
|
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
|
|
|
# ---------- share management ----------
|
|
|
|
apply_shares() {
|
|
# Sync SecuBox shares to ksmbd config
|
|
local workgroup=$(uci_get workgroup)
|
|
local description=$(uci_get description)
|
|
|
|
# Update ksmbd globals
|
|
uci set ${KSMBD_CONFIG}.@globals[0].workgroup="$workgroup"
|
|
uci set ${KSMBD_CONFIG}.@globals[0].description="$description"
|
|
|
|
# Remove existing ksmbd shares
|
|
while uci -q delete ${KSMBD_CONFIG}.@share[0]; do :; done
|
|
|
|
# Add SecuBox shares
|
|
. /lib/functions.sh
|
|
config_load "$CONFIG"
|
|
config_foreach _add_ksmbd_share share
|
|
|
|
uci commit "$KSMBD_CONFIG"
|
|
log_info "Applied SecuBox shares to ksmbd"
|
|
}
|
|
|
|
_add_ksmbd_share() {
|
|
local section="$1"
|
|
local enabled name path guest_ok read_only comment
|
|
|
|
config_get enabled "$section" enabled 0
|
|
[ "$enabled" = "1" ] || return 0
|
|
|
|
config_get name "$section" name "$section"
|
|
config_get path "$section" path ""
|
|
config_get guest_ok "$section" guest_ok 0
|
|
config_get read_only "$section" read_only 0
|
|
config_get comment "$section" comment ""
|
|
|
|
[ -z "$path" ] && return 0
|
|
|
|
# Ensure directory exists
|
|
ensure_dir "$path"
|
|
|
|
# Add to ksmbd
|
|
uci add ${KSMBD_CONFIG} share >/dev/null
|
|
uci set ${KSMBD_CONFIG}.@share[-1].name="$name"
|
|
uci set ${KSMBD_CONFIG}.@share[-1].path="$path"
|
|
uci set ${KSMBD_CONFIG}.@share[-1].guest_ok="$guest_ok"
|
|
uci set ${KSMBD_CONFIG}.@share[-1].read_only="$read_only"
|
|
[ -n "$comment" ] && uci set ${KSMBD_CONFIG}.@share[-1].comment="$comment"
|
|
uci set ${KSMBD_CONFIG}.@share[-1].browseable='1'
|
|
uci set ${KSMBD_CONFIG}.@share[-1].create_mask='0644'
|
|
uci set ${KSMBD_CONFIG}.@share[-1].dir_mask='0755'
|
|
|
|
log_info "Added share: $name -> $path"
|
|
}
|
|
|
|
cmd_add_share() {
|
|
local name="$1"
|
|
local path="$2"
|
|
shift 2
|
|
|
|
[ -z "$name" ] || [ -z "$path" ] && {
|
|
echo "Usage: ksmbdctl add-share <name> <path> [--guest] [--readonly]"
|
|
return 1
|
|
}
|
|
|
|
local guest_ok=0
|
|
local read_only=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--guest) guest_ok=1 ;;
|
|
--readonly) read_only=1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Sanitize name for UCI section
|
|
local section=$(echo "$name" | tr '[:upper:] ' '[:lower:]_' | tr -cd 'a-z0-9_')
|
|
|
|
uci set ${CONFIG}.${section}=share
|
|
uci set ${CONFIG}.${section}.enabled='1'
|
|
uci set ${CONFIG}.${section}.name="$name"
|
|
uci set ${CONFIG}.${section}.path="$path"
|
|
uci set ${CONFIG}.${section}.guest_ok="$guest_ok"
|
|
uci set ${CONFIG}.${section}.read_only="$read_only"
|
|
uci commit "$CONFIG"
|
|
|
|
log_info "Created share: $name -> $path"
|
|
apply_shares
|
|
cmd_restart
|
|
}
|
|
|
|
cmd_remove_share() {
|
|
local name="$1"
|
|
[ -z "$name" ] && { echo "Usage: ksmbdctl remove-share <name>"; return 1; }
|
|
|
|
local section=$(echo "$name" | tr '[:upper:] ' '[:lower:]_' | tr -cd 'a-z0-9_')
|
|
|
|
if uci -q get ${CONFIG}.${section} >/dev/null; then
|
|
uci delete ${CONFIG}.${section}
|
|
uci commit "$CONFIG"
|
|
log_info "Removed share: $name"
|
|
apply_shares
|
|
cmd_restart
|
|
else
|
|
log_error "Share not found: $name"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
cmd_list_shares() {
|
|
echo "Configured Shares:"
|
|
echo "=================="
|
|
. /lib/functions.sh
|
|
config_load "$CONFIG"
|
|
config_foreach _list_share share
|
|
}
|
|
|
|
_list_share() {
|
|
local section="$1"
|
|
local enabled name path guest_ok read_only
|
|
|
|
config_get enabled "$section" enabled 0
|
|
config_get name "$section" name "$section"
|
|
config_get path "$section" path ""
|
|
config_get guest_ok "$section" guest_ok 0
|
|
config_get read_only "$section" read_only 0
|
|
|
|
local status="disabled"
|
|
[ "$enabled" = "1" ] && status="enabled"
|
|
|
|
local flags=""
|
|
[ "$guest_ok" = "1" ] && flags="${flags}guest "
|
|
[ "$read_only" = "1" ] && flags="${flags}ro "
|
|
[ -z "$flags" ] && flags="auth rw"
|
|
|
|
printf " %-12s %-25s [%s] %s\n" "$name" "$path" "$status" "$flags"
|
|
}
|
|
|
|
# ---------- user management ----------
|
|
|
|
cmd_add_user() {
|
|
local username="$1"
|
|
[ -z "$username" ] && { echo "Usage: ksmbdctl add-user <username>"; return 1; }
|
|
|
|
echo "Adding SMB user: $username"
|
|
ksmbd.adduser -a "$username"
|
|
}
|
|
|
|
cmd_remove_user() {
|
|
local username="$1"
|
|
[ -z "$username" ] && { echo "Usage: ksmbdctl remove-user <username>"; return 1; }
|
|
|
|
ksmbd.adduser -d "$username"
|
|
log_info "Removed user: $username"
|
|
}
|
|
|
|
cmd_list_users() {
|
|
echo "SMB Users:"
|
|
echo "=========="
|
|
if [ -f /etc/ksmbd/ksmbdpwd.db ]; then
|
|
ksmbd.adduser -l 2>/dev/null || echo " (none)"
|
|
else
|
|
echo " (no user database)"
|
|
fi
|
|
}
|
|
|
|
# ---------- service control ----------
|
|
|
|
cmd_enable() {
|
|
require_root || { log_error "Must run as root"; return 1; }
|
|
|
|
uci_set enabled '1'
|
|
uci commit "$CONFIG"
|
|
|
|
apply_shares
|
|
|
|
/etc/init.d/ksmbd enable
|
|
/etc/init.d/ksmbd start
|
|
|
|
log_info "Mesh media server enabled"
|
|
}
|
|
|
|
cmd_disable() {
|
|
require_root || { log_error "Must run as root"; return 1; }
|
|
|
|
/etc/init.d/ksmbd stop
|
|
/etc/init.d/ksmbd disable
|
|
|
|
uci_set enabled '0'
|
|
uci commit "$CONFIG"
|
|
|
|
log_info "Mesh media server disabled"
|
|
}
|
|
|
|
cmd_restart() {
|
|
/etc/init.d/ksmbd restart
|
|
log_info "ksmbd restarted"
|
|
}
|
|
|
|
cmd_status() {
|
|
echo "SecuBox Mesh Media Server Status"
|
|
echo "================================="
|
|
echo ""
|
|
echo "Configuration:"
|
|
echo " Enabled: $(uci_get enabled || echo '0')"
|
|
echo " Workgroup: $(uci_get workgroup)"
|
|
echo " Description: $(uci_get description)"
|
|
echo " Mesh Announce: $(uci_get mesh_announce)"
|
|
echo ""
|
|
|
|
# Service status
|
|
if pgrep ksmbd.mountd >/dev/null 2>&1; then
|
|
echo "Service: RUNNING"
|
|
else
|
|
echo "Service: STOPPED"
|
|
fi
|
|
|
|
# Show listening port
|
|
if netstat -tln 2>/dev/null | grep -q ":445 "; then
|
|
echo "SMB Port: 445 (listening)"
|
|
else
|
|
echo "SMB Port: 445 (not listening)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Active Shares (from ksmbd):"
|
|
local idx=0
|
|
while true; do
|
|
local share_name=$(uci -q get ksmbd.@share[$idx].name)
|
|
local share_path=$(uci -q get ksmbd.@share[$idx].path)
|
|
[ -z "$share_name" ] && break
|
|
printf " %-12s -> %s\n" "$share_name" "$share_path"
|
|
idx=$((idx + 1))
|
|
done
|
|
[ "$idx" = "0" ] && echo " (no shares configured)"
|
|
|
|
echo ""
|
|
echo "Network Access:"
|
|
local lan_ip=$(uci -q get network.lan.ipaddr || echo '192.168.255.1')
|
|
echo " smb://${lan_ip}/"
|
|
echo " \\\\\\\\${lan_ip}\\\\"
|
|
}
|
|
|
|
cmd_mesh_register() {
|
|
if [ -x /usr/sbin/secubox-p2p ]; then
|
|
/usr/sbin/secubox-p2p register-service ksmbd 445 2>/dev/null
|
|
log_info "Registered ksmbd with mesh"
|
|
else
|
|
log_error "secubox-p2p not available"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ---------- main ----------
|
|
|
|
case "$1" in
|
|
enable) cmd_enable ;;
|
|
disable) cmd_disable ;;
|
|
status) cmd_status ;;
|
|
apply) apply_shares ;;
|
|
add-share) shift; cmd_add_share "$@" ;;
|
|
remove-share) shift; cmd_remove_share "$@" ;;
|
|
list-shares) cmd_list_shares ;;
|
|
add-user) shift; cmd_add_user "$@" ;;
|
|
remove-user) shift; cmd_remove_user "$@" ;;
|
|
list-users) cmd_list_users ;;
|
|
mesh-register) cmd_mesh_register ;;
|
|
restart) cmd_restart ;;
|
|
*) usage; exit 1 ;;
|
|
esac
|