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>
712 lines
21 KiB
Bash
712 lines
21 KiB
Bash
#!/bin/sh
|
|
# SecuBox Feed Manager
|
|
# Manages local and remote package feeds for SecuBox appstore
|
|
# Copyright 2026 CyberMind <contact@cybermind.fr>
|
|
|
|
FEED_DIR="/www/secubox-feed"
|
|
OPKG_LIST="/var/opkg-lists/secubox"
|
|
REMOTE_FEED_URL="${SECUBOX_FEED_URL:-https://feed.maegia.tv/secubox-feed}"
|
|
GITEA_API="${SECUBOX_GITEA_API:-}"
|
|
GITHUB_REPO="${SECUBOX_GITHUB_REPO:-}"
|
|
|
|
usage() {
|
|
cat << EOF
|
|
SecuBox Feed Manager - Manage local and remote package feeds
|
|
|
|
Usage: secubox-feed <command> [options]
|
|
|
|
Commands:
|
|
update Update local feed from installed IPKs
|
|
sync Sync feed to /var/opkg-lists for opkg
|
|
fetch <url> Fetch IPK from URL and add to local feed
|
|
fetch-release <pkg> Fetch latest release from GitHub/Gitea
|
|
list List packages in local feed
|
|
info <pkg> Show package info
|
|
install <pkg> Install package from local feed
|
|
install all Install all packages from local feed
|
|
clean Remove old package versions
|
|
serve Start simple HTTP server for WAN access
|
|
export Export feed URL for sharing
|
|
|
|
P2P Commands:
|
|
peers List mesh peers with active package feeds
|
|
search <pkg> Search for package across local and all peers
|
|
fetch-peer <pkg> <peer> Fetch package from specific peer
|
|
fetch-any <pkg> Fetch package from first available peer
|
|
sync-peers Sync package catalogs from all mesh peers
|
|
sync-content Auto-install content packages (sites/apps) from peers
|
|
|
|
Options:
|
|
-h, --help Show this help
|
|
-v, --verbose Verbose output
|
|
-q, --quiet Quiet mode
|
|
|
|
Environment:
|
|
SECUBOX_FEED_URL Remote feed URL (default: $REMOTE_FEED_URL)
|
|
SECUBOX_GITEA_API Gitea API URL for releases
|
|
SECUBOX_GITHUB_REPO GitHub repo (owner/repo) for releases
|
|
|
|
EOF
|
|
}
|
|
|
|
log() {
|
|
[ "$QUIET" = "1" ] && return
|
|
echo "$@"
|
|
}
|
|
|
|
verbose() {
|
|
[ "$VERBOSE" = "1" ] && echo "$@"
|
|
}
|
|
|
|
error() {
|
|
echo "Error: $@" >&2
|
|
}
|
|
|
|
# Regenerate Packages index from IPK files
|
|
regenerate_packages() {
|
|
local feed_dir="${1:-$FEED_DIR}"
|
|
|
|
log "Regenerating Packages index..."
|
|
rm -f "$feed_dir/Packages" "$feed_dir/Packages.gz"
|
|
|
|
local count=0
|
|
for pkg in "$feed_dir"/*.ipk; do
|
|
[ -f "$pkg" ] || continue
|
|
|
|
local tmp_dir=$(mktemp -d)
|
|
(
|
|
cd "$tmp_dir"
|
|
tar -xzf "$pkg" 2>/dev/null || return 1
|
|
tar -xzf control.tar.gz 2>/dev/null || return 1
|
|
|
|
if [ -f control ]; then
|
|
# Strip libc and blank lines
|
|
sed -e 's/^Depends: libc$//' \
|
|
-e 's/^Depends: libc, /Depends: /' \
|
|
-e '/^$/d' control
|
|
echo "Filename: $(basename "$pkg")"
|
|
echo "Size: $(stat -c%s "$pkg" 2>/dev/null || wc -c < "$pkg")"
|
|
echo "SHA256sum: $(sha256sum "$pkg" | cut -d' ' -f1)"
|
|
echo ""
|
|
fi
|
|
) >> "$feed_dir/Packages"
|
|
rm -rf "$tmp_dir"
|
|
count=$((count + 1))
|
|
done
|
|
|
|
gzip -kf "$feed_dir/Packages" 2>/dev/null
|
|
log "Generated index for $count packages"
|
|
}
|
|
|
|
# Sync feed to opkg lists
|
|
sync_feed() {
|
|
if [ -f "$FEED_DIR/Packages" ]; then
|
|
cp "$FEED_DIR/Packages" "$OPKG_LIST"
|
|
log "Feed synced to $OPKG_LIST"
|
|
log "$(grep -c '^Package:' "$OPKG_LIST") packages available"
|
|
else
|
|
error "No Packages file found. Run 'secubox-feed update' first."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Fetch IPK from URL
|
|
fetch_ipk() {
|
|
local url="$1"
|
|
local filename=$(basename "$url")
|
|
|
|
log "Fetching $filename..."
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsSL -o "$FEED_DIR/$filename" "$url"
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
wget -q -O "$FEED_DIR/$filename" "$url"
|
|
else
|
|
error "Neither curl nor wget available"
|
|
return 1
|
|
fi
|
|
|
|
if [ -f "$FEED_DIR/$filename" ]; then
|
|
log "Downloaded: $filename"
|
|
regenerate_packages
|
|
sync_feed
|
|
else
|
|
error "Download failed"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Fetch latest release from GitHub
|
|
fetch_github_release() {
|
|
local pkg="$1"
|
|
local repo="${GITHUB_REPO:-secubox/secubox-openwrt}"
|
|
|
|
log "Fetching latest release for $pkg from GitHub..."
|
|
|
|
local api_url="https://api.github.com/repos/$repo/releases/latest"
|
|
local release_json=$(curl -fsSL "$api_url" 2>/dev/null)
|
|
|
|
if [ -z "$release_json" ]; then
|
|
error "Failed to fetch release info"
|
|
return 1
|
|
fi
|
|
|
|
# Find IPK matching package name
|
|
local ipk_url=$(echo "$release_json" | jsonfilter -e "@.assets[*].browser_download_url" 2>/dev/null | grep -i "${pkg}.*\.ipk" | head -1)
|
|
|
|
if [ -n "$ipk_url" ]; then
|
|
fetch_ipk "$ipk_url"
|
|
else
|
|
error "No IPK found for $pkg in latest release"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Fetch from Gitea releases
|
|
fetch_gitea_release() {
|
|
local pkg="$1"
|
|
|
|
if [ -z "$GITEA_API" ]; then
|
|
error "SECUBOX_GITEA_API not set"
|
|
return 1
|
|
fi
|
|
|
|
log "Fetching latest release for $pkg from Gitea..."
|
|
|
|
local api_url="$GITEA_API/repos/secubox/secubox-openwrt/releases/latest"
|
|
local release_json=$(curl -fsSL "$api_url" 2>/dev/null)
|
|
|
|
if [ -z "$release_json" ]; then
|
|
error "Failed to fetch release info from Gitea"
|
|
return 1
|
|
fi
|
|
|
|
local ipk_url=$(echo "$release_json" | jsonfilter -e "@.assets[*].browser_download_url" 2>/dev/null | grep -i "${pkg}.*\.ipk" | head -1)
|
|
|
|
if [ -n "$ipk_url" ]; then
|
|
fetch_ipk "$ipk_url"
|
|
else
|
|
error "No IPK found for $pkg in Gitea release"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# List packages in feed
|
|
list_packages() {
|
|
if [ -f "$FEED_DIR/Packages" ]; then
|
|
grep "^Package:" "$FEED_DIR/Packages" | sed 's/Package: //' | sort
|
|
else
|
|
error "No Packages file found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Show package info
|
|
package_info() {
|
|
local pkg="$1"
|
|
|
|
if [ -f "$FEED_DIR/Packages" ]; then
|
|
awk -v pkg="$pkg" '
|
|
/^Package:/ { found = ($2 == pkg) }
|
|
found { print }
|
|
found && /^$/ { exit }
|
|
' "$FEED_DIR/Packages"
|
|
else
|
|
error "No Packages file found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Install package from local feed (handles dependencies)
|
|
install_package() {
|
|
local pkg="$1"
|
|
|
|
# Handle "all" - install all packages
|
|
if [ "$pkg" = "all" ]; then
|
|
install_all_packages
|
|
return $?
|
|
fi
|
|
|
|
local ipk_pattern="$FEED_DIR/${pkg}_"*.ipk
|
|
|
|
# Find the IPK file
|
|
local found=$(ls $ipk_pattern 2>/dev/null | head -1)
|
|
|
|
if [ -z "$found" ]; then
|
|
error "Package $pkg not found in local feed"
|
|
return 1
|
|
fi
|
|
|
|
log "Installing $pkg..."
|
|
|
|
# Get dependencies from the package
|
|
local deps=$(tar -xOzf "$found" control.tar.gz 2>/dev/null | tar -xOzf - ./control 2>/dev/null | grep "^Depends:" | sed 's/^Depends: //')
|
|
|
|
# Install local dependencies first
|
|
if [ -n "$deps" ]; then
|
|
for dep in $(echo "$deps" | tr ',' '\n' | sed 's/^ *//; s/ *$//; s/ (.*)//' | sort -u); do
|
|
[ -z "$dep" ] && continue
|
|
local dep_ipk=$(ls "$FEED_DIR/${dep}_"*.ipk 2>/dev/null | head -1)
|
|
if [ -n "$dep_ipk" ]; then
|
|
verbose "Installing dependency: $dep"
|
|
opkg install "$dep_ipk" 2>/dev/null || true
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Install the main package
|
|
opkg install "$found"
|
|
}
|
|
|
|
# Install all packages from local feed
|
|
install_all_packages() {
|
|
log "Installing all packages from local feed..."
|
|
|
|
local total=0
|
|
local installed=0
|
|
local failed=0
|
|
|
|
# First pass: install secubox-core and dependencies
|
|
for ipk in "$FEED_DIR"/secubox-core_*.ipk "$FEED_DIR"/secubox-app_*.ipk; do
|
|
[ -f "$ipk" ] || continue
|
|
total=$((total + 1))
|
|
local name=$(basename "$ipk" | sed 's/_[0-9].*//')
|
|
log " Installing $name..."
|
|
if opkg install "$ipk" 2>/dev/null; then
|
|
installed=$((installed + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
# Second pass: install secubox-app-* backend packages
|
|
for ipk in "$FEED_DIR"/secubox-app-*.ipk; do
|
|
[ -f "$ipk" ] || continue
|
|
# Skip secubox-app-bonus (meta package)
|
|
echo "$ipk" | grep -q "secubox-app-bonus" && continue
|
|
total=$((total + 1))
|
|
local name=$(basename "$ipk" | sed 's/_[0-9].*//')
|
|
log " Installing $name..."
|
|
if opkg install "$ipk" 2>/dev/null; then
|
|
installed=$((installed + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
# Third pass: install luci-app-* frontend packages
|
|
for ipk in "$FEED_DIR"/luci-app-*.ipk; do
|
|
[ -f "$ipk" ] || continue
|
|
total=$((total + 1))
|
|
local name=$(basename "$ipk" | sed 's/_[0-9].*//')
|
|
log " Installing $name..."
|
|
if opkg install "$ipk" 2>/dev/null; then
|
|
installed=$((installed + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
# Fourth pass: install luci-theme-* packages
|
|
for ipk in "$FEED_DIR"/luci-theme-*.ipk; do
|
|
[ -f "$ipk" ] || continue
|
|
total=$((total + 1))
|
|
local name=$(basename "$ipk" | sed 's/_[0-9].*//')
|
|
log " Installing $name..."
|
|
if opkg install "$ipk" 2>/dev/null; then
|
|
installed=$((installed + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
log ""
|
|
log "Installation complete: $installed/$total succeeded, $failed failed"
|
|
return 0
|
|
}
|
|
|
|
# Clean old versions
|
|
clean_old_versions() {
|
|
log "Cleaning old package versions..."
|
|
|
|
local removed=0
|
|
for name in $(ls "$FEED_DIR"/*.ipk 2>/dev/null | sed 's/_[0-9].*$//' | sort -u); do
|
|
local basename=$(basename "$name")
|
|
local versions=$(ls -t "$FEED_DIR/${basename}_"*.ipk 2>/dev/null)
|
|
local count=$(echo "$versions" | wc -l)
|
|
|
|
if [ "$count" -gt 1 ]; then
|
|
echo "$versions" | tail -n +2 | while read old; do
|
|
[ -f "$old" ] || continue
|
|
verbose "Removing: $(basename "$old")"
|
|
rm -f "$old"
|
|
removed=$((removed + 1))
|
|
done
|
|
fi
|
|
done
|
|
|
|
log "Removed $removed old versions"
|
|
regenerate_packages
|
|
}
|
|
|
|
# Start simple HTTP server
|
|
serve_feed() {
|
|
local port="${1:-8080}"
|
|
|
|
if command -v uhttpd >/dev/null 2>&1; then
|
|
log "Feed available at: http://$(uci get network.lan.ipaddr 2>/dev/null || echo '<router-ip>'):$port/secubox-feed/"
|
|
log "Add to remote opkg: src/gz secubox http://<router-ip>:$port/secubox-feed"
|
|
# uhttpd is already running for LuCI, feed is at /www/secubox-feed/
|
|
else
|
|
log "uhttpd not available. Feed is at file:///www/secubox-feed/"
|
|
fi
|
|
}
|
|
|
|
# Export feed URL
|
|
export_feed() {
|
|
local ip=$(uci get network.lan.ipaddr 2>/dev/null || echo "192.168.1.1")
|
|
local wan_ip=$(curl -s ifconfig.me 2>/dev/null || echo "")
|
|
|
|
echo "Local feed configuration:"
|
|
echo " src secubox file:///www/secubox-feed"
|
|
echo ""
|
|
echo "LAN access (for other OpenWrt devices):"
|
|
echo " src/gz secubox http://$ip/secubox-feed"
|
|
echo ""
|
|
if [ -n "$wan_ip" ]; then
|
|
echo "WAN access (if port 80 forwarded):"
|
|
echo " src/gz secubox http://$wan_ip/secubox-feed"
|
|
fi
|
|
echo ""
|
|
echo "Remote hosted feed:"
|
|
echo " src/gz secubox $REMOTE_FEED_URL"
|
|
}
|
|
|
|
# ==================== P2P PEER FUNCTIONS ====================
|
|
|
|
# List mesh peers with active package feeds
|
|
list_feed_peers() {
|
|
log "Discovering mesh peers with active feeds..."
|
|
|
|
local result=$(curl -s "http://127.0.0.1:7331/api/factory/packages-sync?refresh=0" 2>/dev/null)
|
|
|
|
if [ -z "$result" ]; then
|
|
error "Failed to query P2P API. Is secubox-p2p running?"
|
|
return 1
|
|
fi
|
|
|
|
local sources=$(echo "$result" | jsonfilter -e '@.sources[*]' 2>/dev/null | wc -l)
|
|
|
|
if [ "$sources" -eq 0 ]; then
|
|
log "No peers with active feeds found"
|
|
return 0
|
|
fi
|
|
|
|
printf "%-20s %-15s %-10s %s\n" "NAME" "ADDRESS" "PACKAGES" "STATUS"
|
|
printf "%-20s %-15s %-10s %s\n" "----" "-------" "--------" "------"
|
|
|
|
local i=0
|
|
while [ $i -lt $sources ]; do
|
|
local node_name=$(echo "$result" | jsonfilter -e "@.sources[$i].node_name" 2>/dev/null)
|
|
local address=$(echo "$result" | jsonfilter -e "@.sources[$i].address" 2>/dev/null)
|
|
local pkg_count=$(echo "$result" | jsonfilter -e "@.sources[$i].package_count" 2>/dev/null)
|
|
local status=$(echo "$result" | jsonfilter -e "@.sources[$i].status" 2>/dev/null)
|
|
local type=$(echo "$result" | jsonfilter -e "@.sources[$i].type" 2>/dev/null)
|
|
|
|
[ "$type" = "local" ] && address="(local)"
|
|
|
|
printf "%-20s %-15s %-10s %s\n" "$node_name" "$address" "${pkg_count:-0}" "$status"
|
|
i=$((i + 1))
|
|
done
|
|
|
|
local total=$(echo "$result" | jsonfilter -e '@.sync_stats.total_packages' 2>/dev/null)
|
|
echo ""
|
|
log "Total packages across all sources: ${total:-0}"
|
|
}
|
|
|
|
# Search for package across local and all peers
|
|
search_package() {
|
|
local pkg_name="$1"
|
|
[ -z "$pkg_name" ] && { error "Package name required"; return 1; }
|
|
|
|
log "Searching for '$pkg_name' across local and peers..."
|
|
|
|
# First check local
|
|
if [ -f "$FEED_DIR/Packages" ]; then
|
|
local local_version=$(awk -v pkg="$pkg_name" '
|
|
/^Package:/ { current_pkg = $2 }
|
|
/^Version:/ { if (current_pkg == pkg) print $2 }
|
|
' "$FEED_DIR/Packages")
|
|
|
|
if [ -n "$local_version" ]; then
|
|
printf "%-15s %-20s %s\n" "LOCAL" "(this node)" "$local_version"
|
|
fi
|
|
fi
|
|
|
|
# Search peers
|
|
local result=$(curl -s "http://127.0.0.1:7331/api/factory/packages-sync" 2>/dev/null)
|
|
local sources=$(echo "$result" | jsonfilter -e '@.sources[*]' 2>/dev/null | wc -l)
|
|
|
|
local i=0
|
|
while [ $i -lt $sources ]; do
|
|
local type=$(echo "$result" | jsonfilter -e "@.sources[$i].type" 2>/dev/null)
|
|
[ "$type" = "local" ] && { i=$((i + 1)); continue; }
|
|
|
|
local node_name=$(echo "$result" | jsonfilter -e "@.sources[$i].node_name" 2>/dev/null)
|
|
local address=$(echo "$result" | jsonfilter -e "@.sources[$i].address" 2>/dev/null)
|
|
local packages=$(echo "$result" | jsonfilter -e "@.sources[$i].packages" 2>/dev/null)
|
|
|
|
# Search in packages array
|
|
local pkg_count=$(echo "$packages" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
|
|
local j=0
|
|
while [ $j -lt $pkg_count ]; do
|
|
local name=$(echo "$packages" | jsonfilter -e "@[$j].name" 2>/dev/null)
|
|
if [ "$name" = "$pkg_name" ]; then
|
|
local version=$(echo "$packages" | jsonfilter -e "@[$j].version" 2>/dev/null)
|
|
printf "%-15s %-20s %s\n" "PEER" "$address" "$version"
|
|
break
|
|
fi
|
|
j=$((j + 1))
|
|
done
|
|
|
|
i=$((i + 1))
|
|
done
|
|
}
|
|
|
|
# Fetch package from specific peer
|
|
fetch_from_peer() {
|
|
local pkg_name="$1"
|
|
local peer_addr="$2"
|
|
|
|
[ -z "$pkg_name" ] && { error "Package name required"; return 1; }
|
|
[ -z "$peer_addr" ] && { error "Peer address required"; return 1; }
|
|
|
|
log "Fetching '$pkg_name' from $peer_addr..."
|
|
|
|
local result=$(ubus call luci.secubox-p2p fetch_package \
|
|
"{\"package\":\"$pkg_name\",\"peer_addr\":\"$peer_addr\"}" 2>/dev/null)
|
|
|
|
if [ -z "$result" ]; then
|
|
error "Failed to fetch package. Is secubox-p2p running?"
|
|
return 1
|
|
fi
|
|
|
|
local success=$(echo "$result" | jsonfilter -e '@.success' 2>/dev/null)
|
|
if [ "$success" = "true" ]; then
|
|
local filename=$(echo "$result" | jsonfilter -e '@.filename' 2>/dev/null)
|
|
log "Successfully fetched: $filename"
|
|
log "Run 'secubox-feed update' to refresh local index"
|
|
return 0
|
|
else
|
|
local err=$(echo "$result" | jsonfilter -e '@.error' 2>/dev/null)
|
|
error "Failed: ${err:-unknown error}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Fetch package from first available peer
|
|
fetch_from_any() {
|
|
local pkg_name="$1"
|
|
[ -z "$pkg_name" ] && { error "Package name required"; return 1; }
|
|
|
|
log "Searching for '$pkg_name' on any peer..."
|
|
|
|
local result=$(ubus call luci.secubox-p2p fetch_package \
|
|
"{\"package\":\"$pkg_name\"}" 2>/dev/null)
|
|
|
|
if [ -z "$result" ]; then
|
|
error "Failed to fetch package. Is secubox-p2p running?"
|
|
return 1
|
|
fi
|
|
|
|
local success=$(echo "$result" | jsonfilter -e '@.success' 2>/dev/null)
|
|
if [ "$success" = "true" ]; then
|
|
local source=$(echo "$result" | jsonfilter -e '@.source' 2>/dev/null)
|
|
local filename=$(echo "$result" | jsonfilter -e '@.filename' 2>/dev/null)
|
|
log "Successfully fetched from $source: $filename"
|
|
return 0
|
|
else
|
|
local err=$(echo "$result" | jsonfilter -e '@.error' 2>/dev/null)
|
|
error "Failed: ${err:-unknown error}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Sync package catalogs from all peers
|
|
sync_peer_catalogs() {
|
|
log "Syncing package catalogs from all mesh peers..."
|
|
|
|
local result=$(curl -s "http://127.0.0.1:7331/api/factory/packages-sync?refresh=1" 2>/dev/null)
|
|
|
|
if [ -z "$result" ]; then
|
|
error "Failed to sync. Is secubox-p2p running?"
|
|
return 1
|
|
fi
|
|
|
|
local peers_synced=$(echo "$result" | jsonfilter -e '@.sync_stats.peers_synced' 2>/dev/null)
|
|
local peers_failed=$(echo "$result" | jsonfilter -e '@.sync_stats.peers_failed' 2>/dev/null)
|
|
local total=$(echo "$result" | jsonfilter -e '@.sync_stats.total_packages' 2>/dev/null)
|
|
|
|
log "Sync complete:"
|
|
log " Peers synced: ${peers_synced:-0}"
|
|
log " Peers failed: ${peers_failed:-0}"
|
|
log " Total packages: ${total:-0}"
|
|
}
|
|
|
|
# Auto-install content packages from mesh peers
|
|
sync_content() {
|
|
log "Syncing content packages from mesh..."
|
|
|
|
# First sync catalogs
|
|
sync_peer_catalogs
|
|
|
|
# Get list of content packages from peers
|
|
local result=$(curl -s "http://127.0.0.1:7331/api/factory/packages-sync" 2>/dev/null)
|
|
[ -z "$result" ] && return 1
|
|
|
|
# Extract content packages (secubox-site-* and secubox-streamlit-*)
|
|
local content_pkgs=$(echo "$result" | jsonfilter -e '@.sources[*].packages[*].name' 2>/dev/null | \
|
|
grep -E "^secubox-(site|streamlit)-" | sort -u)
|
|
|
|
if [ -z "$content_pkgs" ]; then
|
|
log "No content packages found on mesh peers"
|
|
return 0
|
|
fi
|
|
|
|
local installed=0
|
|
local failed=0
|
|
|
|
for pkg in $content_pkgs; do
|
|
# Check if already installed
|
|
if opkg status "$pkg" 2>/dev/null | grep -q "Status:.*installed"; then
|
|
[ "$VERBOSE" = "1" ] && log " Already installed: $pkg"
|
|
continue
|
|
fi
|
|
|
|
log " Installing: $pkg"
|
|
|
|
# Find which peer has it and fetch
|
|
if fetch_from_any "$pkg" >/dev/null 2>&1; then
|
|
# Install
|
|
opkg install "$FEED_DIR/${pkg}"*.ipk 2>/dev/null && {
|
|
installed=$((installed + 1))
|
|
log " Installed: $pkg"
|
|
} || {
|
|
failed=$((failed + 1))
|
|
error " Failed to install: $pkg"
|
|
}
|
|
else
|
|
failed=$((failed + 1))
|
|
error " Failed to fetch: $pkg"
|
|
fi
|
|
done
|
|
|
|
log "Content sync complete: $installed installed, $failed failed"
|
|
}
|
|
|
|
# ==================== END P2P FUNCTIONS ====================
|
|
|
|
# Main
|
|
main() {
|
|
local cmd=""
|
|
VERBOSE=0
|
|
QUIET=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-h|--help) usage; exit 0 ;;
|
|
-v|--verbose) VERBOSE=1; shift ;;
|
|
-q|--quiet) QUIET=1; shift ;;
|
|
*)
|
|
if [ -z "$cmd" ]; then
|
|
cmd="$1"
|
|
else
|
|
break
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Ensure feed directory exists
|
|
mkdir -p "$FEED_DIR"
|
|
|
|
case "$cmd" in
|
|
update|regenerate)
|
|
regenerate_packages
|
|
sync_feed
|
|
;;
|
|
sync)
|
|
sync_feed
|
|
;;
|
|
fetch)
|
|
[ -z "$1" ] && { error "URL required"; exit 1; }
|
|
fetch_ipk "$1"
|
|
;;
|
|
fetch-release|release)
|
|
[ -z "$1" ] && { error "Package name required"; exit 1; }
|
|
if [ -n "$GITEA_API" ]; then
|
|
fetch_gitea_release "$1"
|
|
elif [ -n "$GITHUB_REPO" ]; then
|
|
fetch_github_release "$1"
|
|
else
|
|
error "Set SECUBOX_GITEA_API or SECUBOX_GITHUB_REPO"
|
|
exit 1
|
|
fi
|
|
;;
|
|
list|ls)
|
|
list_packages
|
|
;;
|
|
info|show)
|
|
[ -z "$1" ] && { error "Package name required"; exit 1; }
|
|
package_info "$1"
|
|
;;
|
|
install)
|
|
[ -z "$1" ] && { error "Package name required"; exit 1; }
|
|
install_package "$1"
|
|
;;
|
|
clean)
|
|
clean_old_versions
|
|
;;
|
|
serve)
|
|
serve_feed "$1"
|
|
;;
|
|
export|url)
|
|
export_feed
|
|
;;
|
|
peers)
|
|
list_feed_peers
|
|
;;
|
|
search)
|
|
[ -z "$1" ] && { error "Package name required"; exit 1; }
|
|
search_package "$1"
|
|
;;
|
|
fetch-peer)
|
|
[ -z "$1" ] && { error "Package name required"; exit 1; }
|
|
[ -z "$2" ] && { error "Peer address required"; exit 1; }
|
|
fetch_from_peer "$1" "$2"
|
|
;;
|
|
fetch-any)
|
|
[ -z "$1" ] && { error "Package name required"; exit 1; }
|
|
fetch_from_any "$1"
|
|
;;
|
|
sync-peers)
|
|
sync_peer_catalogs
|
|
;;
|
|
sync-content)
|
|
sync_content
|
|
;;
|
|
"")
|
|
usage
|
|
exit 1
|
|
;;
|
|
*)
|
|
error "Unknown command: $cmd"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|