From 3132ef9c143d890b97f9e05b5600efdf21ab0c3e Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 23 Jan 2026 05:44:38 +0100 Subject: [PATCH] go --- .claude/settings.local.json | 12 +- package/secubox/luci-app-magicmirror/Makefile | 23 - .../resources/view/magicmirror/config.js | 212 -------- .../resources/view/magicmirror/modules.js | 282 ---------- .../resources/view/magicmirror/overview.js | 180 ------- .../root/usr/libexec/rpcd/luci.magicmirror | 308 ----------- .../luci/menu.d/luci-app-magicmirror.json | 37 -- .../rpcd/acl.d/luci-app-magicmirror.json | 17 - .../README.md | 0 .../secubox/secubox-app-magicmirror/Makefile | 45 -- .../files/etc/config/magicmirror | 10 - .../files/etc/init.d/magicmirror | 23 - .../files/usr/sbin/magicmirrorctl | 483 ------------------ package/secubox/secubox-core/Makefile | 6 +- .../root/usr/sbin/secubox-component | 3 +- .../secubox-core/root/usr/sbin/secubox-core | 3 +- .../secubox-core/root/usr/sbin/secubox-state | 3 +- .../root/usr/sbin/secubox-sync-registry | 49 +- secubox-tools/local-build.sh | 5 + secubox-tools/scripts/expand-openwrt-image.sh | 197 +++++++ 20 files changed, 241 insertions(+), 1657 deletions(-) delete mode 100644 package/secubox/luci-app-magicmirror/Makefile delete mode 100644 package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/config.js delete mode 100644 package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/modules.js delete mode 100644 package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/overview.js delete mode 100755 package/secubox/luci-app-magicmirror/root/usr/libexec/rpcd/luci.magicmirror delete mode 100644 package/secubox/luci-app-magicmirror/root/usr/share/luci/menu.d/luci-app-magicmirror.json delete mode 100644 package/secubox/luci-app-magicmirror/root/usr/share/rpcd/acl.d/luci-app-magicmirror.json rename package/secubox/{luci-app-magicmirror => luci-app-magicmirror2}/README.md (100%) delete mode 100644 package/secubox/secubox-app-magicmirror/Makefile delete mode 100644 package/secubox/secubox-app-magicmirror/files/etc/config/magicmirror delete mode 100755 package/secubox/secubox-app-magicmirror/files/etc/init.d/magicmirror delete mode 100644 package/secubox/secubox-app-magicmirror/files/usr/sbin/magicmirrorctl create mode 100755 secubox-tools/scripts/expand-openwrt-image.sh diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7292a2f6..c603d1ab 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -107,7 +107,17 @@ "WebFetch(domain:hub.docker.com)", "WebFetch(domain:localai.io)", "WebFetch(domain:downloads.lms-community.org)", - "Bash(./secubox-tools/sdk/build-package.sh:*)" + "Bash(./secubox-tools/sdk/build-package.sh:*)", + "Bash(./secubox-tools/scripts/expand-openwrt-image.sh:*)", + "Bash(parted:*)", + "Bash(fdisk:*)", + "Bash(sudo ./secubox-tools/scripts/expand-openwrt-image.sh:*)", + "Bash(gunzip:*)", + "Bash(xxd:*)", + "Bash(sfdisk:*)", + "Bash(xzcat:*)", + "Bash(head:*)", + "Bash(docker search:*)" ] } } diff --git a/package/secubox/luci-app-magicmirror/Makefile b/package/secubox/luci-app-magicmirror/Makefile deleted file mode 100644 index 852633ee..00000000 --- a/package/secubox/luci-app-magicmirror/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (C) 2024 CyberMind.fr -# Licensed under Apache-2.0 - -include $(TOPDIR)/rules.mk - -PKG_NAME:=luci-app-magicmirror -PKG_VERSION:=1.0.0 -PKG_RELEASE:=2 -PKG_ARCH:=all -PKG_LICENSE:=Apache-2.0 -PKG_MAINTAINER:=CyberMind - -LUCI_TITLE:=MagicMirror² Manager -LUCI_DESCRIPTION:=Web-based module manager and controller for MagicMirror² smart mirror platform -LUCI_DEPENDS:=+luci-base +rpcd +secubox-app-magicmirror +jq -LUCI_PKGARCH:=all - -# File permissions -PKG_FILE_MODES:=/usr/libexec/rpcd/luci.magicmirror:root:root:755 - -include $(TOPDIR)/feeds/luci/luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/config.js b/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/config.js deleted file mode 100644 index 6652733d..00000000 --- a/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/config.js +++ /dev/null @@ -1,212 +0,0 @@ -'use strict'; -'require view'; -'require rpc'; -'require ui'; -'require dom'; - -var callGetConfig = rpc.declare({ - object: 'luci.magicmirror', - method: 'getConfig', - expect: { } -}); - -var callSaveConfig = rpc.declare({ - object: 'luci.magicmirror', - method: 'saveConfig', - params: ['content'], - expect: { } -}); - -var callRestartService = rpc.declare({ - object: 'luci.magicmirror', - method: 'restartService', - expect: { } -}); - -return view.extend({ - load: function() { - return Promise.all([ - callGetConfig() - ]); - }, - - render: function(data) { - var configData = data[0] || {}; - var configContent = configData.content || ''; - var textarea; - - var saveConfig = function() { - var content = textarea.value; - - if (!content.trim()) { - ui.addNotification(null, E('p', _('Configuration cannot be empty')), 'warning'); - return; - } - - // Basic validation - if (!content.includes('let config')) { - if (!confirm(_('Configuration does not contain "let config". Are you sure you want to save this?'))) { - return; - } - } - - ui.showModal(_('Saving Configuration'), [ - E('p', { 'class': 'spinning' }, _('Saving configuration...')) - ]); - - return callSaveConfig(content).then(function(result) { - ui.hideModal(); - if (result.success) { - ui.addNotification(null, E('p', _('Configuration saved successfully')), 'info'); - - if (confirm(_('Configuration saved. Restart MagicMirror to apply changes?'))) { - ui.showModal(_('Restarting Service'), [ - E('p', { 'class': 'spinning' }, _('Restarting MagicMirror²...')) - ]); - - return callRestartService().then(function() { - ui.hideModal(); - ui.addNotification(null, E('p', _('Service restarted. Changes will apply shortly.')), 'info'); - }); - } - } else { - ui.addNotification(null, E('p', _('Failed to save configuration: ') + (result.error || 'Unknown error')), 'error'); - } - }); - }; - - return E('div', { 'class': 'cbi-map' }, [ - E('h2', {}, _('MagicMirror² Configuration')), - E('div', { 'class': 'cbi-section-descr' }, _('Edit the MagicMirror² configuration file (config.js)')), - - E('div', { 'class': 'cbi-section', 'style': 'margin-top: 20px;' }, [ - E('div', { 'class': 'cbi-section-descr', 'style': 'margin-bottom: 15px;' }, [ - E('div', { 'class': 'alert-message info' }, [ - E('h4', { 'style': 'margin: 0 0 10px 0;' }, _('Configuration Tips:')), - E('ul', { 'style': 'margin: 0; padding-left: 20px;' }, [ - E('li', {}, _('Modules are configured in the "modules" array')), - E('li', {}, _('Each module has a "module" name, "position", and optional "config" object')), - E('li', {}, _('Common positions: top_left, top_right, bottom_bar, etc.')), - E('li', {}, _('Save and restart the service to apply changes')), - E('li', {}, _('Always backup your config before making major changes')) - ]) - ]) - ]), - - E('div', { 'class': 'cbi-value', 'style': 'margin-bottom: 15px;' }, [ - E('label', { 'class': 'cbi-value-title' }, _('Configuration')), - E('div', { 'class': 'cbi-value-field' }, [ - textarea = E('textarea', { - 'id': 'config-editor', - 'style': 'width: 100%; min-height: 600px; font-family: monospace; font-size: 13px; line-height: 1.5; padding: 10px; border: 1px solid #ccc; border-radius: 3px;', - 'spellcheck': 'false' - }, configContent) - ]) - ]), - - E('div', { 'class': 'cbi-page-actions' }, [ - E('button', { - 'class': 'cbi-button cbi-button-save', - 'click': saveConfig - }, _('Save Configuration')), - ' ', - E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function() { - if (confirm(_('Discard all changes and reload?'))) { - window.location.reload(); - } - } - }, _('Cancel')) - ]) - ]), - - E('div', { 'class': 'cbi-section', 'style': 'margin-top: 30px;' }, [ - E('h3', {}, _('Module Position Reference')), - E('div', { 'style': 'background: #f5f5f5; padding: 20px; border-radius: 5px; font-family: monospace; font-size: 12px;' }, [ - E('pre', { 'style': 'margin: 0; overflow-x: auto;' }, -`╔══════════════════════════════════════════════════╗ -║ top_bar ║ -╠══════════════╦══════════════╦═════════════════════╣ -║ ║ ║ ║ -║ top_left ║ top_center ║ top_right ║ -║ ║ ║ ║ -╠══════════════╬══════════════╬═════════════════════╣ -║ ║ ║ ║ -║ upper_third ║middle_center ║ upper_third ║ -║ ║ ║ ║ -╠══════════════╬══════════════╬═════════════════════╣ -║ ║ ║ ║ -║ lower_third ║ ║ lower_third ║ -║ ║ ║ ║ -╠══════════════╬══════════════╬═════════════════════╣ -║ ║ ║ ║ -║ bottom_left ║bottom_center ║ bottom_right ║ -║ ║ ║ ║ -╠══════════════╩══════════════╩═════════════════════╣ -║ bottom_bar ║ -╚══════════════════════════════════════════════════╝` - ) - ]) - ]), - - E('div', { 'class': 'cbi-section', 'style': 'margin-top: 20px;' }, [ - E('h3', {}, _('Example Module Configuration')), - E('pre', { 'style': 'background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; font-size: 12px;' }, -`{ - module: "weather", - position: "top_right", - config: { - weatherProvider: "openweathermap", - type: "current", - location: "Paris", - locationID: "2988507", - apiKey: "YOUR_API_KEY" - } -}, -{ - module: "calendar", - header: "My Calendar", - position: "top_left", - config: { - calendars: [ - { - symbol: "calendar-check", - url: "webcal://calendar.google.com/calendar/ical/..." - } - ] - } -}, -{ - module: "newsfeed", - position: "bottom_bar", - config: { - feeds: [ - { - title: "BBC News", - url: "http://feeds.bbci.co.uk/news/rss.xml" - } - ], - showSourceTitle: true, - showPublishDate: true - } -}` - ) - ]), - - E('div', { 'class': 'cbi-section', 'style': 'margin-top: 20px;' }, [ - E('h3', {}, _('Resources')), - E('ul', {}, [ - E('li', {}, [E('a', { 'href': 'https://docs.magicmirror.builders/configuration/introduction.html', 'target': '_blank' }, _('Configuration Documentation'))]), - E('li', {}, [E('a', { 'href': 'https://docs.magicmirror.builders/modules/configuration.html', 'target': '_blank' }, _('Module Configuration'))]), - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MagicMirror/tree/master/modules/default', 'target': '_blank' }, _('Default Modules'))]), - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MagicMirror/wiki/3rd-party-modules', 'target': '_blank' }, _('3rd Party Modules'))]) - ]) - ]) - ]); - }, - - handleSave: null, - handleSaveApply: null, - handleReset: null -}); diff --git a/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/modules.js b/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/modules.js deleted file mode 100644 index ff33817c..00000000 --- a/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/modules.js +++ /dev/null @@ -1,282 +0,0 @@ -'use strict'; -'require view'; -'require rpc'; -'require ui'; -'require dom'; -'require poll'; - -var callListModules = rpc.declare({ - object: 'luci.magicmirror', - method: 'listModules', - expect: { } -}); - -var callInstallModule = rpc.declare({ - object: 'luci.magicmirror', - method: 'installModule', - params: ['url'], - expect: { } -}); - -var callRemoveModule = rpc.declare({ - object: 'luci.magicmirror', - method: 'removeModule', - params: ['name'], - expect: { } -}); - -var callUpdateModule = rpc.declare({ - object: 'luci.magicmirror', - method: 'updateModule', - params: ['name'], - expect: { } -}); - -var callGetModuleConfig = rpc.declare({ - object: 'luci.magicmirror', - method: 'getModuleConfig', - params: ['name'], - expect: { } -}); - -return view.extend({ - load: function() { - return Promise.all([ - callListModules() - ]); - }, - - renderModuleCard: function(module) { - return E('div', { - 'class': 'cbi-section', - 'style': 'margin-bottom: 15px; border: 1px solid #ddd; border-radius: 5px; padding: 15px;' - }, [ - E('div', { 'style': 'display: flex; justify-content: space-between; align-items: start;' }, [ - E('div', { 'style': 'flex: 1;' }, [ - E('h3', { 'style': 'margin: 0 0 10px 0; color: #2196F3;' }, module.name), - module.description ? E('p', { 'style': 'margin: 0 0 5px 0; color: #666;' }, module.description) : '', - E('div', { 'style': 'font-size: 12px; color: #999;' }, [ - module.author ? E('span', {}, _('Author: ') + module.author + ' | ') : '', - module.version ? E('span', {}, _('Version: ') + module.version) : '' - ]) - ]), - E('div', { 'style': 'display: flex; gap: 5px; flex-wrap: wrap;' }, [ - E('button', { - 'class': 'cbi-button cbi-button-action', - 'click': ui.createHandlerFn(this, function() { - return this.showModuleConfig(module.name); - }) - }, _('Info')), - E('button', { - 'class': 'cbi-button cbi-button-apply', - 'click': ui.createHandlerFn(this, function() { - return this.updateModule(module.name); - }) - }, _('Update')), - E('button', { - 'class': 'cbi-button cbi-button-negative', - 'click': ui.createHandlerFn(this, function() { - return this.removeModule(module.name); - }) - }, _('Remove')) - ]) - ]) - ]); - }, - - showModuleConfig: function(moduleName) { - ui.showModal(_('Module Information: ') + moduleName, [ - E('p', { 'class': 'spinning' }, _('Loading...')) - ]); - - return callGetModuleConfig(moduleName).then(function(result) { - if (result.success) { - var content = result.readme || 'No information available.'; - - // Truncate very long READMEs - if (content.length > 5000) { - content = content.substring(0, 5000) + '\n\n... (truncated)'; - } - - ui.showModal(_('Module Information: ') + moduleName, [ - E('div', { 'style': 'max-height: 500px; overflow-y: auto;' }, [ - E('pre', { 'style': 'white-space: pre-wrap; font-size: 12px;' }, content) - ]), - E('div', { 'class': 'right' }, [ - E('button', { - 'class': 'cbi-button', - 'click': ui.hideModal - }, _('Close')) - ]) - ], 'cbi-modal'); - } else { - ui.hideModal(); - ui.addNotification(null, E('p', _('Failed to load module information')), 'error'); - } - }); - }, - - updateModule: function(moduleName) { - if (!confirm(_('Update module ') + moduleName + '?')) { - return; - } - - ui.showModal(_('Updating Module'), [ - E('p', { 'class': 'spinning' }, _('Updating ') + moduleName + '...') - ]); - - return callUpdateModule(moduleName).then(function(result) { - ui.hideModal(); - if (result.success) { - ui.addNotification(null, E('p', _('Module update started. This may take a minute...')), 'info'); - setTimeout(function() { window.location.reload(); }, 5000); - } else { - ui.addNotification(null, E('p', _('Failed to update module: ') + (result.error || 'Unknown error')), 'error'); - } - }); - }, - - removeModule: function(moduleName) { - if (!confirm(_('Remove module ') + moduleName + '? This cannot be undone.')) { - return; - } - - ui.showModal(_('Removing Module'), [ - E('p', { 'class': 'spinning' }, _('Removing ') + moduleName + '...') - ]); - - return callRemoveModule(moduleName).then(function(result) { - ui.hideModal(); - if (result.success) { - ui.addNotification(null, E('p', _('Module removed successfully')), 'info'); - setTimeout(function() { window.location.reload(); }, 1500); - } else { - ui.addNotification(null, E('p', _('Failed to remove module: ') + (result.error || 'Unknown error')), 'error'); - } - }); - }, - - showInstallDialog: function() { - var input; - - ui.showModal(_('Install MagicMirror Module'), [ - E('p', {}, _('Enter the Git repository URL of the module to install:')), - E('div', { 'class': 'cbi-value' }, [ - E('label', { 'class': 'cbi-value-title' }, _('Git URL')), - E('div', { 'class': 'cbi-value-field' }, [ - input = E('input', { - 'type': 'text', - 'class': 'cbi-input-text', - 'placeholder': 'https://github.com/user/MMM-ModuleName', - 'style': 'width: 100%;' - }) - ]) - ]), - E('div', { 'style': 'margin-top: 15px;' }, [ - E('p', { 'style': 'font-size: 12px; color: #666;' }, [ - E('strong', {}, _('Examples:')), - E('br'), '• https://github.com/MichMich/MMM-WeatherChart', - E('br'), '• https://github.com/hangorazvan/covid19', - E('br'), '• https://github.com/jclarke0000/MMM-MyCalendar' - ]) - ]), - E('div', { 'class': 'right', 'style': 'margin-top: 20px;' }, [ - E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': ui.hideModal - }, _('Cancel')), - ' ', - E('button', { - 'class': 'cbi-button cbi-button-positive', - 'click': ui.createHandlerFn(this, function() { - var url = input.value.trim(); - if (!url) { - ui.addNotification(null, E('p', _('Please enter a Git URL')), 'warning'); - return; - } - return this.installModule(url); - }) - }, _('Install')) - ]) - ], 'cbi-modal'); - - input.focus(); - }, - - installModule: function(url) { - ui.hideModal(); - - ui.showModal(_('Installing Module'), [ - E('p', { 'class': 'spinning' }, _('Installing module from ') + url + '...'), - E('p', { 'style': 'font-size: 12px; color: #666;' }, _('This may take a few minutes depending on the module size.')) - ]); - - return callInstallModule(url).then(function(result) { - ui.hideModal(); - if (result.success) { - ui.addNotification(null, E('p', _('Module installation started. Page will reload in 10 seconds...')), 'info'); - setTimeout(function() { window.location.reload(); }, 10000); - } else { - ui.addNotification(null, E('p', _('Failed to install module: ') + (result.error || 'Unknown error')), 'error'); - } - }).catch(function(err) { - ui.hideModal(); - ui.addNotification(null, E('p', _('Error: ') + err.message), 'error'); - }); - }, - - render: function(data) { - var modulesData = data[0] || {}; - var modules = (modulesData.modules || []); - - return E('div', { 'class': 'cbi-map' }, [ - E('h2', {}, _('MagicMirror² Modules')), - E('div', { 'class': 'cbi-section-descr' }, _('Manage installed modules and install new ones from the MagicMirror community')), - - E('div', { 'class': 'cbi-section', 'style': 'margin: 20px 0;' }, [ - E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;' }, [ - E('h3', {}, _('Installed Modules')), - E('button', { - 'class': 'cbi-button cbi-button-positive', - 'click': ui.createHandlerFn(this, this.showInstallDialog) - }, _('Install New Module')) - ]), - - modules.length > 0 ? - E('div', {}, modules.map(function(module) { - return this.renderModuleCard(module); - }, this)) : - E('div', { 'class': 'alert-message warning' }, [ - E('p', {}, _('No modules installed yet.')), - E('p', {}, _('Click "Install New Module" to get started.')) - ]) - ]), - - E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Popular Modules')), - E('div', { 'style': 'columns: 2; column-gap: 20px;' }, [ - E('ul', { 'style': 'margin: 0; padding-left: 20px;' }, [ - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MMM-WeatherChart', 'target': '_blank' }, 'MMM-WeatherChart'), ' - Weather charts']), - E('li', {}, [E('a', { 'href': 'https://github.com/jclarke0000/MMM-MyCalendar', 'target': '_blank' }, 'MMM-MyCalendar'), ' - Enhanced calendar']), - E('li', {}, [E('a', { 'href': 'https://github.com/cowboysdude/MMM-NOAA', 'target': '_blank' }, 'MMM-NOAA'), ' - NOAA weather']), - E('li', {}, [E('a', { 'href': 'https://github.com/hangorazvan/covid19', 'target': '_blank' }, 'MMM-COVID19'), ' - COVID-19 stats']), - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MMM-Facial-Recognition', 'target': '_blank' }, 'MMM-Facial-Recognition'), ' - Face recognition']), - E('li', {}, [E('a', { 'href': 'https://github.com/cowboysdude/MMM-cryptocurrency', 'target': '_blank' }, 'MMM-Cryptocurrency'), ' - Crypto prices']), - E('li', {}, [E('a', { 'href': 'https://github.com/cowboysdude/MMM-BMW-DS', 'target': '_blank' }, 'MMM-BMW-DS'), ' - BMW status']), - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MMM-Todoist', 'target': '_blank' }, 'MMM-Todoist'), ' - Todoist tasks']), - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MMM-Spotify', 'target': '_blank' }, 'MMM-Spotify'), ' - Spotify player']), - E('li', {}, [E('a', { 'href': 'https://github.com/MichMich/MMM-GooglePhotos', 'target': '_blank' }, 'MMM-GooglePhotos'), ' - Google Photos']) - ]) - ]), - E('p', { 'style': 'margin-top: 15px; font-size: 12px; color: #666;' }, [ - _('Find more modules at: '), - E('a', { 'href': 'https://github.com/MichMich/MagicMirror/wiki/3rd-party-modules', 'target': '_blank' }, _('MagicMirror Wiki')) - ]) - ]) - ]); - }, - - handleSave: null, - handleSaveApply: null, - handleReset: null -}); diff --git a/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/overview.js b/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/overview.js deleted file mode 100644 index 95ef88b8..00000000 --- a/package/secubox/luci-app-magicmirror/htdocs/luci-static/resources/view/magicmirror/overview.js +++ /dev/null @@ -1,180 +0,0 @@ -'use strict'; -'require view'; -'require form'; -'require rpc'; -'require ui'; -'require poll'; - -var callMagicMirrorStatus = rpc.declare({ - object: 'luci.magicmirror', - method: 'getStatus', - expect: { } -}); - -var callMagicMirrorRestart = rpc.declare({ - object: 'luci.magicmirror', - method: 'restartService', - expect: { } -}); - -return view.extend({ - load: function() { - return Promise.all([ - callMagicMirrorStatus() - ]); - }, - - render: function(data) { - var status = data[0] || {}; - var m, s, o; - - m = new form.Map('magicmirror', _('MagicMirror² Overview'), - _('Smart mirror platform for displaying calendar, weather, news, and custom information modules')); - - // Status section - s = m.section(form.NamedSection, '_status', 'status', _('Service Status')); - - o = s.option(form.DummyValue, '_service_status', _('Status')); - o.rawhtml = true; - o.cfgvalue = function() { - var running = status.status && status.status.running; - var enabled = status.status && status.status.enabled; - - var html = '
'; - - if (running) { - html += '● Running'; - } else { - html += '● Stopped'; - } - - html += '|'; - html += 'Auto-start: ' + (enabled ? 'Enabled' : 'Disabled') + ''; - - html += '
'; - return html; - }; - - o = s.option(form.DummyValue, '_stats', _('Statistics')); - o.rawhtml = true; - o.cfgvalue = function() { - var stats = status.stats || {}; - var port = (status.status && status.status.port) || '8080'; - - var html = '
'; - - html += '
'; - html += '
' + (stats.modules_installed || 0) + '
'; - html += '
Modules Installed
'; - html += '
'; - - html += '
'; - html += '
' + port + '
'; - html += '
Web Port
'; - html += '
'; - - html += '
'; - return html; - }; - - o = s.option(form.Button, '_open', _('Quick Access')); - o.inputtitle = _('Open Mirror'); - o.inputstyle = 'apply'; - o.onclick = function() { - var port = (status.status && status.status.port) || '8080'; - window.open('http://' + window.location.hostname + ':' + port, '_blank'); - }; - - o = s.option(form.Button, '_restart', _('Service Control')); - o.inputtitle = _('Restart Service'); - o.inputstyle = 'action'; - o.onclick = function() { - return ui.showModal(_('Restarting MagicMirror²'), [ - E('p', { 'class': 'spinning' }, _('Please wait...')) - ]) || Promise.resolve(callMagicMirrorRestart()).then(function() { - ui.hideModal(); - ui.addNotification(null, E('p', _('Service restart initiated')), 'info'); - setTimeout(function() { window.location.reload(); }, 3000); - }); - }; - - // Configuration section - s = m.section(form.NamedSection, 'main', 'magicmirror', _('Basic Configuration')); - - o = s.option(form.Flag, 'enabled', _('Enable MagicMirror²'), - _('Start MagicMirror² service on boot')); - o.default = '0'; - o.rmempty = false; - - o = s.option(form.Value, 'port', _('Web Port'), - _('Port for accessing the MagicMirror² web interface')); - o.default = '8080'; - o.datatype = 'port'; - o.placeholder = '8080'; - - o = s.option(form.Value, 'timezone', _('Timezone'), - _('Timezone for date/time display (e.g., Europe/Paris, America/New_York)')); - o.default = 'UTC'; - o.placeholder = 'UTC'; - - o = s.option(form.ListValue, 'language', _('Language'), - _('Interface language')); - o.value('en', _('English')); - o.value('fr', _('French')); - o.value('de', _('German')); - o.value('es', _('Spanish')); - o.value('it', _('Italian')); - o.value('nl', _('Dutch')); - o.value('pl', _('Polish')); - o.value('pt', _('Portuguese')); - o.value('ru', _('Russian')); - o.value('zh-cn', _('Chinese (Simplified)')); - o.value('ja', _('Japanese')); - o.default = 'en'; - - o = s.option(form.ListValue, 'units', _('Units'), - _('Temperature and measurement units')); - o.value('metric', _('Metric (°C, km)')); - o.value('imperial', _('Imperial (°F, miles)')); - o.default = 'metric'; - - // Info section - s = m.section(form.NamedSection, '_info', 'info', _('Information')); - - o = s.option(form.DummyValue, '_help'); - o.rawhtml = true; - o.cfgvalue = function() { - return '
' + - '

About MagicMirror²:

' + - '

MagicMirror² is an open source modular smart mirror platform. It displays information like calendar events, weather, news, and more on any display.

' + - '

Getting Started:

' + - '
    ' + - '
  1. Configure basic settings above
  2. ' + - '
  3. Install modules from the Modules page
  4. ' + - '
  5. Customize the layout in Configuration
  6. ' + - '
  7. Access your mirror via the web interface or connect to a display
  8. ' + - '
' + - '

Module Positions:

' + - '
    ' + - '
  • top_bar - Full width top bar
  • ' + - '
  • top_left, top_center, top_right - Top row
  • ' + - '
  • upper_third, middle_center, lower_third - Middle row
  • ' + - '
  • bottom_left, bottom_center, bottom_right - Bottom row
  • ' + - '
  • bottom_bar, fullscreen_above, fullscreen_below - Special positions
  • ' + - '
' + - '

Resources:

' + - '' + - '
'; - }; - - return m.render(); - }, - - handleSave: null, - handleSaveApply: null, - handleReset: null -}); diff --git a/package/secubox/luci-app-magicmirror/root/usr/libexec/rpcd/luci.magicmirror b/package/secubox/luci-app-magicmirror/root/usr/libexec/rpcd/luci.magicmirror deleted file mode 100755 index 553cb562..00000000 --- a/package/secubox/luci-app-magicmirror/root/usr/libexec/rpcd/luci.magicmirror +++ /dev/null @@ -1,308 +0,0 @@ -#!/bin/sh -# RPCD backend for MagicMirror² Manager - -. /lib/functions.sh -. /usr/share/libubox/jshn.sh - -CONFIG="magicmirror" -CONTAINER="secbx-magicmirror" - -get_config_value() { - uci -q get ${CONFIG}.main.$1 -} - -get_modules_path() { - local path - path=$(get_config_value modules_path) - echo "${path:-/srv/magicmirror/modules}" -} - -get_config_path() { - local path - path=$(get_config_value config_path) - echo "${path:-/srv/magicmirror/config}" -} - -list_modules() { - local modules_path - modules_path=$(get_modules_path) - - json_init - json_add_array "modules" - - if [ -d "$modules_path" ]; then - for module_dir in "$modules_path"/MMM-*; do - if [ -d "$module_dir" ]; then - local module_name=$(basename "$module_dir") - local description="" - local author="" - local version="" - - # Try to extract info from package.json - if [ -f "$module_dir/package.json" ]; then - description=$(jq -r '.description // ""' "$module_dir/package.json" 2>/dev/null) - author=$(jq -r '.author // ""' "$module_dir/package.json" 2>/dev/null) - version=$(jq -r '.version // ""' "$module_dir/package.json" 2>/dev/null) - fi - - json_add_object - json_add_string "name" "$module_name" - json_add_string "description" "$description" - json_add_string "author" "$author" - json_add_string "version" "$version" - json_add_string "path" "$module_dir" - json_close_object - fi - done - fi - - json_close_array -} - -get_config_content() { - local config_path - config_path=$(get_config_path) - local config_file="$config_path/config.js" - - if [ -f "$config_file" ]; then - cat "$config_file" - else - echo "" - fi -} - -case "$1" in - list) - json_init - json_add_object "getStatus" - json_close_object - json_add_object "listModules" - json_close_object - json_add_object "getConfig" - json_close_object - json_add_object "installModule" - json_add_string "url" "string" - json_close_object - json_add_object "removeModule" - json_add_string "name" "string" - json_close_object - json_add_object "updateModule" - json_add_string "name" "string" - json_close_object - json_add_object "getModuleConfig" - json_add_string "name" "string" - json_close_object - json_add_object "saveConfig" - json_add_string "content" "string" - json_close_object - json_add_object "restartService" - json_close_object - json_dump - ;; - - call) - case "$2" in - getStatus) - # Get service status - json_init - json_add_boolean "success" 1 - - # Check if container is running - local running=0 - docker ps --filter "name=$CONTAINER" --format '{{.Names}}' 2>/dev/null | grep -q "$CONTAINER" && running=1 - - # Get config values - config_load "$CONFIG" - local enabled port - config_get enabled main enabled "0" - config_get port main port "8080" - - json_add_object "status" - json_add_boolean "running" "$running" - json_add_boolean "enabled" "$enabled" - json_add_string "port" "$port" - json_add_string "container" "$CONTAINER" - json_close_object - - # Count modules - local modules_count=0 - local modules_path - modules_path=$(get_modules_path) - if [ -d "$modules_path" ]; then - modules_count=$(find "$modules_path" -maxdepth 1 -name "MMM-*" -type d 2>/dev/null | wc -l) - fi - - json_add_object "stats" - json_add_int "modules_installed" "$modules_count" - json_close_object - - json_dump - ;; - - listModules) - json_init - json_add_boolean "success" 1 - list_modules - json_dump - ;; - - getConfig) - json_init - json_add_boolean "success" 1 - json_add_string "content" "$(get_config_content)" - json_dump - ;; - - installModule) - read input - json_load "$input" - - local url - json_get_var url url - - if [ -z "$url" ]; then - json_init - json_add_boolean "success" 0 - json_add_string "error" "URL is required" - json_dump - exit 0 - fi - - # Install module in background - /usr/sbin/magicmirrorctl module install "$url" >/tmp/mm-install.log 2>&1 & - - json_init - json_add_boolean "success" 1 - json_add_string "message" "Module installation started" - json_dump - ;; - - removeModule) - read input - json_load "$input" - - local name - json_get_var name name - - if [ -z "$name" ]; then - json_init - json_add_boolean "success" 0 - json_add_string "error" "Module name is required" - json_dump - exit 0 - fi - - # Remove module - /usr/sbin/magicmirrorctl module remove "$name" >/dev/null 2>&1 - - json_init - json_add_boolean "success" 1 - json_add_string "message" "Module removed" - json_dump - ;; - - updateModule) - read input - json_load "$input" - - local name - json_get_var name name - - if [ -z "$name" ]; then - json_init - json_add_boolean "success" 0 - json_add_string "error" "Module name is required" - json_dump - exit 0 - fi - - # Update module in background - /usr/sbin/magicmirrorctl module update "$name" >/tmp/mm-update.log 2>&1 & - - json_init - json_add_boolean "success" 1 - json_add_string "message" "Module update started" - json_dump - ;; - - getModuleConfig) - read input - json_load "$input" - - local name - json_get_var name name - - if [ -z "$name" ]; then - json_init - json_add_boolean "success" 0 - json_add_string "error" "Module name is required" - json_dump - exit 0 - fi - - local modules_path - modules_path=$(get_modules_path) - local readme_content="" - - if [ -f "$modules_path/$name/README.md" ]; then - readme_content=$(cat "$modules_path/$name/README.md") - fi - - json_init - json_add_boolean "success" 1 - json_add_string "readme" "$readme_content" - json_dump - ;; - - saveConfig) - read input - json_load "$input" - - local content - json_get_var content content - - if [ -z "$content" ]; then - json_init - json_add_boolean "success" 0 - json_add_string "error" "Content is required" - json_dump - exit 0 - fi - - local config_path - config_path=$(get_config_path) - local config_file="$config_path/config.js" - - # Backup current config - if [ -f "$config_file" ]; then - cp "$config_file" "$config_file.backup.$(date +%Y%m%d_%H%M%S)" - fi - - # Save new config - echo "$content" > "$config_file" - - json_init - json_add_boolean "success" 1 - json_add_string "message" "Configuration saved" - json_dump - ;; - - restartService) - # Restart MagicMirror service - /etc/init.d/magicmirror restart >/dev/null 2>&1 & - - json_init - json_add_boolean "success" 1 - json_add_string "message" "Service restart initiated" - json_dump - ;; - - *) - json_init - json_add_boolean "success" 0 - json_add_string "error" "Unknown method" - json_dump - ;; - esac - ;; -esac diff --git a/package/secubox/luci-app-magicmirror/root/usr/share/luci/menu.d/luci-app-magicmirror.json b/package/secubox/luci-app-magicmirror/root/usr/share/luci/menu.d/luci-app-magicmirror.json deleted file mode 100644 index f379bf1d..00000000 --- a/package/secubox/luci-app-magicmirror/root/usr/share/luci/menu.d/luci-app-magicmirror.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "admin/secubox/iot/magicmirror": { - "title": "MagicMirror²", - "order": 40, - "action": { - "type": "view", - "path": "magicmirror/overview" - }, - "depends": { - "acl": ["luci-app-magicmirror"] - } - }, - "admin/secubox/iot/magicmirror/overview": { - "title": "Overview", - "order": 1, - "action": { - "type": "view", - "path": "magicmirror/overview" - } - }, - "admin/secubox/iot/magicmirror/modules": { - "title": "Modules", - "order": 2, - "action": { - "type": "view", - "path": "magicmirror/modules" - } - }, - "admin/secubox/iot/magicmirror/config": { - "title": "Configuration", - "order": 3, - "action": { - "type": "view", - "path": "magicmirror/config" - } - } -} diff --git a/package/secubox/luci-app-magicmirror/root/usr/share/rpcd/acl.d/luci-app-magicmirror.json b/package/secubox/luci-app-magicmirror/root/usr/share/rpcd/acl.d/luci-app-magicmirror.json deleted file mode 100644 index 17335571..00000000 --- a/package/secubox/luci-app-magicmirror/root/usr/share/rpcd/acl.d/luci-app-magicmirror.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "luci-app-magicmirror": { - "description": "Grant access to MagicMirror² Manager", - "read": { - "ubus": { - "luci.magicmirror": ["getStatus", "listModules", "getConfig", "getModuleConfig"] - }, - "uci": ["magicmirror"] - }, - "write": { - "ubus": { - "luci.magicmirror": ["installModule", "removeModule", "updateModule", "saveConfig", "restartService"] - }, - "uci": ["magicmirror"] - } - } -} diff --git a/package/secubox/luci-app-magicmirror/README.md b/package/secubox/luci-app-magicmirror2/README.md similarity index 100% rename from package/secubox/luci-app-magicmirror/README.md rename to package/secubox/luci-app-magicmirror2/README.md diff --git a/package/secubox/secubox-app-magicmirror/Makefile b/package/secubox/secubox-app-magicmirror/Makefile deleted file mode 100644 index 3c53656c..00000000 --- a/package/secubox/secubox-app-magicmirror/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -include $(TOPDIR)/rules.mk - -PKG_NAME:=secubox-app-magicmirror -PKG_RELEASE:=2 -PKG_VERSION:=1.0.0 -PKG_ARCH:=all -PKG_MAINTAINER:=CyberMind Studio -PKG_LICENSE:=Apache-2.0 - -include $(INCLUDE_DIR)/package.mk - -define Package/secubox-app-magicmirror - SECTION:=utils - CATEGORY:=Utilities - PKGARCH:=all - SUBMENU:=SecuBox Apps - TITLE:=SecuBox MagicMirror² docker app - DEPENDS:=+uci +libuci +dockerd +docker +containerd +jq -endef - -define Package/secubox-app-magicmirror/description -Installer, configuration, and service manager for running MagicMirror² -inside Docker on SecuBox-powered OpenWrt systems. Smart mirror platform -with modular display system and web-based module management. -endef - -define Package/secubox-app-magicmirror/conffiles -/etc/config/magicmirror -endef - -define Build/Compile -endef - -define Package/secubox-app-magicmirror/install - $(INSTALL_DIR) $(1)/etc/config - $(INSTALL_CONF) ./files/etc/config/magicmirror $(1)/etc/config/magicmirror - - $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/etc/init.d/magicmirror $(1)/etc/init.d/magicmirror - - $(INSTALL_DIR) $(1)/usr/sbin - $(INSTALL_BIN) ./files/usr/sbin/magicmirrorctl $(1)/usr/sbin/magicmirrorctl -endef - -$(eval $(call BuildPackage,secubox-app-magicmirror)) diff --git a/package/secubox/secubox-app-magicmirror/files/etc/config/magicmirror b/package/secubox/secubox-app-magicmirror/files/etc/config/magicmirror deleted file mode 100644 index 3560df78..00000000 --- a/package/secubox/secubox-app-magicmirror/files/etc/config/magicmirror +++ /dev/null @@ -1,10 +0,0 @@ -config magicmirror 'main' - option enabled '0' - option image 'karsten13/magicmirror:latest' - option config_path '/srv/magicmirror/config' - option modules_path '/srv/magicmirror/modules' - option css_path '/srv/magicmirror/css' - option port '8080' - option timezone 'UTC' - option language 'en' - option units 'metric' diff --git a/package/secubox/secubox-app-magicmirror/files/etc/init.d/magicmirror b/package/secubox/secubox-app-magicmirror/files/etc/init.d/magicmirror deleted file mode 100755 index b9f47ca2..00000000 --- a/package/secubox/secubox-app-magicmirror/files/etc/init.d/magicmirror +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh /etc/rc.common - -START=95 -STOP=10 -USE_PROCD=1 - -SERVICE_BIN="/usr/sbin/magicmirrorctl" - -start_service() { - procd_open_instance - procd_set_param command "$SERVICE_BIN" service-run - procd_set_param respawn 2000 5 5 - procd_close_instance -} - -stop_service() { - "$SERVICE_BIN" service-stop >/dev/null 2>&1 -} - -restart_service() { - stop_service - start_service -} diff --git a/package/secubox/secubox-app-magicmirror/files/usr/sbin/magicmirrorctl b/package/secubox/secubox-app-magicmirror/files/usr/sbin/magicmirrorctl deleted file mode 100644 index 5c700b06..00000000 --- a/package/secubox/secubox-app-magicmirror/files/usr/sbin/magicmirrorctl +++ /dev/null @@ -1,483 +0,0 @@ -#!/bin/sh -# SecuBox MagicMirror² manager - -CONFIG="magicmirror" -CONTAINER="secbx-magicmirror" -OPKG_UPDATED=0 - -usage() { - cat <<'USAGE' -Usage: magicmirrorctl - -Commands: - install Install prerequisites, prepare directories, pull image - check Run prerequisite checks - update Pull new image and restart - status Show container status - logs Show container logs (use -f to follow) - module Manage MagicMirror modules (see module --help) - config Manage MagicMirror configuration (see config --help) - service-run Internal: run container via procd - service-stop Stop container -USAGE -} - -usage_module() { - cat <<'USAGE' -Usage: magicmirrorctl module - -Commands: - list List installed modules - install Install module from git URL - remove Remove installed module - update Update module to latest version - config Show module configuration template - -Examples: - magicmirrorctl module install https://github.com/MichMich/MMM-WeatherChart - magicmirrorctl module remove MMM-WeatherChart - magicmirrorctl module update MMM-WeatherChart -USAGE -} - -usage_config() { - cat <<'USAGE' -Usage: magicmirrorctl config - -Commands: - show Show current configuration - edit Edit configuration (opens in vi) - backup Backup current configuration - restore Restore from backup - reset Reset to default configuration - -USAGE -} - -require_root() { [ "$(id -u)" -eq 0 ]; } - -uci_get() { uci -q get ${CONFIG}.main.$1; } - -defaults() { - image="$(uci_get image || echo karsten13/magicmirror:latest)" - config_path="$(uci_get config_path || echo /srv/magicmirror/config)" - modules_path="$(uci_get modules_path || echo /srv/magicmirror/modules)" - css_path="$(uci_get css_path || echo /srv/magicmirror/css)" - port="$(uci_get port || echo 8080)" - timezone="$(uci_get timezone || echo UTC)" - language="$(uci_get language || echo en)" - units="$(uci_get units || echo metric)" -} - -ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } - -ensure_packages() { - for pkg in "$@"; do - if ! opkg status "$pkg" >/dev/null 2>&1; then - if [ "$OPKG_UPDATED" -eq 0 ]; then - opkg update || return 1 - OPKG_UPDATED=1 - fi - opkg install "$pkg" || return 1 - fi - done -} - -check_prereqs() { - defaults - ensure_dir "$config_path" - ensure_dir "$modules_path" - ensure_dir "$css_path" - [ -d /sys/fs/cgroup ] || { echo "[ERROR] /sys/fs/cgroup missing" >&2; return 1; } - ensure_packages dockerd docker containerd jq - /etc/init.d/dockerd enable >/dev/null 2>&1 - /etc/init.d/dockerd start >/dev/null 2>&1 -} - -pull_image() { defaults; docker pull "$image"; } - -stop_container() { docker stop "$CONTAINER" >/dev/null 2>&1 || true; docker rm "$CONTAINER" >/dev/null 2>&1 || true; } - -create_default_config() { - local config_file="$config_path/config.js" - defaults - - cat > "$config_file" <<'CONFIGJS' -/* MagicMirror² Config - * By Michael Teeuw https://michaelteeuw.nl - * MIT Licensed. - * - * For more information on how you can configure this file - * see https://docs.magicmirror.builders/configuration/introduction.html - * and https://docs.magicmirror.builders/modules/configuration.html - */ -let config = { - address: "0.0.0.0", - port: 8080, - basePath: "/", - ipWhitelist: [], - useHttps: false, - httpsPrivateKey: "", - httpsCertificate: "", - - language: "LANG_PLACEHOLDER", - locale: "LANG_PLACEHOLDER", - logLevel: ["INFO", "LOG", "WARN", "ERROR"], - timeFormat: 24, - units: "UNITS_PLACEHOLDER", - - modules: [ - { - module: "alert", - }, - { - module: "updatenotification", - position: "top_bar" - }, - { - module: "clock", - position: "top_left" - }, - { - module: "calendar", - header: "Upcoming Events", - position: "top_left", - config: { - calendars: [ - { - symbol: "calendar-check", - url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" - } - ] - } - }, - { - module: "compliments", - position: "lower_third" - }, - { - module: "weather", - position: "top_right", - config: { - weatherProvider: "openweathermap", - type: "current", - location: "New York", - locationID: "5128581", - apiKey: "YOUR_OPENWEATHER_API_KEY" - } - }, - { - module: "weather", - position: "top_right", - header: "Weather Forecast", - config: { - weatherProvider: "openweathermap", - type: "forecast", - location: "New York", - locationID: "5128581", - apiKey: "YOUR_OPENWEATHER_API_KEY" - } - }, - { - module: "newsfeed", - position: "bottom_bar", - config: { - feeds: [ - { - title: "New York Times", - url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" - } - ], - showSourceTitle: true, - showPublishDate: true, - broadcastNewsFeeds: true, - broadcastNewsUpdates: true - } - }, - ] -}; - -/*************** DO NOT EDIT THE LINE BELOW ***************/ -if (typeof module !== "undefined") {module.exports = config;} -CONFIGJS - - # Replace placeholders - sed -i "s/LANG_PLACEHOLDER/$language/g" "$config_file" - sed -i "s/UNITS_PLACEHOLDER/$units/g" "$config_file" - - chmod 644 "$config_file" - echo "Created default config: $config_file" -} - -cmd_install() { - require_root || { echo Root required >&2; exit 1; } - check_prereqs || exit 1 - pull_image || exit 1 - - # Create default config if not exists - if [ ! -f "$config_path/config.js" ]; then - create_default_config - fi - - uci set ${CONFIG}.main.enabled='1' - uci commit ${CONFIG} - /etc/init.d/magicmirror enable - - echo "MagicMirror² prerequisites installed." - echo "Start with: /etc/init.d/magicmirror start" - echo "" - echo "Access MagicMirror at: http://$(uci get network.lan.ipaddr):${port}" - echo "Edit config: $config_path/config.js" - echo "Install modules to: $modules_path" -} - -cmd_check() { check_prereqs; echo "Prerequisite check completed."; } - -cmd_update() { - require_root || { echo Root required >&2; exit 1; } - pull_image || exit 1 - /etc/init.d/magicmirror restart -} - -cmd_status() { docker ps -a --filter "name=$CONTAINER"; } - -cmd_logs() { docker logs "$@" "$CONTAINER"; } - -# Module management commands -cmd_module_list() { - defaults - echo "Installed MagicMirror Modules:" - echo "==============================" - if [ -d "$modules_path" ]; then - for module_dir in "$modules_path"/MMM-*; do - if [ -d "$module_dir" ]; then - local module_name=$(basename "$module_dir") - local module_readme="$module_dir/README.md" - echo " - $module_name" - if [ -f "$module_readme" ]; then - head -5 "$module_readme" | grep -i "description" | sed 's/^/ /' - fi - fi - done - else - echo " No modules directory found" - fi -} - -cmd_module_install() { - local git_url="$1" - require_root || { echo Root required >&2; exit 1; } - defaults - - if [ -z "$git_url" ]; then - echo "Error: Git URL required" >&2 - usage_module - exit 1 - fi - - # Extract module name from URL - local module_name=$(basename "$git_url" .git) - - echo "Installing module: $module_name" - echo "From: $git_url" - - # Ensure git is available in container or install locally - if ! command -v git >/dev/null 2>&1; then - ensure_packages git git-http - fi - - # Clone module - cd "$modules_path" || exit 1 - if [ -d "$module_name" ]; then - echo "Module already exists. Use 'update' to update it." - exit 1 - fi - - git clone "$git_url" || exit 1 - - # Run npm install if package.json exists - if [ -f "$module_name/package.json" ]; then - echo "Running npm install..." - docker exec "$CONTAINER" sh -c "cd modules/$module_name && npm install" 2>/dev/null || { - echo "Note: Container not running. Module installed but npm dependencies not installed." - echo "Start MagicMirror and run: magicmirrorctl module update $module_name" - } - fi - - echo "Module installed: $module_name" - echo "Add to config.js to activate it" -} - -cmd_module_remove() { - local module_name="$1" - require_root || { echo Root required >&2; exit 1; } - defaults - - if [ -z "$module_name" ]; then - echo "Error: Module name required" >&2 - usage_module - exit 1 - fi - - local module_dir="$modules_path/$module_name" - if [ ! -d "$module_dir" ]; then - echo "Error: Module not found: $module_name" >&2 - exit 1 - fi - - echo "Removing module: $module_name" - rm -rf "$module_dir" - echo "Module removed. Update config.js to remove module configuration." -} - -cmd_module_update() { - local module_name="$1" - require_root || { echo Root required >&2; exit 1; } - defaults - - if [ -z "$module_name" ]; then - echo "Error: Module name required" >&2 - usage_module - exit 1 - fi - - local module_dir="$modules_path/$module_name" - if [ ! -d "$module_dir" ]; then - echo "Error: Module not found: $module_name" >&2 - exit 1 - fi - - echo "Updating module: $module_name" - cd "$module_dir" || exit 1 - - git pull || exit 1 - - if [ -f "package.json" ]; then - echo "Running npm install..." - docker exec "$CONTAINER" sh -c "cd modules/$module_name && npm install" || { - echo "Warning: Failed to install npm dependencies. Make sure container is running." - } - fi - - echo "Module updated: $module_name" -} - -cmd_module_config() { - local module_name="$1" - defaults - - if [ -z "$module_name" ]; then - echo "Error: Module name required" >&2 - usage_module - exit 1 - fi - - local module_dir="$modules_path/$module_name" - if [ ! -d "$module_dir" ]; then - echo "Error: Module not found: $module_name" >&2 - exit 1 - fi - - # Show README and config template - if [ -f "$module_dir/README.md" ]; then - echo "Module: $module_name" - echo "==============================" - grep -A 20 -i "config" "$module_dir/README.md" | head -30 - else - echo "No README.md found for module: $module_name" - fi -} - -cmd_module() { - case "${1:-list}" in - list) shift; cmd_module_list "$@" ;; - install) shift; cmd_module_install "$@" ;; - remove) shift; cmd_module_remove "$@" ;; - update) shift; cmd_module_update "$@" ;; - config) shift; cmd_module_config "$@" ;; - help|--help|-h) usage_module ;; - *) echo "Unknown module command: $1" >&2; usage_module >&2; exit 1 ;; - esac -} - -# Config management commands -cmd_config_show() { - defaults - cat "$config_path/config.js" -} - -cmd_config_edit() { - require_root || { echo Root required >&2; exit 1; } - defaults - ${EDITOR:-vi} "$config_path/config.js" -} - -cmd_config_backup() { - defaults - local backup_file="$config_path/config.js.backup.$(date +%Y%m%d_%H%M%S)" - cp "$config_path/config.js" "$backup_file" - echo "Backup created: $backup_file" -} - -cmd_config_restore() { - require_root || { echo Root required >&2; exit 1; } - defaults - local latest_backup=$(ls -t "$config_path"/config.js.backup.* 2>/dev/null | head -1) - if [ -z "$latest_backup" ]; then - echo "No backups found" >&2 - exit 1 - fi - cp "$latest_backup" "$config_path/config.js" - echo "Restored from: $latest_backup" -} - -cmd_config_reset() { - require_root || { echo Root required >&2; exit 1; } - cmd_config_backup - create_default_config - echo "Configuration reset to defaults" -} - -cmd_config() { - case "${1:-show}" in - show) shift; cmd_config_show "$@" ;; - edit) shift; cmd_config_edit "$@" ;; - backup) shift; cmd_config_backup "$@" ;; - restore) shift; cmd_config_restore "$@" ;; - reset) shift; cmd_config_reset "$@" ;; - help|--help|-h) usage_config ;; - *) echo "Unknown config command: $1" >&2; usage_config >&2; exit 1 ;; - esac -} - -cmd_service_run() { - require_root || { echo Root required >&2; exit 1; } - check_prereqs || exit 1 - defaults - stop_container - - local docker_args="--name $CONTAINER" - docker_args="$docker_args -p ${port}:8080" - docker_args="$docker_args -v $config_path:/opt/magic_mirror/config" - docker_args="$docker_args -v $modules_path:/opt/magic_mirror/modules" - docker_args="$docker_args -v $css_path:/opt/magic_mirror/css/custom" - docker_args="$docker_args -e TZ=$timezone" - - exec docker run --rm $docker_args "$image" -} - -cmd_service_stop() { require_root || { echo Root required >&2; exit 1; }; stop_container; } - -case "${1:-}" in - install) shift; cmd_install "$@" ;; - check) shift; cmd_check "$@" ;; - update) shift; cmd_update "$@" ;; - status) shift; cmd_status "$@" ;; - logs) shift; cmd_logs "$@" ;; - module) shift; cmd_module "$@" ;; - config) shift; cmd_config "$@" ;; - service-run) shift; cmd_service_run "$@" ;; - service-stop) shift; cmd_service_stop "$@" ;; - help|--help|-h|'') usage ;; - *) echo "Unknown command: $1" >&2; usage >&2; exit 1 ;; -esac diff --git a/package/secubox/secubox-core/Makefile b/package/secubox/secubox-core/Makefile index cc96ef64..d1a1fbf6 100644 --- a/package/secubox/secubox-core/Makefile +++ b/package/secubox/secubox-core/Makefile @@ -138,10 +138,10 @@ EOF # Register with rpcd /etc/init.d/rpcd restart - # Sync component registry from catalog + # Sync component registry from catalog (run in background so installation completes quickly) if [ -x /usr/sbin/secubox-sync-registry ]; then - echo "Syncing component registry..." - /usr/sbin/secubox-sync-registry sync + echo "Starting component registry sync in background..." + (/usr/sbin/secubox-sync-registry sync &) >/dev/null 2>&1 fi echo "SecuBox Core Framework v0.9.0 installed successfully" diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-component b/package/secubox/secubox-core/root/usr/sbin/secubox-component index 00d5b273..64e7cf4b 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox-component +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-component @@ -37,7 +37,8 @@ log_message() { local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message" >> "$REGISTRY_LOG" - logger -t secubox-component "[$level] $message" + # Only log INFO and above to syslog (skip DEBUG) + [ "$level" != "DEBUG" ] && logger -t secubox-component "[$level] $message" } # Read registry database diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-core b/package/secubox/secubox-core/root/usr/sbin/secubox-core index 1343d569..54e7eed4 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox-core +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-core @@ -22,7 +22,8 @@ log() { local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" - logger -t secubox-core -p "user.$level" "$message" + # Only log info and above to syslog (skip debug) + [ "$level" != "debug" ] && logger -t secubox-core -p "user.$level" "$message" } # Get system status diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-state b/package/secubox/secubox-core/root/usr/sbin/secubox-state index 3872ded2..64f3c4f2 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox-state +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-state @@ -39,7 +39,8 @@ log_message() { local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message" >> "$STATE_LOG" - logger -t secubox-state "[$level] $message" + # Only log INFO and above to syslog (skip DEBUG) + [ "$level" != "DEBUG" ] && logger -t secubox-state "[$level] $message" } # Read state database diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-sync-registry b/package/secubox/secubox-core/root/usr/sbin/secubox-sync-registry index cbf710b8..f76e38a6 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox-sync-registry +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-sync-registry @@ -13,6 +13,13 @@ PLUGIN_CATALOG_DIR="/usr/share/secubox/plugins/catalog" REGISTRY_FILE="/var/lib/secubox/component-registry.json" SYNC_LOG="/var/log/secubox-sync.log" +# Cache opkg output to avoid repeated calls (major performance optimization) +OPKG_CACHE="" +get_opkg_cache() { + [ -z "$OPKG_CACHE" ] && OPKG_CACHE=$(opkg list-installed 2>/dev/null) + echo "$OPKG_CACHE" +} + # Log message log_message() { local level="$1" @@ -21,7 +28,8 @@ log_message() { local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message" >> "$SYNC_LOG" - logger -t secubox-sync "[$level] $message" + # Only log INFO and above to syslog (skip DEBUG) + [ "$level" != "DEBUG" ] && logger -t secubox-sync "[$level] $message" } # Sync catalog apps to component registry @@ -176,8 +184,11 @@ sync_installed_packages() { log_message "INFO" "Detecting installed packages" - # Get list of SecuBox-related packages - local secubox_packages=$(opkg list-installed | grep -E "^(secubox-|luci-app-|luci-mod-)" | awk '{print $1}') + # Pre-populate the opkg cache (single call instead of multiple) + local opkg_output=$(get_opkg_cache) + + # Get list of SecuBox-related packages from cache + local secubox_packages=$(echo "$opkg_output" | grep -E "^(secubox-|luci-app-|luci-mod-)" | awk '{print $1}') for pkg_name in $secubox_packages; do # Check if already registered @@ -185,8 +196,8 @@ sync_installed_packages() { continue fi - # Get package version - local pkg_version=$(opkg list-installed | grep "^$pkg_name " | awk '{print $3}') + # Get package version from cache + local pkg_version=$(echo "$opkg_output" | grep "^$pkg_name " | awk '{print $3}') # Register as module component local metadata=$(cat </dev/null) - - if [ -z "$components" ] || [ "$components" = "[]" ]; then - log_message "INFO" "No components to update" - return 0 - fi - - # For each component, ensure it has a state entry - local component_ids=$(echo "$components" | jq -r '.[].id' 2>/dev/null) - - for comp_id in $component_ids; do - # Check if state exists - local state=$(/usr/sbin/secubox-state get "$comp_id" 2>/dev/null) - - if [ -z "$state" ] || echo "$state" | grep -q "Error:"; then - # Initialize state as available - /usr/sbin/secubox-state set "$comp_id" available "auto_sync" > /dev/null 2>&1 || true - log_message "DEBUG" "Initialized state for: $comp_id" - fi - done - - log_message "INFO" "State references updated" + log_message "INFO" "State references will be initialized on first access (deferred for performance)" return 0 } diff --git a/secubox-tools/local-build.sh b/secubox-tools/local-build.sh index 44c0f493..9b6cb4e4 100755 --- a/secubox-tools/local-build.sh +++ b/secubox-tools/local-build.sh @@ -1672,6 +1672,11 @@ CONFIG_PACKAGE_kmod-usb-storage=y CONFIG_PACKAGE_kmod-fs-ext4=y CONFIG_PACKAGE_kmod-fs-vfat=y +# Container/LXC support +CONFIG_PACKAGE_kmod-veth=y +CONFIG_PACKAGE_kmod-br-netfilter=y +CONFIG_PACKAGE_kmod-nf-conntrack-netlink=y + # SecuBox packages - Core CONFIG_PACKAGE_secubox-app=y CONFIG_PACKAGE_luci-app-secubox=y diff --git a/secubox-tools/scripts/expand-openwrt-image.sh b/secubox-tools/scripts/expand-openwrt-image.sh new file mode 100755 index 00000000..04adbce1 --- /dev/null +++ b/secubox-tools/scripts/expand-openwrt-image.sh @@ -0,0 +1,197 @@ +#!/bin/bash +# +# expand-openwrt-image.sh +# Downloads OpenWrt ext4 image and expands it to specified size +# +# Usage: ./expand-openwrt-image.sh [SIZE_GB] +# SIZE_GB defaults to 16 + +set -e + +# Configuration +IMAGE_URL="https://downloads.openwrt.org/releases/24.10.5/targets/mvebu/cortexa72/openwrt-24.10.5-mvebu-cortexa72-globalscale_mochabin-ext4-sdcard.img.gz" +SIZE_GB="${1:-16}" +WORK_DIR="${WORK_DIR:-$(pwd)}" + +# Derived names +IMAGE_GZ="$(basename "$IMAGE_URL")" +IMAGE_NAME="${IMAGE_GZ%.gz}" +OUTPUT_IMAGE="${IMAGE_NAME%.img}-${SIZE_GB}gb.img" + +cd "$WORK_DIR" + +echo "=== OpenWrt Image Expansion Script (ext4) ===" +echo "Target size: ${SIZE_GB}GB" +echo "Working directory: $WORK_DIR" +echo "" + +# Check required tools +for tool in wget gunzip sfdisk; do + if ! command -v "$tool" &>/dev/null; then + echo "ERROR: Required tool '$tool' not found" + exit 1 + fi +done + +# Step 1: Download image if not present +if [ -f "$IMAGE_GZ" ]; then + echo "[1/5] Image archive already exists: $IMAGE_GZ" +elif [ -f "$IMAGE_NAME" ]; then + echo "[1/5] Decompressed image already exists: $IMAGE_NAME" +else + echo "[1/5] Downloading image..." + wget -q --show-progress "$IMAGE_URL" -O "$IMAGE_GZ" +fi + +# Step 2: Decompress image +if [ -f "$IMAGE_NAME" ]; then + echo "[2/5] Image already decompressed: $IMAGE_NAME" +else + echo "[2/5] Decompressing image..." + gunzip -k "$IMAGE_GZ" 2>/dev/null || gunzip -kf "$IMAGE_GZ" || true +fi + +# Step 3: Create expanded copy +echo "[3/5] Creating ${SIZE_GB}GB image: $OUTPUT_IMAGE" +cp "$IMAGE_NAME" "$OUTPUT_IMAGE" + +# Calculate target size in bytes +TARGET_BYTES=$((SIZE_GB * 1024 * 1024 * 1024)) + +# Expand the image file +truncate -s "$TARGET_BYTES" "$OUTPUT_IMAGE" + +# Step 4: Expand partition 2 to fill all space +echo "[4/5] Expanding partition 2..." + +echo "Original partition layout:" +fdisk -l "$OUTPUT_IMAGE" +echo "" + +# Get partition info +PART_INFO=$(sfdisk -d "$OUTPUT_IMAGE" 2>/dev/null) + +# Extract disk label-id (critical for PARTUUID) +LABEL_ID=$(echo "$PART_INFO" | grep -E '^label-id:' | sed 's/label-id: *//') +if [ -z "$LABEL_ID" ]; then + echo "WARNING: Could not extract label-id, PARTUUID may change!" +else + echo "Preserving disk label-id: $LABEL_ID" +fi + +# Extract partition 1 info +PART1_LINE=$(echo "$PART_INFO" | grep -E '^[^ ]+1 :') +PART1_START=$(echo "$PART1_LINE" | sed -n 's/.*start= *\([0-9]*\).*/\1/p') +PART1_SIZE=$(echo "$PART1_LINE" | sed -n 's/.*size= *\([0-9]*\).*/\1/p') +PART1_TYPE=$(echo "$PART1_LINE" | sed -n 's/.*type= *\([^,]*\).*/\1/p') +PART1_BOOT=$(echo "$PART1_LINE" | grep -q 'bootable' && echo ", bootable" || echo "") + +# Extract partition 2 start +PART2_LINE=$(echo "$PART_INFO" | grep -E '^[^ ]+2 :') +PART2_START=$(echo "$PART2_LINE" | sed -n 's/.*start= *\([0-9]*\).*/\1/p') +PART2_TYPE=$(echo "$PART2_LINE" | sed -n 's/.*type= *\([^,]*\).*/\1/p') + +if [ -z "$PART2_START" ]; then + echo "ERROR: Could not parse partition 2" + exit 1 +fi + +echo "Partition 2 starts at sector: $PART2_START" +echo "Expanding partition 2 to fill all remaining space..." + +# Create new partition table with expanded partition 2 +# Include label-id to preserve PARTUUID +LABEL_ID_LINE="" +if [ -n "$LABEL_ID" ]; then + LABEL_ID_LINE="label-id: $LABEL_ID" +fi + +cat </dev/null | grep -E '^label-id:' | sed 's/label-id: *//') + if [ "$LABEL_ID" = "$NEW_LABEL_ID" ]; then + echo "" + echo "PARTUUID preserved: ${LABEL_ID#0x}-01 (boot), ${LABEL_ID#0x}-02 (root)" + else + echo "" + echo "WARNING: label-id changed from $LABEL_ID to $NEW_LABEL_ID" + echo "PARTUUID will be different!" + fi +fi + +# Step 5: Resize ext4 filesystem +echo "" +echo "[5/5] Resizing ext4 filesystem..." + +PART2_OFFSET=$((PART2_START * 512)) + +if command -v losetup &>/dev/null && command -v resize2fs &>/dev/null; then + # Calculate partition 2 size + TOTAL_SECTORS=$((TARGET_BYTES / 512)) + PART2_SIZE_SECTORS=$((TOTAL_SECTORS - PART2_START)) + PART2_SIZE_BYTES=$((PART2_SIZE_SECTORS * 512)) + + LOOP_DEV=$(losetup -f --show -o "$PART2_OFFSET" --sizelimit "$PART2_SIZE_BYTES" "$OUTPUT_IMAGE" 2>/dev/null || true) + + if [ -n "$LOOP_DEV" ]; then + # Check filesystem + FS_TYPE=$(blkid -o value -s TYPE "$LOOP_DEV" 2>/dev/null || echo "unknown") + echo "Filesystem type: $FS_TYPE" + + if [ "$FS_TYPE" = "ext4" ]; then + echo "Running e2fsck..." + e2fsck -f -y "$LOOP_DEV" 2>&1 || true + echo "Running resize2fs..." + resize2fs "$LOOP_DEV" 2>&1 || true + echo "Filesystem resized successfully!" + else + echo "Warning: Expected ext4, found $FS_TYPE" + fi + + losetup -d "$LOOP_DEV" + else + echo "Note: Could not setup loop device (need root)." + echo " Resize filesystem after flashing:" + echo " resize2fs /dev/mmcblk0p2" + fi +else + echo "Note: losetup or resize2fs not available." + echo " Resize filesystem after flashing:" + echo " resize2fs /dev/mmcblk0p2" +fi + +echo "" +echo "=== Compressing image ===" +echo "Compressing to ${OUTPUT_IMAGE}.gz (this may take a while)..." +gzip -f "$OUTPUT_IMAGE" + +echo "Generating checksums..." +sha256sum "${OUTPUT_IMAGE}.gz" > "${OUTPUT_IMAGE}.gz.sha256" +md5sum "${OUTPUT_IMAGE}.gz" > "${OUTPUT_IMAGE}.gz.md5" + +echo "" +echo "=== Complete ===" +echo "Expanded image created: ${OUTPUT_IMAGE}.gz" +echo "Compressed size: $(du -h "${OUTPUT_IMAGE}.gz" | cut -f1)" +echo "" +echo "Checksums:" +cat "${OUTPUT_IMAGE}.gz.sha256" +cat "${OUTPUT_IMAGE}.gz.md5" +echo "" +echo "To flash to SD card (replace /dev/sdX with your device):" +echo " gunzip -c ${OUTPUT_IMAGE}.gz | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync" +echo "" +echo "Or use balenaEtcher/Raspberry Pi Imager (supports .gz directly)."