refactor(bonus): Rename luci-app-secubox-bonus to secubox-app-bonus

- Remove all LuCI dependencies (luci-base, rpcd, luci-lib-jsonc)
- Remove LuCI-specific files (RPCD backend, ACL, menu, JS views)
- Package now only provides local opkg feed and documentation
- Remove Packages.sig to avoid signature verification errors
- Update local-build.sh to skip signature generation for local feeds

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-29 07:21:19 +01:00
parent 26b1f476f8
commit 06faaabc19
189 changed files with 71 additions and 678 deletions

View File

@ -168,7 +168,9 @@
"Bash(python3 -m json.tool:*)",
"Bash(git restore:*)",
"Bash(__NEW_LINE_80f7f5dbdf93db8a__ echo \"\")",
"Bash(# Check for other service-like apps in other secubox menus echo \"\"=== Mitmproxy location ===\"\" grep -h ''\"\"admin/'' package/secubox/luci-app-mitmproxy/root/usr/share/luci/menu.d/*.json)"
"Bash(# Check for other service-like apps in other secubox menus echo \"\"=== Mitmproxy location ===\"\" grep -h ''\"\"admin/'' package/secubox/luci-app-mitmproxy/root/usr/share/luci/menu.d/*.json)",
"Bash(ar -p:*)",
"WebFetch(domain:openwrt.org)"
]
}
}

View File

@ -1,64 +0,0 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-secubox-bonus
PKG_VERSION:=0.2.0
PKG_RELEASE:=2
PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
LUCI_TITLE:=LuCI - SecuBox Bonus Content & Local Package Store
LUCI_DESCRIPTION:=SecuBox documentation, local package repository, and app store. Includes all SecuBox packages as a local opkg feed for offline installation. Accessible at /luci-static/secubox/
LUCI_DEPENDS:=+luci-base +rpcd +luci-lib-jsonc
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
define Package/luci-app-secubox-bonus/conffiles
/etc/opkg/customfeeds.conf
endef
define Package/luci-app-secubox-bonus/install
# Documentation and static content
$(INSTALL_DIR) $(1)/www/luci-static/secubox
$(CP) ./htdocs/luci-static/secubox/* $(1)/www/luci-static/secubox/
# Local package feed (populated by build)
$(INSTALL_DIR) $(1)/www/secubox-feed
if [ -d ./root/www/secubox-feed ] && [ -n "$$$$(ls -A ./root/www/secubox-feed 2>/dev/null)" ]; then \
$(CP) ./root/www/secubox-feed/* $(1)/www/secubox-feed/; \
fi
# opkg custom feeds configuration
$(INSTALL_DIR) $(1)/etc/opkg
$(INSTALL_CONF) ./root/etc/opkg/customfeeds.conf $(1)/etc/opkg/customfeeds.conf
# RPCD backend for package management
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-store $(1)/usr/libexec/rpcd/
# ACL permissions
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-secubox-bonus.json $(1)/usr/share/rpcd/acl.d/
# LuCI menu entry
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-secubox-bonus.json $(1)/usr/share/luci/menu.d/
# JavaScript view
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/secubox-bonus
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-bonus/*.js $(1)/www/luci-static/resources/view/secubox-bonus/
endef
define Package/luci-app-secubox-bonus/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
# Restart rpcd to load new backend
/etc/init.d/rpcd restart
rm -rf /tmp/luci-modulecache /tmp/luci-indexcache 2>/dev/null
echo "SecuBox Bonus & Package Store installed."
}
exit 0
endef
# call BuildPackage - OpenWrt buildroot

View File

@ -1,287 +0,0 @@
'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
var callListPackages = rpc.declare({
object: 'luci.secubox-store',
method: 'list_packages',
expect: { packages: [] }
});
var callInstallPackage = rpc.declare({
object: 'luci.secubox-store',
method: 'install_package',
params: ['package'],
expect: { success: false }
});
var callRemovePackage = rpc.declare({
object: 'luci.secubox-store',
method: 'remove_package',
params: ['package'],
expect: { success: false }
});
var callGetFeedStatus = rpc.declare({
object: 'luci.secubox-store',
method: 'get_feed_status',
expect: {}
});
// Icon mapping
var iconMap = {
'shield': '\u{1F6E1}',
'lock': '\u{1F512}',
'activity': '\u{1F4CA}',
'filter': '\u{1F50D}',
'users': '\u{1F465}',
'wifi': '\u{1F4F6}',
'server': '\u{1F5A5}',
'box': '\u{1F4E6}',
'radio': '\u{1F4FB}',
'message-square': '\u{1F4AC}',
'eye': '\u{1F441}',
'bar-chart-2': '\u{1F4CA}',
'settings': '\u{2699}',
'globe': '\u{1F310}',
'cpu': '\u{1F4BB}',
'film': '\u{1F3AC}',
'monitor': '\u{1F5B5}',
'key': '\u{1F511}',
'palette': '\u{1F3A8}',
'package': '\u{1F4E6}'
};
// Category colors
var categoryColors = {
'security': '#e74c3c',
'network': '#3498db',
'vpn': '#9b59b6',
'iot': '#27ae60',
'monitoring': '#f39c12',
'system': '#34495e',
'media': '#e91e63',
'theme': '#00bcd4',
'secubox': '#2ecc71',
'utility': '#95a5a6'
};
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function getIcon(iconName) {
return iconMap[iconName] || '\u{1F4E6}';
}
function getCategoryColor(category) {
return categoryColors[category] || '#95a5a6';
}
return view.extend({
load: function() {
return Promise.all([
callListPackages(),
callGetFeedStatus()
]);
},
renderPackageCard: function(pkg) {
var self = this;
var icon = getIcon(pkg.icon);
var color = getCategoryColor(pkg.category);
var card = E('div', { 'class': 'package-card', 'data-category': pkg.category }, [
E('div', { 'class': 'package-header' }, [
E('span', { 'class': 'package-icon', 'style': 'background-color: ' + color }, icon),
E('div', { 'class': 'package-title' }, [
E('h3', {}, pkg.name),
E('span', { 'class': 'package-version' }, 'v' + pkg.version)
])
]),
E('p', { 'class': 'package-description' }, pkg.description || 'SecuBox package'),
E('div', { 'class': 'package-meta' }, [
E('span', { 'class': 'package-category' }, pkg.category),
E('span', { 'class': 'package-size' }, formatSize(pkg.size || 0))
]),
E('div', { 'class': 'package-actions' }, [
pkg.installed
? E('button', {
'class': 'btn cbi-button cbi-button-remove',
'click': ui.createHandlerFn(self, 'handleRemove', pkg.name)
}, 'Remove')
: E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': ui.createHandlerFn(self, 'handleInstall', pkg.name)
}, 'Install'),
pkg.installed
? E('span', { 'class': 'status-installed' }, '\u2713 Installed')
: E('span', { 'class': 'status-available' }, 'Available')
])
]);
return card;
},
handleInstall: function(pkgName, ev) {
var btn = ev.currentTarget;
btn.disabled = true;
btn.textContent = 'Installing...';
return callInstallPackage(pkgName).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', {}, 'Package ' + pkgName + ' installed successfully. Refreshing...'));
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Failed to install ' + pkgName + ': ' + (result.error || 'Unknown error')), 'error');
btn.disabled = false;
btn.textContent = 'Install';
}
}).catch(function(err) {
ui.addNotification(null, E('p', {}, 'Error: ' + err.message), 'error');
btn.disabled = false;
btn.textContent = 'Install';
});
},
handleRemove: function(pkgName, ev) {
var btn = ev.currentTarget;
if (!confirm('Remove package ' + pkgName + '?'))
return;
btn.disabled = true;
btn.textContent = 'Removing...';
return callRemovePackage(pkgName).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', {}, 'Package ' + pkgName + ' removed successfully. Refreshing...'));
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Failed to remove ' + pkgName + ': ' + (result.error || 'Unknown error')), 'error');
btn.disabled = false;
btn.textContent = 'Remove';
}
}).catch(function(err) {
ui.addNotification(null, E('p', {}, 'Error: ' + err.message), 'error');
btn.disabled = false;
btn.textContent = 'Remove';
});
},
filterPackages: function(category) {
var cards = document.querySelectorAll('.package-card');
cards.forEach(function(card) {
if (category === 'all' || card.dataset.category === category) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
var btns = document.querySelectorAll('.filter-btn');
btns.forEach(function(btn) {
btn.classList.remove('active');
if (btn.dataset.category === category) {
btn.classList.add('active');
}
});
},
render: function(data) {
var packages = data[0].packages || data[0] || [];
var feedStatus = data[1] || {};
var self = this;
// Get unique categories
var categories = ['all'];
packages.forEach(function(pkg) {
if (pkg.category && categories.indexOf(pkg.category) === -1) {
categories.push(pkg.category);
}
});
// Sort packages: installed first, then by name
packages.sort(function(a, b) {
if (a.installed !== b.installed) return b.installed ? 1 : -1;
return a.name.localeCompare(b.name);
});
var installedCount = packages.filter(function(p) { return p.installed; }).length;
var view = E('div', { 'class': 'secubox-store' }, [
E('style', {}, [
'.secubox-store { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }',
'.store-header { margin-bottom: 20px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white; }',
'.store-header h2 { margin: 0 0 10px 0; }',
'.store-stats { display: flex; gap: 20px; }',
'.store-stats span { background: rgba(255,255,255,0.2); padding: 5px 15px; border-radius: 20px; }',
'.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }',
'.filter-btn { padding: 8px 16px; border: 1px solid #ddd; border-radius: 20px; background: white; cursor: pointer; transition: all 0.2s; }',
'.filter-btn:hover { background: #f0f0f0; }',
'.filter-btn.active { background: #667eea; color: white; border-color: #667eea; }',
'.package-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }',
'.package-card { background: white; border: 1px solid #e0e0e0; border-radius: 10px; padding: 20px; transition: box-shadow 0.2s; }',
'.package-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }',
'.package-header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }',
'.package-icon { width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 24px; color: white; }',
'.package-title h3 { margin: 0; font-size: 16px; }',
'.package-version { color: #888; font-size: 12px; }',
'.package-description { color: #666; font-size: 14px; margin-bottom: 12px; line-height: 1.4; }',
'.package-meta { display: flex; gap: 10px; margin-bottom: 15px; }',
'.package-category { background: #f0f0f0; padding: 3px 10px; border-radius: 12px; font-size: 12px; text-transform: capitalize; }',
'.package-size { color: #888; font-size: 12px; }',
'.package-actions { display: flex; align-items: center; gap: 10px; }',
'.package-actions .btn { padding: 8px 20px; }',
'.status-installed { color: #27ae60; font-weight: 500; }',
'.status-available { color: #888; }',
'.cbi-button-remove { background: #e74c3c !important; border-color: #e74c3c !important; color: white !important; }',
'.cbi-button-remove:hover { background: #c0392b !important; }',
'.no-packages { text-align: center; padding: 40px; color: #888; }'
].join('\n')),
E('div', { 'class': 'store-header' }, [
E('h2', {}, 'SecuBox Package Store'),
E('p', {}, 'Install and manage SecuBox packages from the local repository'),
E('div', { 'class': 'store-stats' }, [
E('span', {}, packages.length + ' packages available'),
E('span', {}, installedCount + ' installed'),
feedStatus.feed_configured
? E('span', {}, '\u2713 Feed configured')
: E('span', {}, '\u26A0 Feed not configured')
])
]),
E('div', { 'class': 'filter-bar' },
categories.map(function(cat) {
return E('button', {
'class': 'filter-btn' + (cat === 'all' ? ' active' : ''),
'data-category': cat,
'click': function() { self.filterPackages(cat); }
}, cat === 'all' ? 'All' : cat.charAt(0).toUpperCase() + cat.slice(1));
})
),
packages.length > 0
? E('div', { 'class': 'package-grid' },
packages.map(function(pkg) {
return self.renderPackageCard(pkg);
})
)
: E('div', { 'class': 'no-packages' }, [
E('p', {}, 'No packages found in local feed.'),
E('p', {}, 'The local package feed may not be populated yet.')
])
]);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -1,274 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# SecuBox Local Package Store - RPCD Backend
# Manages installation/removal of packages from local feed
. /usr/share/libubox/jshn.sh
FEED_DIR="/www/secubox-feed"
APPS_JSON="$FEED_DIR/apps-local.json"
# List available packages from local feed
list_packages() {
if [ -f "$APPS_JSON" ]; then
# Read apps-local.json and add installation status
local packages=$(cat "$APPS_JSON")
json_init
json_add_boolean "success" 1
json_add_string "feed_url" "/secubox-feed"
json_add_array "packages"
# Parse apps-local.json and check each package
local pkg_list=$(jsonfilter -s "$packages" -e '@.packages[*].name' 2>/dev/null)
for name in $pkg_list; do
local pkg_data=$(jsonfilter -s "$packages" -e "@.packages[@.name='$name']" 2>/dev/null)
local version=$(echo "$pkg_data" | jsonfilter -e '@.version' 2>/dev/null)
local filename=$(echo "$pkg_data" | jsonfilter -e '@.filename' 2>/dev/null)
local size=$(echo "$pkg_data" | jsonfilter -e '@.size' 2>/dev/null)
local category=$(echo "$pkg_data" | jsonfilter -e '@.category' 2>/dev/null)
local icon=$(echo "$pkg_data" | jsonfilter -e '@.icon' 2>/dev/null)
local description=$(echo "$pkg_data" | jsonfilter -e '@.description' 2>/dev/null)
# Check if installed
local installed=0
if opkg list-installed 2>/dev/null | grep -q "^${name} "; then
installed=1
fi
json_add_object ""
json_add_string "name" "$name"
json_add_string "version" "$version"
json_add_string "filename" "$filename"
json_add_int "size" "${size:-0}"
json_add_string "category" "$category"
json_add_string "icon" "$icon"
json_add_string "description" "$description"
json_add_boolean "installed" "$installed"
json_close_object
done
json_close_array
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Local feed not found"
json_add_array "packages"
json_close_array
json_dump
fi
}
# Install a package from local feed
install_package() {
local pkg_name="$1"
if [ -z "$pkg_name" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Package name required"
json_dump
return
fi
# Find the package file
local pkg_file=$(ls "$FEED_DIR/${pkg_name}_"*.ipk 2>/dev/null | head -1)
if [ -z "$pkg_file" ] || [ ! -f "$pkg_file" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Package file not found: $pkg_name"
json_dump
return
fi
# Update opkg lists first
opkg update 2>/dev/null || true
# Install the package
local output
output=$(opkg install "$pkg_file" 2>&1)
local result=$?
json_init
if [ $result -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Package installed successfully"
json_add_string "package" "$pkg_name"
# Clear LuCI cache
rm -rf /tmp/luci-modulecache /tmp/luci-indexcache 2>/dev/null
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_add_string "output" "$output"
json_dump
}
# Remove a package
remove_package() {
local pkg_name="$1"
if [ -z "$pkg_name" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Package name required"
json_dump
return
fi
# Check if installed
if ! opkg list-installed 2>/dev/null | grep -q "^${pkg_name} "; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Package not installed: $pkg_name"
json_dump
return
fi
# Remove the package
local output
output=$(opkg remove "$pkg_name" 2>&1)
local result=$?
json_init
if [ $result -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Package removed successfully"
json_add_string "package" "$pkg_name"
# Clear LuCI cache
rm -rf /tmp/luci-modulecache /tmp/luci-indexcache 2>/dev/null
else
json_add_boolean "success" 0
json_add_string "error" "$output"
fi
json_add_string "output" "$output"
json_dump
}
# Get package info
get_package_info() {
local pkg_name="$1"
if [ -z "$pkg_name" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Package name required"
json_dump
return
fi
json_init
json_add_boolean "success" 1
json_add_string "name" "$pkg_name"
# Check if installed
local installed_info=$(opkg list-installed "$pkg_name" 2>/dev/null)
if [ -n "$installed_info" ]; then
json_add_boolean "installed" 1
json_add_string "installed_version" "$(echo "$installed_info" | cut -d' ' -f3)"
else
json_add_boolean "installed" 0
fi
# Get info from feed
if [ -f "$APPS_JSON" ]; then
local pkg_data=$(cat "$APPS_JSON" | jsonfilter -e "@.packages[@.name='$pkg_name']" 2>/dev/null)
if [ -n "$pkg_data" ]; then
json_add_string "feed_version" "$(echo "$pkg_data" | jsonfilter -e '@.version' 2>/dev/null)"
json_add_string "description" "$(echo "$pkg_data" | jsonfilter -e '@.description' 2>/dev/null)"
json_add_string "category" "$(echo "$pkg_data" | jsonfilter -e '@.category' 2>/dev/null)"
json_add_int "size" "$(echo "$pkg_data" | jsonfilter -e '@.size' 2>/dev/null)"
fi
fi
json_dump
}
# Get feed status
get_feed_status() {
json_init
if [ -d "$FEED_DIR" ]; then
json_add_boolean "feed_exists" 1
json_add_string "feed_path" "$FEED_DIR"
local pkg_count=$(ls -1 "$FEED_DIR"/*.ipk 2>/dev/null | wc -l)
json_add_int "package_count" "$pkg_count"
if [ -f "$APPS_JSON" ]; then
json_add_boolean "index_exists" 1
local generated=$(jsonfilter -i "$APPS_JSON" -e '@.generated' 2>/dev/null)
json_add_string "generated" "$generated"
else
json_add_boolean "index_exists" 0
fi
# Check if feed is in opkg config
if grep -q "secubox-feed" /etc/opkg/customfeeds.conf 2>/dev/null; then
json_add_boolean "feed_configured" 1
else
json_add_boolean "feed_configured" 0
fi
else
json_add_boolean "feed_exists" 0
fi
json_dump
}
# Main dispatcher
case "$1" in
list)
json_init
json_add_object "list_packages"
json_close_object
json_add_object "install_package"
json_add_string "package" "str"
json_close_object
json_add_object "remove_package"
json_add_string "package" "str"
json_close_object
json_add_object "get_package_info"
json_add_string "package" "str"
json_close_object
json_add_object "get_feed_status"
json_close_object
json_dump
;;
call)
case "$2" in
list_packages)
list_packages
;;
install_package)
read -r input
pkg=$(echo "$input" | jsonfilter -e '@.package')
install_package "$pkg"
;;
remove_package)
read -r input
pkg=$(echo "$input" | jsonfilter -e '@.package')
remove_package "$pkg"
;;
get_package_info)
read -r input
pkg=$(echo "$input" | jsonfilter -e '@.package')
get_package_info "$pkg"
;;
get_feed_status)
get_feed_status
;;
*)
echo '{"error":"Unknown method"}'
;;
esac
;;
*)
echo '{"error":"Invalid action"}'
;;
esac

View File

@ -1,14 +0,0 @@
{
"admin/secubox/local-packages": {
"title": "Local Packages",
"order": 19,
"action": {
"type": "view",
"path": "secubox-bonus/store"
},
"depends": {
"acl": ["luci-app-secubox-bonus"],
"uci": {}
}
}
}

View File

@ -1,22 +0,0 @@
{
"luci-app-secubox-bonus": {
"description": "Grant access to SecuBox local package store",
"read": {
"ubus": {
"luci.secubox-store": [
"list_packages",
"get_package_info",
"get_feed_status"
]
}
},
"write": {
"ubus": {
"luci.secubox-store": [
"install_package",
"remove_package"
]
}
}
}
}

View File

@ -1 +0,0 @@
1ae1c0208ec6763082b99daa513443048c8ac971c285cafa3d1d115b48c69f01

View File

@ -0,0 +1,57 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-app-bonus
PKG_VERSION:=0.3.0
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
include $(INCLUDE_DIR)/package.mk
define Package/secubox-app-bonus
SECTION:=secubox
CATEGORY:=SecuBox
TITLE:=SecuBox Local Package Feed & Documentation
PKGARCH:=all
endef
define Package/secubox-app-bonus/description
SecuBox local package repository for offline installation.
Provides pre-built SecuBox packages via opkg local feed at /www/secubox-feed/.
Also includes SecuBox documentation at /www/luci-static/secubox/.
endef
define Package/secubox-app-bonus/conffiles
/etc/opkg/customfeeds.conf
endef
define Build/Compile
endef
define Package/secubox-app-bonus/install
# Documentation and static content
$(INSTALL_DIR) $(1)/www/luci-static/secubox
$(CP) ./htdocs/luci-static/secubox/* $(1)/www/luci-static/secubox/
# Local package feed (populated by build)
$(INSTALL_DIR) $(1)/www/secubox-feed
if [ -d ./root/www/secubox-feed ] && [ -n "$$$$(ls -A ./root/www/secubox-feed 2>/dev/null)" ]; then \
$(CP) ./root/www/secubox-feed/* $(1)/www/secubox-feed/; \
fi
# opkg custom feeds configuration
$(INSTALL_DIR) $(1)/etc/opkg
$(INSTALL_CONF) ./root/etc/opkg/customfeeds.conf $(1)/etc/opkg/customfeeds.conf
endef
define Package/secubox-app-bonus/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
# Update opkg package lists to include the new feed
opkg update 2>/dev/null || true
echo "SecuBox local package feed installed at /www/secubox-feed/"
}
exit 0
endef
$(eval $(call BuildPackage,secubox-app-bonus))

Some files were not shown because too many files have changed in this diff Show More