#!/bin/sh # SecuBox Content Packager # Package Metablogizer sites and Streamlit apps as IPKs for P2P distribution FEED_PATH="/www/secubox-feed" SITES_PATH="/srv/metablogizer" STREAMLIT_PATH="/srv/streamlit/apps" TMP_DIR="/tmp/content-pkg" usage() { cat <<'USAGE' Usage: secubox-content-pkg [args] Commands: site [domain] Package Metablogizer site as IPK streamlit [port] Package Streamlit app as IPK list List content packages in feed remove Remove content package from feed rebuild-index Rebuild Packages index USAGE } log_info() { echo "[INFO] $*"; logger -t content-pkg "$*"; } log_error() { echo "[ERROR] $*" >&2; logger -t content-pkg -p err "$*"; } ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } # Get next available port for Streamlit get_next_port() { local base_port=8501 local port=$base_port while [ $port -lt 8600 ]; do if ! uci show streamlit 2>/dev/null | grep -q "port='$port'"; then echo $port return fi port=$((port + 1)) done echo $base_port } # Build IPK from directory build_ipk() { local pkg_dir="$1" local output_dir="$2" local pkg_name=$(grep "^Package:" "$pkg_dir/CONTROL/control" | cut -d: -f2 | tr -d ' ') local version=$(grep "^Version:" "$pkg_dir/CONTROL/control" | cut -d: -f2 | tr -d ' ') local arch=$(grep "^Architecture:" "$pkg_dir/CONTROL/control" | cut -d: -f2 | tr -d ' ') local ipk_name="${pkg_name}_${version}_${arch}.ipk" cd "$pkg_dir" || return 1 # Create data.tar.gz (exclude CONTROL) tar --exclude='CONTROL' -czf "$TMP_DIR/data.tar.gz" ./* 2>/dev/null # Create control.tar.gz tar -czf "$TMP_DIR/control.tar.gz" -C CONTROL . 2>/dev/null # Create debian-binary echo "2.0" > "$TMP_DIR/debian-binary" # Create IPK (ar archive) cd "$TMP_DIR" tar -czf "$output_dir/$ipk_name" debian-binary control.tar.gz data.tar.gz 2>/dev/null || \ ar -r "$output_dir/$ipk_name" debian-binary control.tar.gz data.tar.gz 2>/dev/null # Cleanup temp files rm -f "$TMP_DIR/data.tar.gz" "$TMP_DIR/control.tar.gz" "$TMP_DIR/debian-binary" echo "$ipk_name" } # Rebuild Packages index rebuild_index() { cd "$FEED_PATH" || return 1 log_info "Rebuilding Packages index..." # Generate Packages file > Packages for ipk in *.ipk; do [ -f "$ipk" ] || continue # Extract control info local tmpctl="/tmp/ctl-$$" mkdir -p "$tmpctl" # Try tar first (our format), then ar (standard opkg) tar -xzf "$ipk" -C "$tmpctl" control.tar.gz 2>/dev/null && \ tar -xzf "$tmpctl/control.tar.gz" -C "$tmpctl" 2>/dev/null if [ ! -f "$tmpctl/control" ]; then # Standard ar format cd "$tmpctl" ar -x "../$ipk" control.tar.gz 2>/dev/null tar -xzf control.tar.gz 2>/dev/null cd "$FEED_PATH" fi if [ -f "$tmpctl/control" ]; then cat "$tmpctl/control" >> Packages echo "Filename: $ipk" >> Packages echo "Size: $(stat -c%s "$ipk" 2>/dev/null || wc -c < "$ipk")" >> Packages echo "SHA256sum: $(sha256sum "$ipk" | cut -d' ' -f1)" >> Packages echo "" >> Packages fi rm -rf "$tmpctl" done # Compress gzip -kf Packages 2>/dev/null log_info "Index rebuilt: $(grep -c "^Package:" Packages) packages" } # ---------- Site Packaging ---------- pkg_site() { local name="$1" local domain="${2:-${name}.secubox.local}" local site_path="$SITES_PATH/$name/public" local version="1.0.0-$(date +%Y%m%d%H%M)" local pkg_name="secubox-site-${name}" local pkg_dir="$TMP_DIR/$pkg_name" # Validate if [ ! -d "$site_path" ]; then log_error "Site not found: $site_path" log_error "Run 'metablogizerctl build $name' first" return 1 fi log_info "Packaging site: $name" # Create package structure ensure_dir "$pkg_dir/www/sites/$name" ensure_dir "$pkg_dir/CONTROL" ensure_dir "$FEED_PATH" # Copy site files cp -a "$site_path"/* "$pkg_dir/www/sites/$name/" || { log_error "Failed to copy site files" return 1 } # Calculate installed size local installed_size=$(du -sk "$pkg_dir/www" | cut -f1) # Create control file cat > "$pkg_dir/CONTROL/control" < "$pkg_dir/CONTROL/postinst" </dev/null 2>&1; then # Add vhost pointing to uhttpd haproxyctl add-vhost "\$SITE_DOMAIN" "127.0.0.1:80" "/sites/\$SITE_NAME" 2>/dev/null || true fi # Log installation logger -t content-pkg "Installed site: \$SITE_NAME at /www/sites/\$SITE_NAME" exit 0 EOF chmod +x "$pkg_dir/CONTROL/postinst" # Create prerm cat > "$pkg_dir/CONTROL/prerm" </dev/null 2>&1; then haproxyctl remove-vhost "\$SITE_DOMAIN" 2>/dev/null || true fi exit 0 EOF chmod +x "$pkg_dir/CONTROL/prerm" # Build IPK local ipk_file=$(build_ipk "$pkg_dir" "$FEED_PATH") # Cleanup rm -rf "$pkg_dir" if [ -n "$ipk_file" ] && [ -f "$FEED_PATH/$ipk_file" ]; then log_info "Created: $FEED_PATH/$ipk_file" rebuild_index return 0 else log_error "Failed to create IPK" return 1 fi } # ---------- Streamlit Packaging ---------- pkg_streamlit() { local name="$1" local port="${2:-$(get_next_port)}" local app_path="$STREAMLIT_PATH/$name" local version="1.0.0-$(date +%Y%m%d%H%M)" local pkg_name="secubox-streamlit-${name}" local pkg_dir="$TMP_DIR/$pkg_name" # Validate if [ ! -d "$app_path" ]; then log_error "Streamlit app not found: $app_path" return 1 fi # Find main app file local app_file="" for f in "$app_path/app.py" "$app_path/main.py" "$app_path"/*.py; do if [ -f "$f" ]; then app_file=$(basename "$f") break fi done if [ -z "$app_file" ]; then log_error "No Python file found in $app_path" return 1 fi log_info "Packaging Streamlit app: $name (port $port)" # Create package structure ensure_dir "$pkg_dir/srv/streamlit/apps/$name" ensure_dir "$pkg_dir/CONTROL" ensure_dir "$FEED_PATH" # Copy app files cp -a "$app_path"/* "$pkg_dir/srv/streamlit/apps/$name/" || { log_error "Failed to copy app files" return 1 } # Calculate installed size local installed_size=$(du -sk "$pkg_dir/srv" | cut -f1) # Create control file cat > "$pkg_dir/CONTROL/control" < "$pkg_dir/CONTROL/postinst" </dev/null || /etc/init.d/streamlit restart 2>/dev/null fi # Log logger -t content-pkg "Installed Streamlit app: \$APP_NAME on port \$APP_PORT" exit 0 EOF chmod +x "$pkg_dir/CONTROL/postinst" # Create prerm cat > "$pkg_dir/CONTROL/prerm" </dev/null uci commit streamlit # Restart Streamlit if [ -x /etc/init.d/streamlit ]; then /etc/init.d/streamlit reload 2>/dev/null fi exit 0 EOF chmod +x "$pkg_dir/CONTROL/prerm" # Build IPK local ipk_file=$(build_ipk "$pkg_dir" "$FEED_PATH") # Cleanup rm -rf "$pkg_dir" if [ -n "$ipk_file" ] && [ -f "$FEED_PATH/$ipk_file" ]; then log_info "Created: $FEED_PATH/$ipk_file" rebuild_index return 0 else log_error "Failed to create IPK" return 1 fi } # ---------- List/Remove ---------- cmd_list() { echo "Content Packages in Feed:" echo "=========================" cd "$FEED_PATH" 2>/dev/null || { echo "Feed not found"; return 1; } local found=0 for ipk in secubox-site-*.ipk secubox-streamlit-*.ipk; do [ -f "$ipk" ] || continue found=1 local name=$(echo "$ipk" | sed 's/_[0-9].*$//') local size=$(ls -lh "$ipk" | awk '{print $5}') if echo "$ipk" | grep -q "secubox-site-"; then printf " [SITE] %-30s %s\n" "$name" "$size" else printf " [STREAMLIT] %-30s %s\n" "$name" "$size" fi done [ "$found" = "0" ] && echo " (no content packages)" } cmd_remove() { local pkg_name="$1" [ -z "$pkg_name" ] && { echo "Usage: secubox-content-pkg remove "; return 1; } cd "$FEED_PATH" 2>/dev/null || { log_error "Feed not found"; return 1; } # Find matching IPK local found="" for ipk in "${pkg_name}"*.ipk "${pkg_name}_"*.ipk; do if [ -f "$ipk" ]; then found="$ipk" break fi done if [ -z "$found" ]; then log_error "Package not found: $pkg_name" return 1 fi rm -f "$found" log_info "Removed: $found" rebuild_index } # ---------- Main ---------- ensure_dir "$TMP_DIR" case "$1" in site) shift pkg_site "$@" ;; streamlit) shift pkg_streamlit "$@" ;; list) cmd_list ;; remove) shift cmd_remove "$@" ;; rebuild-index) rebuild_index ;; *) usage exit 1 ;; esac