#!/bin/sh # SecuBox Feed Manager # Manages local and remote package feeds for SecuBox appstore # Copyright 2026 CyberMind 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 [options] Commands: update Update local feed from installed IPKs sync Sync feed to /var/opkg-lists for opkg fetch Fetch IPK from URL and add to local feed fetch-release Fetch latest release from GitHub/Gitea list List packages in local feed info Show package info install Install package from local feed (force-depends) clean Remove old package versions serve Start simple HTTP server for WAN access export Export feed URL for sharing 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 with force-depends install_package() { local pkg="$1" local ipk_file="$FEED_DIR/${pkg}_"*.ipk # Find the IPK file local found=$(ls $ipk_file 2>/dev/null | head -1) if [ -n "$found" ]; then log "Installing $pkg..." opkg install --force-depends "$found" else error "Package $pkg not found in local feed" return 1 fi } # 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 ''):$port/secubox-feed/" log "Add to remote opkg: src/gz secubox http://:$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" } # 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 ;; "") usage exit 1 ;; *) error "Unknown command: $cmd" usage exit 1 ;; esac } main "$@"