secubox-openwrt/package/secubox/secubox-p2p/root/usr/lib/secubox/p2p-feed.sh
CyberMind-FR 304ac7b9a1 feat: P2P App Store, Remote Access & Mesh Media packages
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>
2026-02-05 00:33:53 +01:00

355 lines
9.4 KiB
Bash

#!/bin/sh
# P2P Feed Library - Package federation functions
# Provides package catalog parsing, peer discovery, and fetch operations
FEED_DIR="/www/secubox-feed"
PACKAGES_FILE="$FEED_DIR/Packages"
PEER_CACHE_DIR="/tmp/secubox-p2p-feed-cache"
PEER_CACHE_TTL=300 # 5 minutes
# Get local node info
get_local_node_id() {
cat /var/run/secubox-p2p/node.id 2>/dev/null || hostname
}
get_local_node_name() {
uci -q get system.@system[0].hostname || hostname
}
# Check if local feed sharing is enabled
feed_sharing_enabled() {
local enabled=$(uci -q get secubox-p2p.feed.share_feed)
[ "$enabled" = "1" ]
}
# Check if local feed exists and is valid
feed_exists() {
[ -f "$PACKAGES_FILE" ] && [ -s "$PACKAGES_FILE" ]
}
# Get feed hash (for change detection)
get_feed_hash() {
if feed_exists; then
sha256sum "$PACKAGES_FILE" 2>/dev/null | cut -c1-16
else
echo "none"
fi
}
# Get package count from Packages file
get_package_count() {
if feed_exists; then
grep -c '^Package:' "$PACKAGES_FILE" 2>/dev/null || echo "0"
else
echo "0"
fi
}
# Parse Packages file and output JSON array
# Reads standard opkg Packages format and converts to JSON
packages_to_json() {
local packages_file="${1:-$PACKAGES_FILE}"
[ -f "$packages_file" ] || { echo '[]'; return; }
awk '
BEGIN {
first = 1
printf "["
}
# Empty line marks end of package block
/^$/ {
if (pkg_name != "") {
if (!first) printf ","
first = 0
printf "\n{"
printf "\"name\":\"%s\"", pkg_name
printf ",\"version\":\"%s\"", pkg_version
printf ",\"architecture\":\"%s\"", pkg_arch
printf ",\"size\":%s", (pkg_size != "" ? pkg_size : "0")
printf ",\"installed_size\":%s", (pkg_isize != "" ? pkg_isize : "0")
if (pkg_sha256 != "") printf ",\"sha256\":\"%s\"", pkg_sha256
if (pkg_depends != "") printf ",\"depends\":\"%s\"", pkg_depends
if (pkg_desc != "") {
gsub(/"/, "\\\"", pkg_desc)
printf ",\"description\":\"%s\"", pkg_desc
}
if (pkg_filename != "") printf ",\"filename\":\"%s\"", pkg_filename
printf "}"
}
pkg_name = pkg_version = pkg_arch = pkg_size = pkg_isize = ""
pkg_sha256 = pkg_depends = pkg_desc = pkg_filename = ""
next
}
/^Package:/ { pkg_name = $2 }
/^Version:/ { pkg_version = $2 }
/^Architecture:/ { pkg_arch = $2 }
/^Size:/ { pkg_size = $2 }
/^Installed-Size:/ { pkg_isize = $2 }
/^SHA256sum:/ { pkg_sha256 = $2 }
/^Depends:/ {
pkg_depends = $0
sub(/^Depends: */, "", pkg_depends)
}
/^Description:/ {
pkg_desc = $0
sub(/^Description: */, "", pkg_desc)
}
/^Filename:/ { pkg_filename = $2 }
END {
# Handle last package if file doesnt end with empty line
if (pkg_name != "") {
if (!first) printf ","
printf "\n{"
printf "\"name\":\"%s\"", pkg_name
printf ",\"version\":\"%s\"", pkg_version
printf ",\"architecture\":\"%s\"", pkg_arch
printf ",\"size\":%s", (pkg_size != "" ? pkg_size : "0")
printf ",\"installed_size\":%s", (pkg_isize != "" ? pkg_isize : "0")
if (pkg_sha256 != "") printf ",\"sha256\":\"%s\"", pkg_sha256
if (pkg_depends != "") printf ",\"depends\":\"%s\"", pkg_depends
if (pkg_desc != "") {
gsub(/"/, "\\\"", pkg_desc)
printf ",\"description\":\"%s\"", pkg_desc
}
if (pkg_filename != "") printf ",\"filename\":\"%s\"", pkg_filename
printf "}"
}
printf "\n]"
}
' "$packages_file"
}
# Get list of mesh peers with active feeds
# Returns: peer_addr|peer_name|feed_hash|pkg_count for each peer
get_feed_peers() {
local peers_file="/tmp/secubox-p2p-peers.json"
[ -f "$peers_file" ] || return
local peer_count=$(jsonfilter -i "$peers_file" -e '@.peers[*]' 2>/dev/null | wc -l)
local i=0
while [ $i -lt $peer_count ]; do
local is_local=$(jsonfilter -i "$peers_file" -e "@.peers[$i].is_local" 2>/dev/null)
[ "$is_local" = "true" ] && { i=$((i + 1)); continue; }
local peer_addr=$(jsonfilter -i "$peers_file" -e "@.peers[$i].address" 2>/dev/null)
local peer_name=$(jsonfilter -i "$peers_file" -e "@.peers[$i].name" 2>/dev/null)
local peer_wg=$(jsonfilter -i "$peers_file" -e "@.peers[$i].wg_addresses" 2>/dev/null | cut -d',' -f1)
# Prefer WireGuard address for mesh access
local use_addr="$peer_addr"
[ -n "$peer_wg" ] && use_addr="$peer_wg"
[ -n "$use_addr" ] || { i=$((i + 1)); continue; }
# Check if peer has feed available (quick probe)
local feed_info=$(probe_peer_feed "$use_addr")
if [ -n "$feed_info" ]; then
echo "$use_addr|$peer_name|$feed_info"
fi
i=$((i + 1))
done
}
# Probe a peer for feed availability
# Returns: feed_hash|pkg_count if available, empty if not
probe_peer_feed() {
local peer_addr="$1"
local cache_file="$PEER_CACHE_DIR/${peer_addr}.probe"
mkdir -p "$PEER_CACHE_DIR"
# Check cache
if [ -f "$cache_file" ]; then
local cache_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0)
local now=$(date +%s)
local age=$((now - cache_time))
if [ $age -lt $PEER_CACHE_TTL ]; then
cat "$cache_file"
return
fi
fi
# Probe peer's packages API
local result=$(curl -s --connect-timeout 2 --max-time 5 \
"http://${peer_addr}:7331/api/factory/packages?info_only=1" 2>/dev/null)
if [ -n "$result" ]; then
local feed_hash=$(echo "$result" | jsonfilter -e '@.feed_hash' 2>/dev/null)
local pkg_count=$(echo "$result" | jsonfilter -e '@.package_count' 2>/dev/null)
if [ -n "$feed_hash" ]; then
echo "$feed_hash|$pkg_count" > "$cache_file"
echo "$feed_hash|$pkg_count"
return
fi
fi
# Mark as unavailable
echo "" > "$cache_file"
}
# Fetch package catalog from a peer
# Returns JSON package list
fetch_peer_packages() {
local peer_addr="$1"
local cache_file="$PEER_CACHE_DIR/${peer_addr}.packages"
mkdir -p "$PEER_CACHE_DIR"
# Check cache
if [ -f "$cache_file" ]; then
local cache_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0)
local now=$(date +%s)
local age=$((now - cache_time))
if [ $age -lt $PEER_CACHE_TTL ]; then
cat "$cache_file"
return
fi
fi
# Fetch from peer
local result=$(curl -s --connect-timeout 5 --max-time 30 \
"http://${peer_addr}:7331/api/factory/packages" 2>/dev/null)
if [ -n "$result" ]; then
echo "$result" > "$cache_file"
echo "$result"
fi
}
# Fetch a specific IPK file from a peer
# Downloads to local feed directory
fetch_peer_ipk() {
local peer_addr="$1"
local package_name="$2"
local filename="$3"
[ -n "$peer_addr" ] && [ -n "$package_name" ] || return 1
# If filename not provided, try to construct it
if [ -z "$filename" ]; then
# Need to get filename from peer's package info
local pkg_info=$(curl -s --connect-timeout 2 --max-time 5 \
"http://${peer_addr}:7331/api/factory/packages?package=$package_name" 2>/dev/null)
filename=$(echo "$pkg_info" | jsonfilter -e '@.packages[0].filename' 2>/dev/null)
fi
[ -n "$filename" ] || return 1
# Download IPK
local dest="$FEED_DIR/$filename"
curl -s --connect-timeout 5 --max-time 300 \
-o "$dest" \
"http://${peer_addr}/secubox-feed/$filename" 2>/dev/null
if [ -f "$dest" ] && [ -s "$dest" ]; then
# Verify download (basic check)
if file "$dest" | grep -qi "gzip\|compressed"; then
return 0
else
rm -f "$dest"
return 1
fi
fi
return 1
}
# Search for a package across local and all peers
# Returns: source|peer_addr|version for each match
search_package() {
local pkg_name="$1"
local results=""
# Check local first
if feed_exists; then
local local_version=$(awk -v pkg="$pkg_name" '
/^Package:/ { current_pkg = $2 }
/^Version:/ { if (current_pkg == pkg) print $2; current_pkg = "" }
' "$PACKAGES_FILE")
if [ -n "$local_version" ]; then
echo "local||$local_version"
fi
fi
# Check peers
local peers=$(get_feed_peers)
for peer_line in $peers; do
local peer_addr=$(echo "$peer_line" | cut -d'|' -f1)
local peer_name=$(echo "$peer_line" | cut -d'|' -f2)
local pkg_info=$(curl -s --connect-timeout 2 --max-time 5 \
"http://${peer_addr}:7331/api/factory/packages?package=$pkg_name" 2>/dev/null)
if [ -n "$pkg_info" ]; then
local version=$(echo "$pkg_info" | jsonfilter -e '@.packages[0].version' 2>/dev/null)
if [ -n "$version" ]; then
echo "peer|$peer_addr|$version"
fi
fi
done
}
# Invalidate peer cache
invalidate_peer_cache() {
local peer_addr="$1"
if [ -n "$peer_addr" ]; then
rm -f "$PEER_CACHE_DIR/${peer_addr}."*
else
rm -rf "$PEER_CACHE_DIR"
fi
}
# Get aggregated package list from local + all peers
# Outputs unified JSON with source attribution
get_all_packages() {
local node_id=$(get_local_node_id)
local node_name=$(get_local_node_name)
echo '{"sources":['
local first_source=1
# Local packages
if feed_exists && feed_sharing_enabled; then
echo '{'
echo "\"node_id\":\"$node_id\","
echo "\"node_name\":\"$node_name\","
echo "\"type\":\"local\","
echo "\"feed_hash\":\"$(get_feed_hash)\","
echo "\"package_count\":$(get_package_count),"
echo "\"packages\":$(packages_to_json)"
echo '}'
first_source=0
fi
# Peer packages
local peers=$(get_feed_peers)
for peer_line in $peers; do
local peer_addr=$(echo "$peer_line" | cut -d'|' -f1)
local peer_name=$(echo "$peer_line" | cut -d'|' -f2)
local feed_info=$(echo "$peer_line" | cut -d'|' -f3-)
local feed_hash=$(echo "$feed_info" | cut -d'|' -f1)
local pkg_count=$(echo "$feed_info" | cut -d'|' -f2)
[ $first_source -eq 0 ] && echo ','
first_source=0
echo '{'
echo "\"node_id\":\"$peer_addr\","
echo "\"node_name\":\"$peer_name\","
echo "\"type\":\"peer\","
echo "\"address\":\"$peer_addr\","
echo "\"feed_hash\":\"$feed_hash\","
echo "\"package_count\":${pkg_count:-0}"
echo '}'
done
echo ']}'
}