secubox-openwrt/package/secubox/secubox-app-bonus/root/usr/sbin/secubox-feed
CyberMind-FR 6f5f9d77c8 feat(bonus): Add 'secubox-feed install all' command
Installs all packages from the local feed in dependency order:
1. secubox-core and secubox-app (base)
2. secubox-app-* backend packages
3. luci-app-* frontend packages
4. luci-theme-* themes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 19:46:28 +01:00

458 lines
13 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
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"
}
# 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 "$@"