feat(p2p): Add mDNS service publishing and REST API for mesh visibility
- Add mDNS service announcement via avahi-publish for _secubox._tcp - Add REST API endpoints on port 7331 (/api/peers, /api/status, /api/services) - Add node self-registration to ensure local node visible in mesh view - Add UCI defaults for uhttpd P2P API instance and firewall rules - Bump secubox-p2p version to 0.2.0 fix(vhost-manager): Fix uninitialized variable syntax errors - Add 'local' keyword to variable declarations on lines 606, 621, 693 fix(metablogizer,service-registry): Add HAProxy availability fallback - Add haproxy_available() helper to check if HAProxy is running - Gracefully skip HAProxy operations when service unavailable - Store pending HAProxy config for later when service becomes available - Prevent crashes when HAProxy container is stopped Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4166f4574e
commit
ce512bbda0
@ -66,8 +66,31 @@ fix_permissions() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reload HAProxy configuration properly
|
# Check if HAProxy is available and running
|
||||||
|
haproxy_available() {
|
||||||
|
# Check if HAProxy container is running (preferred method)
|
||||||
|
if command -v lxc-info >/dev/null 2>&1; then
|
||||||
|
lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING" && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: check if haproxy process is running
|
||||||
|
pgrep haproxy >/dev/null 2>&1 && return 0
|
||||||
|
|
||||||
|
# Fallback: check if init script exists and service is enabled
|
||||||
|
if [ -x /etc/init.d/haproxy ]; then
|
||||||
|
/etc/init.d/haproxy status >/dev/null 2>&1 && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reload HAProxy configuration properly (with availability check)
|
||||||
reload_haproxy() {
|
reload_haproxy() {
|
||||||
|
if ! haproxy_available; then
|
||||||
|
logger -t metablogizer "HAProxy not available, skipping reload"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
/usr/sbin/haproxyctl generate >/dev/null 2>&1
|
/usr/sbin/haproxyctl generate >/dev/null 2>&1
|
||||||
if [ -x /etc/init.d/haproxy ]; then
|
if [ -x /etc/init.d/haproxy ]; then
|
||||||
/etc/init.d/haproxy reload >/dev/null 2>&1
|
/etc/init.d/haproxy reload >/dev/null 2>&1
|
||||||
@ -362,42 +385,48 @@ EOF
|
|||||||
[ -n "$port" ] && uci set "$UCI_CONFIG.$section_id.port=$port"
|
[ -n "$port" ] && uci set "$UCI_CONFIG.$section_id.port=$port"
|
||||||
uci set "$UCI_CONFIG.$section_id.runtime=$current_runtime"
|
uci set "$UCI_CONFIG.$section_id.runtime=$current_runtime"
|
||||||
|
|
||||||
# 7. Create HAProxy backend
|
# 7. Create HAProxy backend (if HAProxy is available)
|
||||||
local backend_name="metablog_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')"
|
local haproxy_configured=0
|
||||||
|
if haproxy_available; then
|
||||||
|
local backend_name="metablog_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')"
|
||||||
|
|
||||||
uci set "haproxy.$backend_name=backend"
|
uci set "haproxy.$backend_name=backend"
|
||||||
uci set "haproxy.$backend_name.name=$backend_name"
|
uci set "haproxy.$backend_name.name=$backend_name"
|
||||||
uci set "haproxy.$backend_name.mode=http"
|
uci set "haproxy.$backend_name.mode=http"
|
||||||
uci set "haproxy.$backend_name.balance=roundrobin"
|
uci set "haproxy.$backend_name.balance=roundrobin"
|
||||||
uci set "haproxy.$backend_name.enabled=1"
|
uci set "haproxy.$backend_name.enabled=1"
|
||||||
|
|
||||||
# Create server
|
# Create server
|
||||||
local server_name="${backend_name}_srv"
|
local server_name="${backend_name}_srv"
|
||||||
uci set "haproxy.$server_name=server"
|
uci set "haproxy.$server_name=server"
|
||||||
uci set "haproxy.$server_name.backend=$backend_name"
|
uci set "haproxy.$server_name.backend=$backend_name"
|
||||||
uci set "haproxy.$server_name.name=srv"
|
uci set "haproxy.$server_name.name=srv"
|
||||||
uci set "haproxy.$server_name.address=$server_address"
|
uci set "haproxy.$server_name.address=$server_address"
|
||||||
uci set "haproxy.$server_name.port=$server_port"
|
uci set "haproxy.$server_name.port=$server_port"
|
||||||
uci set "haproxy.$server_name.weight=100"
|
uci set "haproxy.$server_name.weight=100"
|
||||||
uci set "haproxy.$server_name.check=1"
|
uci set "haproxy.$server_name.check=1"
|
||||||
uci set "haproxy.$server_name.enabled=1"
|
uci set "haproxy.$server_name.enabled=1"
|
||||||
|
|
||||||
# 8. Create HAProxy vhost
|
# 8. Create HAProxy vhost
|
||||||
local vhost_name=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
local vhost_name=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
local acme_val="0"
|
local acme_val="0"
|
||||||
[ "$ssl" = "1" ] && acme_val="1"
|
[ "$ssl" = "1" ] && acme_val="1"
|
||||||
|
|
||||||
uci set "haproxy.$vhost_name=vhost"
|
uci set "haproxy.$vhost_name=vhost"
|
||||||
uci set "haproxy.$vhost_name.domain=$domain"
|
uci set "haproxy.$vhost_name.domain=$domain"
|
||||||
uci set "haproxy.$vhost_name.backend=$backend_name"
|
uci set "haproxy.$vhost_name.backend=$backend_name"
|
||||||
uci set "haproxy.$vhost_name.ssl=$ssl"
|
uci set "haproxy.$vhost_name.ssl=$ssl"
|
||||||
uci set "haproxy.$vhost_name.ssl_redirect=$ssl"
|
uci set "haproxy.$vhost_name.ssl_redirect=$ssl"
|
||||||
uci set "haproxy.$vhost_name.acme=$acme_val"
|
uci set "haproxy.$vhost_name.acme=$acme_val"
|
||||||
uci set "haproxy.$vhost_name.enabled=1"
|
uci set "haproxy.$vhost_name.enabled=1"
|
||||||
uci commit haproxy
|
uci commit haproxy
|
||||||
|
|
||||||
# Regenerate HAProxy config and reload
|
# Regenerate HAProxy config and reload
|
||||||
reload_haproxy
|
reload_haproxy
|
||||||
|
haproxy_configured=1
|
||||||
|
else
|
||||||
|
logger -t metablogizer "HAProxy not available, site created without proxy config"
|
||||||
|
fi
|
||||||
|
|
||||||
uci commit "$UCI_CONFIG"
|
uci commit "$UCI_CONFIG"
|
||||||
|
|
||||||
@ -464,16 +493,22 @@ method_delete_site() {
|
|||||||
# Get site runtime
|
# Get site runtime
|
||||||
local site_runtime=$(get_uci "$id" runtime "")
|
local site_runtime=$(get_uci "$id" runtime "")
|
||||||
|
|
||||||
# 1. Delete HAProxy vhost
|
# 1. Delete HAProxy vhost (if HAProxy config exists)
|
||||||
local vhost_id=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
if uci -q get haproxy >/dev/null 2>&1; then
|
||||||
uci delete "haproxy.$vhost_id" 2>/dev/null
|
local vhost_id=$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
|
uci delete "haproxy.$vhost_id" 2>/dev/null
|
||||||
|
|
||||||
# 2. Delete HAProxy backend and server
|
# 2. Delete HAProxy backend and server
|
||||||
local backend_name="metablog_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')"
|
local backend_name="metablog_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')"
|
||||||
uci delete "haproxy.$backend_name" 2>/dev/null
|
uci delete "haproxy.$backend_name" 2>/dev/null
|
||||||
uci delete "haproxy.${backend_name}_srv" 2>/dev/null
|
uci delete "haproxy.${backend_name}_srv" 2>/dev/null
|
||||||
uci commit haproxy
|
uci commit haproxy
|
||||||
reload_haproxy
|
|
||||||
|
# Only reload if HAProxy is actually running
|
||||||
|
if haproxy_available; then
|
||||||
|
reload_haproxy
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# 3. Remove runtime config
|
# 3. Remove runtime config
|
||||||
if [ "$site_runtime" = "uhttpd" ]; then
|
if [ "$site_runtime" = "uhttpd" ]; then
|
||||||
|
|||||||
@ -35,6 +35,22 @@ is_port_listening() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Helper: Check if HAProxy is available and running
|
||||||
|
haproxy_available() {
|
||||||
|
# Check if HAProxy container is running (preferred method)
|
||||||
|
if command -v lxc-info >/dev/null 2>&1; then
|
||||||
|
lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING" && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: check if haproxy process is running
|
||||||
|
pgrep haproxy >/dev/null 2>&1 && return 0
|
||||||
|
|
||||||
|
# Fallback: check if RPCD haproxy interface is available
|
||||||
|
ubus call luci.haproxy list 2>/dev/null | jsonfilter -e '@' >/dev/null 2>&1 && return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Helper: Get process name for port
|
# Helper: Get process name for port
|
||||||
get_process_for_port() {
|
get_process_for_port() {
|
||||||
local port="$1"
|
local port="$1"
|
||||||
@ -263,6 +279,11 @@ _aggregate_haproxy_services() {
|
|||||||
local lan_ip="$1"
|
local lan_ip="$1"
|
||||||
local tmp_file="$2"
|
local tmp_file="$2"
|
||||||
|
|
||||||
|
# Check if HAProxy is available before calling RPCD
|
||||||
|
if ! haproxy_available; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Call HAProxy RPCD to get vhosts and backends
|
# Call HAProxy RPCD to get vhosts and backends
|
||||||
local vhosts_json backends_json certs_json
|
local vhosts_json backends_json certs_json
|
||||||
vhosts_json=$(ubus call luci.haproxy list_vhosts 2>/dev/null)
|
vhosts_json=$(ubus call luci.haproxy list_vhosts 2>/dev/null)
|
||||||
@ -614,29 +635,37 @@ method_publish_service() {
|
|||||||
local urls_clearnet=""
|
local urls_clearnet=""
|
||||||
local urls_onion=""
|
local urls_onion=""
|
||||||
|
|
||||||
# Create HAProxy vhost if domain specified
|
# Create HAProxy vhost if domain specified and HAProxy is available
|
||||||
if [ -n "$domain" ]; then
|
if [ -n "$domain" ]; then
|
||||||
# Ensure firewall allows HTTP/HTTPS from WAN (for public access + ACME)
|
if haproxy_available; then
|
||||||
ensure_haproxy_firewall_rules
|
# Ensure firewall allows HTTP/HTTPS from WAN (for public access + ACME)
|
||||||
|
ensure_haproxy_firewall_rules
|
||||||
|
|
||||||
# Create backend
|
# Create backend
|
||||||
ubus call luci.haproxy create_backend "{\"name\":\"$section_id\",\"mode\":\"http\"}" 2>/dev/null
|
ubus call luci.haproxy create_backend "{\"name\":\"$section_id\",\"mode\":\"http\"}" 2>/dev/null
|
||||||
|
|
||||||
# Create server pointing to local port
|
# Create server pointing to local port
|
||||||
ubus call luci.haproxy create_server "{\"backend\":\"$section_id\",\"name\":\"local\",\"address\":\"127.0.0.1\",\"port\":$local_port}" 2>/dev/null
|
ubus call luci.haproxy create_server "{\"backend\":\"$section_id\",\"name\":\"local\",\"address\":\"127.0.0.1\",\"port\":$local_port}" 2>/dev/null
|
||||||
|
|
||||||
# Create vhost with SSL
|
# Create vhost with SSL
|
||||||
ubus call luci.haproxy create_vhost "{\"domain\":\"$domain\",\"backend\":\"$section_id\",\"ssl\":1,\"ssl_redirect\":1,\"acme\":1,\"enabled\":1}" 2>/dev/null
|
ubus call luci.haproxy create_vhost "{\"domain\":\"$domain\",\"backend\":\"$section_id\",\"ssl\":1,\"ssl_redirect\":1,\"acme\":1,\"enabled\":1}" 2>/dev/null
|
||||||
|
|
||||||
# Regenerate HAProxy config to include the new vhost
|
# Regenerate HAProxy config to include the new vhost
|
||||||
ubus call luci.haproxy generate 2>/dev/null
|
ubus call luci.haproxy generate 2>/dev/null
|
||||||
ubus call luci.haproxy reload 2>/dev/null
|
ubus call luci.haproxy reload 2>/dev/null
|
||||||
|
|
||||||
uci set "$UCI_CONFIG.$section_id.haproxy_enabled=1"
|
uci set "$UCI_CONFIG.$section_id.haproxy_enabled=1"
|
||||||
uci set "$UCI_CONFIG.$section_id.haproxy_domain=$domain"
|
uci set "$UCI_CONFIG.$section_id.haproxy_domain=$domain"
|
||||||
uci set "$UCI_CONFIG.$section_id.haproxy_ssl=1"
|
uci set "$UCI_CONFIG.$section_id.haproxy_ssl=1"
|
||||||
|
|
||||||
urls_clearnet="https://${domain}"
|
urls_clearnet="https://${domain}"
|
||||||
|
else
|
||||||
|
# Store domain for later HAProxy configuration when it becomes available
|
||||||
|
uci set "$UCI_CONFIG.$section_id.haproxy_enabled=0"
|
||||||
|
uci set "$UCI_CONFIG.$section_id.haproxy_domain=$domain"
|
||||||
|
uci set "$UCI_CONFIG.$section_id.haproxy_pending=1"
|
||||||
|
logger -t service-registry "HAProxy unavailable, domain $domain saved for later"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create Tor hidden service if enabled
|
# Create Tor hidden service if enabled
|
||||||
@ -717,12 +746,14 @@ method_unpublish_service() {
|
|||||||
config_get haproxy_domain "$service_id" haproxy_domain ""
|
config_get haproxy_domain "$service_id" haproxy_domain ""
|
||||||
config_get tor_enabled "$service_id" tor_enabled "0"
|
config_get tor_enabled "$service_id" tor_enabled "0"
|
||||||
|
|
||||||
# Remove HAProxy vhost
|
# Remove HAProxy vhost (if HAProxy is available)
|
||||||
if [ "$haproxy_enabled" = "1" ] && [ -n "$haproxy_domain" ]; then
|
if [ "$haproxy_enabled" = "1" ] && [ -n "$haproxy_domain" ]; then
|
||||||
local vhost_id
|
if haproxy_available; then
|
||||||
vhost_id=$(echo "$haproxy_domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
local vhost_id
|
||||||
ubus call luci.haproxy delete_vhost "{\"id\":\"$vhost_id\"}" 2>/dev/null
|
vhost_id=$(echo "$haproxy_domain" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
ubus call luci.haproxy delete_backend "{\"id\":\"$service_id\"}" 2>/dev/null
|
ubus call luci.haproxy delete_vhost "{\"id\":\"$vhost_id\"}" 2>/dev/null
|
||||||
|
ubus call luci.haproxy delete_backend "{\"id\":\"$service_id\"}" 2>/dev/null
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove Tor hidden service
|
# Remove Tor hidden service
|
||||||
@ -1368,9 +1399,11 @@ method_check_all_health() {
|
|||||||
# Check individual services with domains
|
# Check individual services with domains
|
||||||
json_add_array "services"
|
json_add_array "services"
|
||||||
|
|
||||||
# Get all published services with domains from HAProxy
|
# Get all published services with domains from HAProxy (if available)
|
||||||
local vhosts_json
|
local vhosts_json
|
||||||
vhosts_json=$(ubus call luci.haproxy list_vhosts 2>/dev/null)
|
if haproxy_available; then
|
||||||
|
vhosts_json=$(ubus call luci.haproxy list_vhosts 2>/dev/null)
|
||||||
|
fi
|
||||||
if [ -n "$vhosts_json" ]; then
|
if [ -n "$vhosts_json" ]; then
|
||||||
local count
|
local count
|
||||||
count=$(echo "$vhosts_json" | jsonfilter -e '@.vhosts[*].domain' 2>/dev/null | wc -l)
|
count=$(echo "$vhosts_json" | jsonfilter -e '@.vhosts[*].domain' 2>/dev/null | wc -l)
|
||||||
|
|||||||
@ -603,7 +603,7 @@ case "$1" in
|
|||||||
if [ -z "$domain" ]; then
|
if [ -z "$domain" ]; then
|
||||||
json_init; json_add_boolean "success" 0; json_add_string "message" "Domain required"; json_dump; exit 0
|
json_init; json_add_boolean "success" 0; json_add_string "message" "Domain required"; json_dump; exit 0
|
||||||
fi
|
fi
|
||||||
section
|
local section
|
||||||
section=$(find_section "$domain")
|
section=$(find_section "$domain")
|
||||||
if [ -z "$section" ]; then
|
if [ -z "$section" ]; then
|
||||||
json_init; json_add_boolean "success" 0; json_add_string "message" "VHost not found"; json_dump; exit 0
|
json_init; json_add_boolean "success" 0; json_add_string "message" "VHost not found"; json_dump; exit 0
|
||||||
@ -618,7 +618,7 @@ case "$1" in
|
|||||||
if [ -z "$enabled" ]; then
|
if [ -z "$enabled" ]; then
|
||||||
config_get_bool enabled "$section" enabled 1
|
config_get_bool enabled "$section" enabled 1
|
||||||
fi
|
fi
|
||||||
current_tls current_cert_path current_key_path
|
local current_tls current_cert_path current_key_path
|
||||||
config_get current_tls "$section" tls
|
config_get current_tls "$section" tls
|
||||||
config_get current_cert_path "$section" cert_path
|
config_get current_cert_path "$section" cert_path
|
||||||
config_get current_key_path "$section" key_path
|
config_get current_key_path "$section" key_path
|
||||||
@ -690,9 +690,8 @@ case "$1" in
|
|||||||
json_add_array "certificates"
|
json_add_array "certificates"
|
||||||
if [ -d "$ACME_STATE_DIR" ]; then
|
if [ -d "$ACME_STATE_DIR" ]; then
|
||||||
find "$ACME_STATE_DIR" -name "fullchain.cer" -type f 2>/dev/null | while read -r cert_file; do
|
find "$ACME_STATE_DIR" -name "fullchain.cer" -type f 2>/dev/null | while read -r cert_file; do
|
||||||
domain
|
local domain expires issuer subject
|
||||||
domain=$(basename "$(dirname "$cert_file")")
|
domain=$(basename "$(dirname "$cert_file")")
|
||||||
expires issuer subject
|
|
||||||
expires=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d'=' -f2)
|
expires=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d'=' -f2)
|
||||||
issuer=$(openssl x509 -in "$cert_file" -noout -issuer 2>/dev/null | cut -d'=' -f2-)
|
issuer=$(openssl x509 -in "$cert_file" -noout -issuer 2>/dev/null | cut -d'=' -f2-)
|
||||||
subject=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null | cut -d'=' -f2-)
|
subject=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null | cut -d'=' -f2-)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=secubox-p2p
|
PKG_NAME:=secubox-p2p
|
||||||
PKG_VERSION:=0.1.0
|
PKG_VERSION:=0.2.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
PKG_MAINTAINER:=SecuBox Team
|
PKG_MAINTAINER:=SecuBox Team
|
||||||
@ -13,13 +13,14 @@ define Package/secubox-p2p
|
|||||||
SECTION:=secubox
|
SECTION:=secubox
|
||||||
CATEGORY:=SecuBox
|
CATEGORY:=SecuBox
|
||||||
TITLE:=SecuBox P2P Hub Backend
|
TITLE:=SecuBox P2P Hub Backend
|
||||||
DEPENDS:=+jsonfilter +curl +avahi-daemon
|
DEPENDS:=+jsonfilter +curl +avahi-daemon +avahi-utils +uhttpd
|
||||||
PKGARCH:=all
|
PKGARCH:=all
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/secubox-p2p/description
|
define Package/secubox-p2p/description
|
||||||
SecuBox P2P Hub backend providing peer discovery, mesh networking,
|
SecuBox P2P Hub backend providing peer discovery, mesh networking,
|
||||||
DNS federation, and distributed service management.
|
DNS federation, and distributed service management. Includes mDNS
|
||||||
|
service announcement and REST API on port 7331 for mesh visibility.
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/secubox-p2p/conffiles
|
define Package/secubox-p2p/conffiles
|
||||||
@ -33,6 +34,9 @@ define Package/secubox-p2p/install
|
|||||||
$(INSTALL_DIR) $(1)/usr/sbin
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
$(INSTALL_BIN) ./root/usr/sbin/secubox-p2p $(1)/usr/sbin/
|
$(INSTALL_BIN) ./root/usr/sbin/secubox-p2p $(1)/usr/sbin/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
|
$(INSTALL_BIN) ./root/usr/bin/secubox-restore $(1)/usr/bin/
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||||
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-p2p $(1)/usr/libexec/rpcd/
|
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-p2p $(1)/usr/libexec/rpcd/
|
||||||
|
|
||||||
@ -41,12 +45,27 @@ define Package/secubox-p2p/install
|
|||||||
|
|
||||||
$(INSTALL_DIR) $(1)/etc/init.d
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
$(INSTALL_BIN) ./root/etc/init.d/secubox-p2p $(1)/etc/init.d/
|
$(INSTALL_BIN) ./root/etc/init.d/secubox-p2p $(1)/etc/init.d/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||||
|
$(INSTALL_BIN) ./root/etc/uci-defaults/99-secubox-p2p-api $(1)/etc/uci-defaults/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/www/api
|
||||||
|
$(INSTALL_BIN) ./root/www/api/peers $(1)/www/api/
|
||||||
|
$(INSTALL_BIN) ./root/www/api/status $(1)/www/api/
|
||||||
|
$(INSTALL_BIN) ./root/www/api/services $(1)/www/api/
|
||||||
|
$(INSTALL_BIN) ./root/www/api/sync $(1)/www/api/
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/secubox-p2p/postinst
|
define Package/secubox-p2p/postinst
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||||
|
# Run UCI defaults
|
||||||
|
[ -x /etc/uci-defaults/99-secubox-p2p-api ] && /etc/uci-defaults/99-secubox-p2p-api
|
||||||
|
# Reload uhttpd to pick up new instance
|
||||||
|
/etc/init.d/uhttpd reload 2>/dev/null
|
||||||
|
# Enable and start P2P service
|
||||||
/etc/init.d/secubox-p2p enable
|
/etc/init.d/secubox-p2p enable
|
||||||
|
/etc/init.d/secubox-p2p start
|
||||||
/etc/init.d/rpcd restart
|
/etc/init.d/rpcd restart
|
||||||
}
|
}
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@ -7,6 +7,9 @@ USE_PROCD=1
|
|||||||
NAME="secubox-p2p"
|
NAME="secubox-p2p"
|
||||||
PROG="/usr/sbin/secubox-p2p"
|
PROG="/usr/sbin/secubox-p2p"
|
||||||
|
|
||||||
|
# Ensure avahi-daemon is started before us
|
||||||
|
DEPEND="avahi-daemon"
|
||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
local enabled
|
local enabled
|
||||||
config_load secubox-p2p
|
config_load secubox-p2p
|
||||||
@ -14,14 +17,36 @@ start_service() {
|
|||||||
|
|
||||||
[ "$enabled" = "1" ] || return
|
[ "$enabled" = "1" ] || return
|
||||||
|
|
||||||
|
# Ensure state directory exists
|
||||||
|
mkdir -p /var/run/secubox-p2p
|
||||||
|
|
||||||
|
# Ensure avahi-daemon is running for mDNS
|
||||||
|
if [ -x /etc/init.d/avahi-daemon ]; then
|
||||||
|
/etc/init.d/avahi-daemon running || /etc/init.d/avahi-daemon start
|
||||||
|
fi
|
||||||
|
|
||||||
procd_open_instance
|
procd_open_instance
|
||||||
procd_set_param command $PROG daemon
|
procd_set_param command $PROG daemon
|
||||||
procd_set_param respawn
|
procd_set_param respawn
|
||||||
procd_set_param stdout 1
|
procd_set_param stdout 1
|
||||||
procd_set_param stderr 1
|
procd_set_param stderr 1
|
||||||
|
procd_set_param pidfile /var/run/secubox-p2p/daemon.pid
|
||||||
procd_close_instance
|
procd_close_instance
|
||||||
|
|
||||||
|
logger -t secubox-p2p "P2P mesh daemon started"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
# Stop mDNS announcement
|
||||||
|
$PROG stop-mdns 2>/dev/null
|
||||||
|
logger -t secubox-p2p "P2P mesh daemon stopped"
|
||||||
}
|
}
|
||||||
|
|
||||||
service_triggers() {
|
service_triggers() {
|
||||||
procd_add_reload_trigger "secubox-p2p"
|
procd_add_reload_trigger "secubox-p2p"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Configure uhttpd instance for P2P REST API on port 7331
|
||||||
|
|
||||||
|
# Check if p2p_api instance already exists
|
||||||
|
if ! uci -q get uhttpd.p2p_api >/dev/null 2>&1; then
|
||||||
|
uci set uhttpd.p2p_api=uhttpd
|
||||||
|
uci set uhttpd.p2p_api.listen_http='0.0.0.0:7331'
|
||||||
|
uci set uhttpd.p2p_api.home='/www/api'
|
||||||
|
uci set uhttpd.p2p_api.cgi_prefix='/'
|
||||||
|
uci set uhttpd.p2p_api.no_symlinks='0'
|
||||||
|
uci set uhttpd.p2p_api.no_dirlists='1'
|
||||||
|
uci set uhttpd.p2p_api.script_timeout='60'
|
||||||
|
uci set uhttpd.p2p_api.network_timeout='30'
|
||||||
|
uci commit uhttpd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add firewall rule for P2P API port (LAN only by default)
|
||||||
|
if ! uci show firewall 2>/dev/null | grep -q "P2P-API"; then
|
||||||
|
uci add firewall rule
|
||||||
|
uci set firewall.@rule[-1].name='P2P-API'
|
||||||
|
uci set firewall.@rule[-1].src='lan'
|
||||||
|
uci set firewall.@rule[-1].dest_port='7331'
|
||||||
|
uci set firewall.@rule[-1].proto='tcp'
|
||||||
|
uci set firewall.@rule[-1].target='ACCEPT'
|
||||||
|
uci set firewall.@rule[-1].enabled='1'
|
||||||
|
uci commit firewall
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add mDNS firewall rule if not exists
|
||||||
|
if ! uci show firewall 2>/dev/null | grep -q "mDNS"; then
|
||||||
|
uci add firewall rule
|
||||||
|
uci set firewall.@rule[-1].name='mDNS'
|
||||||
|
uci set firewall.@rule[-1].src='lan'
|
||||||
|
uci set firewall.@rule[-1].dest_port='5353'
|
||||||
|
uci set firewall.@rule[-1].proto='udp'
|
||||||
|
uci set firewall.@rule[-1].target='ACCEPT'
|
||||||
|
uci set firewall.@rule[-1].enabled='1'
|
||||||
|
uci commit firewall
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@ -2,17 +2,44 @@
|
|||||||
# SecuBox P2P Hub Manager
|
# SecuBox P2P Hub Manager
|
||||||
# Handles peer discovery, mesh networking, and service federation
|
# Handles peer discovery, mesh networking, and service federation
|
||||||
|
|
||||||
VERSION="0.1.0"
|
VERSION="0.2.0"
|
||||||
CONFIG_FILE="/etc/config/secubox-p2p"
|
CONFIG_FILE="/etc/config/secubox-p2p"
|
||||||
PEERS_FILE="/tmp/secubox-p2p-peers.json"
|
PEERS_FILE="/tmp/secubox-p2p-peers.json"
|
||||||
SERVICES_FILE="/tmp/secubox-p2p-services.json"
|
SERVICES_FILE="/tmp/secubox-p2p-services.json"
|
||||||
STATE_DIR="/var/run/secubox-p2p"
|
STATE_DIR="/var/run/secubox-p2p"
|
||||||
|
MDNS_PID_FILE="$STATE_DIR/mdns.pid"
|
||||||
|
API_PORT=7331
|
||||||
|
|
||||||
# Initialize
|
# Initialize
|
||||||
init() {
|
init() {
|
||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
[ -f "$PEERS_FILE" ] || echo '{"peers":[]}' > "$PEERS_FILE"
|
[ -f "$PEERS_FILE" ] || echo '{"peers":[]}' > "$PEERS_FILE"
|
||||||
[ -f "$SERVICES_FILE" ] || echo '{"services":[]}' > "$SERVICES_FILE"
|
[ -f "$SERVICES_FILE" ] || echo '{"services":[]}' > "$SERVICES_FILE"
|
||||||
|
|
||||||
|
# Initialize node info
|
||||||
|
_init_node_info
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize node identity
|
||||||
|
_init_node_info() {
|
||||||
|
local node_name
|
||||||
|
node_name=$(get_config main node_name "")
|
||||||
|
|
||||||
|
# Generate node name from hostname if not set
|
||||||
|
if [ -z "$node_name" ]; then
|
||||||
|
node_name=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "secubox")
|
||||||
|
set_config main node_name "$node_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate node ID if not exists
|
||||||
|
local node_id_file="$STATE_DIR/node.id"
|
||||||
|
if [ ! -f "$node_id_file" ]; then
|
||||||
|
# Generate unique node ID from MAC address
|
||||||
|
local mac
|
||||||
|
mac=$(ip link show br-lan 2>/dev/null | grep ether | awk '{print $2}' | tr -d ':')
|
||||||
|
[ -z "$mac" ] && mac=$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 12)
|
||||||
|
echo "sb-${mac}" > "$node_id_file"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get config value
|
# Get config value
|
||||||
@ -276,44 +303,146 @@ broadcast_command() {
|
|||||||
echo "{\"success\":true,\"broadcast_success\":$success,\"broadcast_failed\":$failed}"
|
echo "{\"success\":true,\"broadcast_success\":$success,\"broadcast_failed\":$failed}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Publish mDNS service announcement
|
||||||
|
publish_mdns() {
|
||||||
|
local node_name
|
||||||
|
node_name=$(get_config main node_name "secubox")
|
||||||
|
|
||||||
|
# Kill any existing avahi-publish process
|
||||||
|
[ -f "$MDNS_PID_FILE" ] && kill $(cat "$MDNS_PID_FILE") 2>/dev/null
|
||||||
|
rm -f "$MDNS_PID_FILE"
|
||||||
|
|
||||||
|
# Check if avahi-publish is available
|
||||||
|
if command -v avahi-publish >/dev/null 2>&1; then
|
||||||
|
# Get LAN IP for service registration
|
||||||
|
local lan_ip
|
||||||
|
lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
|
||||||
|
[ -z "$lan_ip" ] && lan_ip=$(uci -q get network.lan.ipaddr)
|
||||||
|
|
||||||
|
# Publish _secubox._tcp service
|
||||||
|
avahi-publish -s "$node_name" "_secubox._tcp" "$API_PORT" \
|
||||||
|
"version=$VERSION" \
|
||||||
|
"type=mesh-node" \
|
||||||
|
"ip=$lan_ip" &
|
||||||
|
echo $! > "$MDNS_PID_FILE"
|
||||||
|
|
||||||
|
logger -t secubox-p2p "mDNS service published: $node_name._secubox._tcp on port $API_PORT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
logger -t secubox-p2p "WARNING: avahi-publish not available, mDNS disabled"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop mDNS service announcement
|
||||||
|
stop_mdns() {
|
||||||
|
if [ -f "$MDNS_PID_FILE" ]; then
|
||||||
|
kill $(cat "$MDNS_PID_FILE") 2>/dev/null
|
||||||
|
rm -f "$MDNS_PID_FILE"
|
||||||
|
logger -t secubox-p2p "mDNS service stopped"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get node status JSON (for REST API)
|
||||||
|
get_node_status() {
|
||||||
|
local node_name node_id lan_ip uptime
|
||||||
|
node_name=$(get_config main node_name "secubox")
|
||||||
|
node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown")
|
||||||
|
lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
|
||||||
|
uptime=$(cat /proc/uptime | cut -d' ' -f1)
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"node_id": "$node_id",
|
||||||
|
"node_name": "$node_name",
|
||||||
|
"version": "$VERSION",
|
||||||
|
"address": "$lan_ip",
|
||||||
|
"api_port": $API_PORT,
|
||||||
|
"uptime": $uptime,
|
||||||
|
"discovery_enabled": $(get_config main discovery_enabled 1),
|
||||||
|
"sharing_enabled": $(get_config main sharing_enabled 1),
|
||||||
|
"peer_count": $(get_peers | jsonfilter -e '@.peers[*]' 2>/dev/null | wc -l)
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register self in peer list (ensure node is visible in its own mesh view)
|
||||||
|
register_self() {
|
||||||
|
local node_name node_id lan_ip
|
||||||
|
node_name=$(get_config main node_name "secubox")
|
||||||
|
node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown")
|
||||||
|
lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
|
||||||
|
|
||||||
|
# Check if self is already registered
|
||||||
|
local current=$(get_peers)
|
||||||
|
local exists
|
||||||
|
exists=$(echo "$current" | jsonfilter -e "@.peers[@.id='$node_id']" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$exists" ]; then
|
||||||
|
local self_peer="{\"id\":\"$node_id\",\"name\":\"$node_name (local)\",\"address\":\"$lan_ip\",\"status\":\"online\",\"is_local\":true,\"added\":\"$(date -Iseconds)\"}"
|
||||||
|
echo "$current" | jsonfilter -e '@' 2>/dev/null | sed "s/\"peers\":\[/\"peers\":[$self_peer,/" > "$PEERS_FILE.tmp"
|
||||||
|
if [ -s "$PEERS_FILE.tmp" ]; then
|
||||||
|
mv "$PEERS_FILE.tmp" "$PEERS_FILE"
|
||||||
|
else
|
||||||
|
# Fallback: just create a fresh peers file with self
|
||||||
|
echo "{\"peers\":[$self_peer]}" > "$PEERS_FILE"
|
||||||
|
fi
|
||||||
|
logger -t secubox-p2p "Registered local node: $node_name ($node_id)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Daemon mode
|
# Daemon mode
|
||||||
daemon_loop() {
|
daemon_loop() {
|
||||||
init
|
init
|
||||||
|
|
||||||
|
# Publish mDNS service
|
||||||
|
publish_mdns
|
||||||
|
|
||||||
|
# Register self in peer list
|
||||||
|
register_self
|
||||||
|
|
||||||
|
# Setup signal handlers
|
||||||
|
trap 'stop_mdns; exit 0' INT TERM
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Auto-discovery if enabled
|
# Auto-discovery if enabled
|
||||||
if [ "$(get_config main discovery_enabled 1)" = "1" ]; then
|
if [ "$(get_config main discovery_enabled 1)" = "1" ]; then
|
||||||
local discovered=$(discover_mdns 3)
|
local discovered=$(discover_mdns 3)
|
||||||
if [ "$discovered" != "[]" ]; then
|
if [ "$discovered" != "[]" ] && [ -n "$discovered" ]; then
|
||||||
# Update peers file with discovered peers
|
# Update peers file with discovered peers
|
||||||
local current=$(get_peers)
|
local current=$(get_peers)
|
||||||
for peer in $(echo "$discovered" | jq -c '.[]'); do
|
# Parse discovered peers (handle jq or jsonfilter)
|
||||||
local peer_id=$(echo "$peer" | jq -r '.id')
|
if command -v jq >/dev/null 2>&1; then
|
||||||
local exists=$(echo "$current" | jq ".peers[] | select(.id==\"$peer_id\")")
|
for peer in $(echo "$discovered" | jq -c '.[]' 2>/dev/null); do
|
||||||
if [ -z "$exists" ]; then
|
local peer_id=$(echo "$peer" | jq -r '.id')
|
||||||
current=$(echo "$current" | jq ".peers += [$peer]")
|
local exists=$(echo "$current" | jq ".peers[] | select(.id==\"$peer_id\")")
|
||||||
fi
|
if [ -z "$exists" ]; then
|
||||||
done
|
current=$(echo "$current" | jq ".peers += [$peer]")
|
||||||
echo "$current" > "$PEERS_FILE"
|
fi
|
||||||
|
done
|
||||||
|
echo "$current" > "$PEERS_FILE"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update peer status
|
# Update peer status (skip local node)
|
||||||
local peers=$(get_peers)
|
local peers=$(get_peers)
|
||||||
local updated_peers=$(echo "$peers" | jq '.peers | map(. + {"status": "checking"})' | jq -c '.[]')
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
local updated_peers=$(echo "$peers" | jq -c '.peers[] | select(.is_local != true)' 2>/dev/null)
|
||||||
|
|
||||||
for peer in $updated_peers; do
|
for peer in $updated_peers; do
|
||||||
local addr=$(echo "$peer" | jq -r '.address')
|
local addr=$(echo "$peer" | jq -r '.address')
|
||||||
local id=$(echo "$peer" | jq -r '.id')
|
local id=$(echo "$peer" | jq -r '.id')
|
||||||
|
|
||||||
if ping -c1 -W1 "$addr" >/dev/null 2>&1; then
|
if ping -c1 -W1 "$addr" >/dev/null 2>&1; then
|
||||||
peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"online\"")
|
peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"online\"")
|
||||||
else
|
else
|
||||||
peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"offline\"")
|
peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"offline\"")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "$peers" > "$PEERS_FILE"
|
echo "$peers" > "$PEERS_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
# Sleep interval
|
# Sleep interval
|
||||||
local interval=$(get_config main sync_interval 60)
|
local interval=$(get_config main sync_interval 60)
|
||||||
@ -359,8 +488,21 @@ case "$1" in
|
|||||||
version)
|
version)
|
||||||
echo "$VERSION"
|
echo "$VERSION"
|
||||||
;;
|
;;
|
||||||
|
status)
|
||||||
|
get_node_status
|
||||||
|
;;
|
||||||
|
publish-mdns)
|
||||||
|
publish_mdns
|
||||||
|
;;
|
||||||
|
stop-mdns)
|
||||||
|
stop_mdns
|
||||||
|
;;
|
||||||
|
register-self)
|
||||||
|
init
|
||||||
|
register_self
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $0 {daemon|discover|peers|add-peer|remove-peer|settings|set-settings|services|shared-services|sync|broadcast|version}"
|
echo "Usage: $0 {daemon|discover|peers|add-peer|remove-peer|settings|set-settings|services|shared-services|sync|broadcast|version|status|publish-mdns|stop-mdns|register-self}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
10
package/secubox/secubox-p2p/root/www/api/peers
Normal file
10
package/secubox/secubox-p2p/root/www/api/peers
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# P2P API - List peers endpoint
|
||||||
|
# Responds to requests on port 7331
|
||||||
|
|
||||||
|
echo "Content-Type: application/json"
|
||||||
|
echo "Access-Control-Allow-Origin: *"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get peers from secubox-p2p
|
||||||
|
/usr/sbin/secubox-p2p peers 2>/dev/null || echo '{"peers":[],"error":"daemon_unavailable"}'
|
||||||
10
package/secubox/secubox-p2p/root/www/api/services
Normal file
10
package/secubox/secubox-p2p/root/www/api/services
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# P2P API - List services endpoint
|
||||||
|
# Responds to requests on port 7331
|
||||||
|
|
||||||
|
echo "Content-Type: application/json"
|
||||||
|
echo "Access-Control-Allow-Origin: *"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get services from secubox-p2p
|
||||||
|
/usr/sbin/secubox-p2p services 2>/dev/null || echo '{"services":[],"error":"daemon_unavailable"}'
|
||||||
10
package/secubox/secubox-p2p/root/www/api/status
Normal file
10
package/secubox/secubox-p2p/root/www/api/status
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# P2P API - Node status endpoint
|
||||||
|
# Responds to requests on port 7331
|
||||||
|
|
||||||
|
echo "Content-Type: application/json"
|
||||||
|
echo "Access-Control-Allow-Origin: *"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get node status from secubox-p2p
|
||||||
|
/usr/sbin/secubox-p2p status 2>/dev/null || echo '{"error":"daemon_unavailable"}'
|
||||||
10
package/secubox/secubox-p2p/root/www/api/sync
Normal file
10
package/secubox/secubox-p2p/root/www/api/sync
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# P2P API - Trigger sync endpoint
|
||||||
|
# Responds to requests on port 7331
|
||||||
|
|
||||||
|
echo "Content-Type: application/json"
|
||||||
|
echo "Access-Control-Allow-Origin: *"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Trigger catalog sync
|
||||||
|
/usr/sbin/secubox-p2p sync 2>/dev/null || echo '{"success":false,"error":"daemon_unavailable"}'
|
||||||
Loading…
Reference in New Issue
Block a user