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>
174 lines
4.3 KiB
Bash
174 lines
4.3 KiB
Bash
#!/bin/sh
|
|
# P2P Package Catalog Sync API
|
|
# Aggregates package catalogs from all mesh peers
|
|
|
|
echo "Content-Type: application/json"
|
|
echo "Access-Control-Allow-Origin: *"
|
|
echo "Access-Control-Allow-Methods: GET, POST, OPTIONS"
|
|
echo ""
|
|
|
|
# Handle CORS preflight
|
|
if [ "$REQUEST_METHOD" = "OPTIONS" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Load P2P feed library
|
|
. /usr/lib/secubox/p2p-feed.sh 2>/dev/null
|
|
|
|
# Parse query string
|
|
parse_query() {
|
|
local query="$QUERY_STRING"
|
|
local key value
|
|
|
|
PEER_FILTER=""
|
|
REFRESH=""
|
|
|
|
while [ -n "$query" ]; do
|
|
key="${query%%=*}"
|
|
query="${query#*=}"
|
|
value="${query%%&*}"
|
|
query="${query#*&}"
|
|
[ "$query" = "$value" ] && query=""
|
|
|
|
case "$key" in
|
|
peer) PEER_FILTER="$value" ;;
|
|
refresh) REFRESH="$value" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
parse_query
|
|
|
|
# Invalidate cache if refresh requested
|
|
if [ "$REFRESH" = "1" ]; then
|
|
invalidate_peer_cache
|
|
fi
|
|
|
|
# Get node info
|
|
NODE_ID=$(get_local_node_id)
|
|
NODE_NAME=$(get_local_node_name)
|
|
UPDATED=$(date -Iseconds 2>/dev/null || date '+%Y-%m-%dT%H:%M:%S')
|
|
|
|
# Start JSON output
|
|
echo '{'
|
|
echo "\"node_id\":\"$NODE_ID\","
|
|
echo "\"node_name\":\"$NODE_NAME\","
|
|
echo "\"updated\":\"$UPDATED\","
|
|
|
|
# Sync stats
|
|
PEERS_SYNCED=0
|
|
PEERS_FAILED=0
|
|
TOTAL_PACKAGES=0
|
|
|
|
echo '"sources":['
|
|
|
|
FIRST_SOURCE=1
|
|
|
|
# Include local packages
|
|
if feed_exists; then
|
|
LOCAL_COUNT=$(get_package_count)
|
|
TOTAL_PACKAGES=$LOCAL_COUNT
|
|
|
|
echo '{'
|
|
echo "\"node_id\":\"$NODE_ID\","
|
|
echo "\"node_name\":\"$NODE_NAME\","
|
|
echo "\"type\":\"local\","
|
|
echo "\"address\":\"local\","
|
|
echo "\"status\":\"ok\","
|
|
echo "\"feed_hash\":\"$(get_feed_hash)\","
|
|
echo "\"package_count\":$LOCAL_COUNT,"
|
|
echo "\"packages\":$(packages_to_json)"
|
|
echo '}'
|
|
FIRST_SOURCE=0
|
|
fi
|
|
|
|
# Sync from peers
|
|
PEERS_FILE="/tmp/secubox-p2p-peers.json"
|
|
if [ -f "$PEERS_FILE" ]; then
|
|
PEER_COUNT=$(jsonfilter -i "$PEERS_FILE" -e '@.peers[*]' 2>/dev/null | wc -l)
|
|
I=0
|
|
|
|
while [ $I -lt $PEER_COUNT ]; do
|
|
IS_LOCAL=$(jsonfilter -i "$PEERS_FILE" -e "@.peers[$I].is_local" 2>/dev/null)
|
|
[ "$IS_LOCAL" = "true" ] && { I=$((I + 1)); continue; }
|
|
|
|
PEER_ADDR=$(jsonfilter -i "$PEERS_FILE" -e "@.peers[$I].address" 2>/dev/null)
|
|
PEER_NAME=$(jsonfilter -i "$PEERS_FILE" -e "@.peers[$I].name" 2>/dev/null)
|
|
PEER_WG=$(jsonfilter -i "$PEERS_FILE" -e "@.peers[$I].wg_addresses" 2>/dev/null | cut -d',' -f1)
|
|
|
|
# Prefer WireGuard address
|
|
USE_ADDR="$PEER_ADDR"
|
|
[ -n "$PEER_WG" ] && USE_ADDR="$PEER_WG"
|
|
|
|
# Skip if filtering for specific peer
|
|
if [ -n "$PEER_FILTER" ] && [ "$USE_ADDR" != "$PEER_FILTER" ] && [ "$PEER_NAME" != "$PEER_FILTER" ]; then
|
|
I=$((I + 1))
|
|
continue
|
|
fi
|
|
|
|
[ -n "$USE_ADDR" ] || { I=$((I + 1)); continue; }
|
|
|
|
# Fetch peer's package catalog
|
|
PEER_CATALOG=$(curl -s --connect-timeout 5 --max-time 30 \
|
|
"http://${USE_ADDR}:7331/api/factory/packages" 2>/dev/null)
|
|
|
|
[ $FIRST_SOURCE -eq 0 ] && echo ','
|
|
FIRST_SOURCE=0
|
|
|
|
if [ -n "$PEER_CATALOG" ]; then
|
|
PEER_HASH=$(echo "$PEER_CATALOG" | jsonfilter -e '@.feed_hash' 2>/dev/null)
|
|
PEER_PKG_COUNT=$(echo "$PEER_CATALOG" | jsonfilter -e '@.package_count' 2>/dev/null)
|
|
PEER_PACKAGES=$(echo "$PEER_CATALOG" | jsonfilter -e '@.packages' 2>/dev/null)
|
|
|
|
if [ -n "$PEER_HASH" ]; then
|
|
PEERS_SYNCED=$((PEERS_SYNCED + 1))
|
|
TOTAL_PACKAGES=$((TOTAL_PACKAGES + ${PEER_PKG_COUNT:-0}))
|
|
|
|
echo '{'
|
|
echo "\"node_id\":\"$USE_ADDR\","
|
|
echo "\"node_name\":\"$PEER_NAME\","
|
|
echo "\"type\":\"peer\","
|
|
echo "\"address\":\"$USE_ADDR\","
|
|
echo "\"status\":\"ok\","
|
|
echo "\"feed_hash\":\"$PEER_HASH\","
|
|
echo "\"package_count\":${PEER_PKG_COUNT:-0},"
|
|
echo "\"packages\":${PEER_PACKAGES:-[]}"
|
|
echo '}'
|
|
else
|
|
PEERS_FAILED=$((PEERS_FAILED + 1))
|
|
echo '{'
|
|
echo "\"node_id\":\"$USE_ADDR\","
|
|
echo "\"node_name\":\"$PEER_NAME\","
|
|
echo "\"type\":\"peer\","
|
|
echo "\"address\":\"$USE_ADDR\","
|
|
echo "\"status\":\"error\","
|
|
echo "\"error\":\"invalid_response\""
|
|
echo '}'
|
|
fi
|
|
else
|
|
PEERS_FAILED=$((PEERS_FAILED + 1))
|
|
echo '{'
|
|
echo "\"node_id\":\"$USE_ADDR\","
|
|
echo "\"node_name\":\"$PEER_NAME\","
|
|
echo "\"type\":\"peer\","
|
|
echo "\"address\":\"$USE_ADDR\","
|
|
echo "\"status\":\"unreachable\","
|
|
echo "\"error\":\"connection_failed\""
|
|
echo '}'
|
|
fi
|
|
|
|
I=$((I + 1))
|
|
done
|
|
fi
|
|
|
|
echo '],'
|
|
|
|
# Summary stats
|
|
echo "\"sync_stats\":{"
|
|
echo "\"peers_synced\":$PEERS_SYNCED,"
|
|
echo "\"peers_failed\":$PEERS_FAILED,"
|
|
echo "\"total_packages\":$TOTAL_PACKAGES"
|
|
echo '}'
|
|
|
|
echo '}'
|