profiles: add presets and wizard integration
This commit is contained in:
parent
eab24f9609
commit
3c0003614d
@ -22,6 +22,8 @@ These notes summarize the repository structure, conventions, and supporting tool
|
|||||||
- `DOCS/` + `docs/`: mirrored, versioned documentation tree (design system, prompts, module templates, validation, permissions, etc.).
|
- `DOCS/` + `docs/`: mirrored, versioned documentation tree (design system, prompts, module templates, validation, permissions, etc.).
|
||||||
- `EXAMPLES/` and `templates/`: snippets and scaffolding.
|
- `EXAMPLES/` and `templates/`: snippets and scaffolding.
|
||||||
- CI workflows live in `.github/workflows/` (referenced from README badges).
|
- CI workflows live in `.github/workflows/` (referenced from README badges).
|
||||||
|
- **Profiles & App Manifests**
|
||||||
|
- App manifests (`/usr/share/secubox/plugins/`) and profile presets (`/usr/share/secubox/profiles/`) feed both the SecuBox wizard UI and the `secubox-app` CLI for automated installs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -223,6 +223,11 @@ Pointer: see `docs/embedded/vhost-manager.md` for the canonical version.
|
|||||||
|
|
||||||
Pointer: see `docs/embedded/app-store.md` for the canonical version.
|
Pointer: see `docs/embedded/app-store.md` for the canonical version.
|
||||||
|
|
||||||
|
#### **embedded/wizard-profiles.md** 🧭
|
||||||
|
*First-run wizard and OS-like profiles.*
|
||||||
|
|
||||||
|
Pointer: see `docs/embedded/wizard-profiles.md` for the canonical version.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5. Tools & Scripts Documentation
|
### 5. Tools & Scripts Documentation
|
||||||
|
|||||||
74
DOCS/embedded/wizard-profiles.md
Normal file
74
DOCS/embedded/wizard-profiles.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# SecuBox Wizard & Profiles
|
||||||
|
|
||||||
|
**Version:** 1.0.0
|
||||||
|
**Last Updated:** 2025-12-28
|
||||||
|
**Status:** Active
|
||||||
|
|
||||||
|
The SecuBox hub now includes a guided setup wizard (LuCI → SecuBox → Wizard) and profile system. Use them to finish the first-run checklist, review app manifests, and apply predefined OS-like configurations (Home, Lab, Hardened, Gateway + DMZ).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## First-Run Checklist
|
||||||
|
|
||||||
|
The wizard queries `luci.secubox`’s `first_run_status` ubus method to determine whether critical items are configured:
|
||||||
|
|
||||||
|
1. **Administrator password** – links to the LuCI password page.
|
||||||
|
2. **Timezone** – dropdown populated with common timezones; applying calls `apply_first_run` with `{ timezone: "Europe/Paris" }`.
|
||||||
|
3. **Storage path** – defaults to `/srv/secubox`; prepares the directory and stores it in `uci set secubox.main.storage_path`.
|
||||||
|
4. **Network mode** – uses the existing Network Modes RPC to switch between `router` and `dmz` presets.
|
||||||
|
|
||||||
|
Each action can be run independently and is idempotent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## App Wizards (Manifests)
|
||||||
|
|
||||||
|
Apps ship manifests under `/usr/share/secubox/plugins/<id>/manifest.json`. `secubox-app` (installed at `/usr/sbin/secubox-app`) uses the same manifests for CLI installs, and the wizard consumes the `wizard.fields` section to build forms. Example snippet:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "zigbee2mqtt",
|
||||||
|
"wizard": {
|
||||||
|
"uci": { "config": "zigbee2mqtt", "section": "main" },
|
||||||
|
"fields": [
|
||||||
|
{ "id": "serial_port", "label": "Serial Port", "uci_option": "serial_port" },
|
||||||
|
{ "id": "mqtt_host", "label": "MQTT Host", "uci_option": "mqtt_host" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Clicking “Configure” opens a modal that writes the provided values into the specified UCI section.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Profiles
|
||||||
|
|
||||||
|
Profiles are stored as JSON in `/usr/share/secubox/profiles/` and can bundle:
|
||||||
|
|
||||||
|
- `network_mode`: target SecuBox network mode (`router`, `dmz`, …)
|
||||||
|
- `apps`: manifest IDs to install via `secubox-app install <id>`
|
||||||
|
- `packages`: additional packages to ensure via opkg/apk
|
||||||
|
- `uci`: array of `{config, section, option, value}` entries applied via UCI
|
||||||
|
|
||||||
|
Baseline profiles:
|
||||||
|
|
||||||
|
| ID | Description | Highlights |
|
||||||
|
|----|-------------|------------|
|
||||||
|
| `home` | Home router + Zigbee2MQTT | Router mode, installs Zigbee2MQTT + Netdata |
|
||||||
|
| `lab` | Monitoring lab | Router mode, ensures Netifyd & Bandwidth Manager |
|
||||||
|
| `hardened` | Security-focused | Enables CrowdSec + Client Guardian |
|
||||||
|
| `gateway_dmz` | Router + DMZ segment | Switches to DMZ mode and enables VHost manager |
|
||||||
|
|
||||||
|
`apply_profile` automatically tars `/etc/config` to `/etc/secubox-profiles/backups/` before modifying settings, so the **Rollback last profile** button (or `rollback_profile` RPC) instantly restores prior UCI files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLI References
|
||||||
|
|
||||||
|
- `secubox-app list|install|status` – manage app manifests (installed by `luci-app-secubox`).
|
||||||
|
- `ubus call luci.secubox list_profiles` – enumerate available profile manifests.
|
||||||
|
- `ubus call luci.secubox apply_profile '{"profile_id":"home"}'` – apply a preset programmatically.
|
||||||
|
- `secubox-app` respects `SECUBOX_PLUGINS_DIR` if you need to point to custom manifest trees.
|
||||||
|
|
||||||
|
Combine the wizard UI with these commands to automate deployments or build higher-level orchestration (e.g., App Store pages, onboarding scripts).
|
||||||
@ -28,6 +28,9 @@ These notes capture the current repository structure, conventions, and supportin
|
|||||||
- **Docs**
|
- **Docs**
|
||||||
Two mirrored trees (`docs/` and uppercase `DOCS/`) feed MkDocs and the GitHub wiki. All Markdown follows the metadata template defined in `docs/documentation-index.md`.
|
Two mirrored trees (`docs/` and uppercase `DOCS/`) feed MkDocs and the GitHub wiki. All Markdown follows the metadata template defined in `docs/documentation-index.md`.
|
||||||
|
|
||||||
|
- **Profiles & App Manifests**
|
||||||
|
App manifests now live under `/usr/share/secubox/plugins/`, profiles under `/usr/share/secubox/profiles/`. `luci-app-secubox` exposes both through the wizard (UI) and `secubox-app` CLI (automation).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Target Platforms & Build System
|
## 2. Target Platforms & Build System
|
||||||
|
|||||||
@ -247,6 +247,18 @@ Follow this template when creating or revising documentation:
|
|||||||
|
|
||||||
**Size:** Short (~120 lines)
|
**Size:** Short (~120 lines)
|
||||||
|
|
||||||
|
#### **embedded/wizard-profiles.md** 🧭
|
||||||
|
*First-run wizard + OS-like profiles.*
|
||||||
|
|
||||||
|
**Contents:**
|
||||||
|
- First-run checklist (password, timezone, storage, network mode)
|
||||||
|
- Using manifest-driven app wizards
|
||||||
|
- Baseline profiles (Home, Lab, Hardened, Gateway+DMZ) and rollback mechanics
|
||||||
|
|
||||||
|
**When to use:** Guiding new deployments or applying pre-made bundles.
|
||||||
|
|
||||||
|
**Size:** Short (~130 lines)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5. Tools & Scripts Documentation
|
### 5. Tools & Scripts Documentation
|
||||||
|
|||||||
74
docs/embedded/wizard-profiles.md
Normal file
74
docs/embedded/wizard-profiles.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# SecuBox Wizard & Profiles
|
||||||
|
|
||||||
|
**Version:** 1.0.0
|
||||||
|
**Last Updated:** 2025-12-28
|
||||||
|
**Status:** Active
|
||||||
|
|
||||||
|
The SecuBox hub now includes a guided setup wizard (LuCI → SecuBox → Wizard) and profile system. Use them to finish the first-run checklist, review app manifests, and apply predefined OS-like configurations (Home, Lab, Hardened, Gateway + DMZ).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## First-Run Checklist
|
||||||
|
|
||||||
|
The wizard queries `luci.secubox`’s `first_run_status` ubus method to determine whether critical items are configured:
|
||||||
|
|
||||||
|
1. **Administrator password** – links to the LuCI password page.
|
||||||
|
2. **Timezone** – dropdown populated with common timezones; applying calls `apply_first_run` with `{ timezone: "Europe/Paris" }`.
|
||||||
|
3. **Storage path** – defaults to `/srv/secubox`; prepares the directory and stores it in `uci set secubox.main.storage_path`.
|
||||||
|
4. **Network mode** – uses the existing Network Modes RPC to switch between `router` and `dmz` presets.
|
||||||
|
|
||||||
|
Each action can be run independently and is idempotent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## App Wizards (Manifests)
|
||||||
|
|
||||||
|
Apps ship manifests under `/usr/share/secubox/plugins/<id>/manifest.json`. `secubox-app` (installed at `/usr/sbin/secubox-app`) uses the same manifests for CLI installs, and the wizard consumes the `wizard.fields` section to build forms. Example snippet:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "zigbee2mqtt",
|
||||||
|
"wizard": {
|
||||||
|
"uci": { "config": "zigbee2mqtt", "section": "main" },
|
||||||
|
"fields": [
|
||||||
|
{ "id": "serial_port", "label": "Serial Port", "uci_option": "serial_port" },
|
||||||
|
{ "id": "mqtt_host", "label": "MQTT Host", "uci_option": "mqtt_host" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Clicking “Configure” opens a modal that writes the provided values into the specified UCI section.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Profiles
|
||||||
|
|
||||||
|
Profiles are stored as JSON in `/usr/share/secubox/profiles/` and can bundle:
|
||||||
|
|
||||||
|
- `network_mode`: target SecuBox network mode (`router`, `dmz`, …)
|
||||||
|
- `apps`: manifest IDs to install via `secubox-app install <id>`
|
||||||
|
- `packages`: additional packages to ensure via opkg/apk
|
||||||
|
- `uci`: array of `{config, section, option, value}` entries applied via UCI
|
||||||
|
|
||||||
|
Baseline profiles:
|
||||||
|
|
||||||
|
| ID | Description | Highlights |
|
||||||
|
|----|-------------|------------|
|
||||||
|
| `home` | Home router + Zigbee2MQTT | Router mode, installs Zigbee2MQTT + Netdata |
|
||||||
|
| `lab` | Monitoring lab | Router mode, ensures Netifyd & Bandwidth Manager |
|
||||||
|
| `hardened` | Security-focused | Enables CrowdSec + Client Guardian |
|
||||||
|
| `gateway_dmz` | Router + DMZ segment | Switches to DMZ mode and enables VHost manager |
|
||||||
|
|
||||||
|
`apply_profile` automatically tars `/etc/config` to `/etc/secubox-profiles/backups/` before modifying settings, so the **Rollback last profile** button (or `rollback_profile` RPC) instantly restores prior UCI files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLI References
|
||||||
|
|
||||||
|
- `secubox-app list|install|status` – manage app manifests (installed by `luci-app-secubox`).
|
||||||
|
- `ubus call luci.secubox list_profiles` – enumerate available profile manifests.
|
||||||
|
- `ubus call luci.secubox apply_profile '{"profile_id":"home"}'` – apply a preset programmatically.
|
||||||
|
- `secubox-app` respects `SECUBOX_PLUGINS_DIR` if you need to point to custom manifest trees.
|
||||||
|
|
||||||
|
Combine the wizard UI with these commands to automate deployments or build higher-level orchestration (e.g., App Store pages, onboarding scripts).
|
||||||
@ -26,6 +26,10 @@ define Package/$(PKG_NAME)/install
|
|||||||
$(call Package/luci/install,$(1))
|
$(call Package/luci/install,$(1))
|
||||||
$(INSTALL_DIR) $(1)/usr/share/secubox/plugins/zigbee2mqtt
|
$(INSTALL_DIR) $(1)/usr/share/secubox/plugins/zigbee2mqtt
|
||||||
$(INSTALL_DATA) $(CURDIR)/../plugins/zigbee2mqtt/manifest.json $(1)/usr/share/secubox/plugins/zigbee2mqtt/manifest.json
|
$(INSTALL_DATA) $(CURDIR)/../plugins/zigbee2mqtt/manifest.json $(1)/usr/share/secubox/plugins/zigbee2mqtt/manifest.json
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/secubox/profiles
|
||||||
|
for file in $(CURDIR)/../profiles/*.json; do \
|
||||||
|
$(INSTALL_DATA) $$file $(1)/usr/share/secubox/profiles/$$(basename $$file); \
|
||||||
|
done
|
||||||
$(INSTALL_DIR) $(1)/usr/sbin
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
$(INSTALL_BIN) $(CURDIR)/../secubox-tools/secubox-app $(1)/usr/sbin/secubox-app
|
$(INSTALL_BIN) $(CURDIR)/../secubox-tools/secubox-app $(1)/usr/sbin/secubox-app
|
||||||
endef
|
endef
|
||||||
|
|||||||
@ -174,6 +174,23 @@ var callApplyAppWizard = rpc.declare({
|
|||||||
params: ['app_id', 'values']
|
params: ['app_id', 'values']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callListProfiles = rpc.declare({
|
||||||
|
object: 'luci.secubox',
|
||||||
|
method: 'list_profiles',
|
||||||
|
expect: { profiles: [] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callApplyProfile = rpc.declare({
|
||||||
|
object: 'luci.secubox',
|
||||||
|
method: 'apply_profile',
|
||||||
|
params: ['profile_id']
|
||||||
|
});
|
||||||
|
|
||||||
|
var callRollbackProfile = rpc.declare({
|
||||||
|
object: 'luci.secubox',
|
||||||
|
method: 'rollback_profile'
|
||||||
|
});
|
||||||
|
|
||||||
function formatUptime(seconds) {
|
function formatUptime(seconds) {
|
||||||
if (!seconds) return '0s';
|
if (!seconds) return '0s';
|
||||||
var d = Math.floor(seconds / 86400);
|
var d = Math.floor(seconds / 86400);
|
||||||
@ -222,6 +239,9 @@ return baseclass.extend({
|
|||||||
listApps: callListApps,
|
listApps: callListApps,
|
||||||
getAppManifest: callGetAppManifest,
|
getAppManifest: callGetAppManifest,
|
||||||
applyAppWizard: callApplyAppWizard,
|
applyAppWizard: callApplyAppWizard,
|
||||||
|
listProfiles: callListProfiles,
|
||||||
|
applyProfile: callApplyProfile,
|
||||||
|
rollbackProfile: callRollbackProfile,
|
||||||
// Utilities
|
// Utilities
|
||||||
formatUptime: formatUptime,
|
formatUptime: formatUptime,
|
||||||
formatBytes: formatBytes
|
formatBytes: formatBytes
|
||||||
|
|||||||
@ -22,18 +22,21 @@ return view.extend({
|
|||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
API.getFirstRunStatus(),
|
API.getFirstRunStatus(),
|
||||||
API.listApps()
|
API.listApps(),
|
||||||
|
API.listProfiles()
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(payload) {
|
render: function(payload) {
|
||||||
this.firstRun = payload[0] || {};
|
this.firstRun = payload[0] || {};
|
||||||
this.appList = (payload[1] && payload[1].apps) || [];
|
this.appList = (payload[1] && payload[1].apps) || [];
|
||||||
|
this.profileList = (payload[2] && payload[2].profiles) || [];
|
||||||
var container = E('div', { 'class': 'secubox-wizard-page' }, [
|
var container = E('div', { 'class': 'secubox-wizard-page' }, [
|
||||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
|
||||||
SecuNav.renderTabs('wizard'),
|
SecuNav.renderTabs('wizard'),
|
||||||
this.renderHeader(),
|
this.renderHeader(),
|
||||||
this.renderFirstRunCard(),
|
this.renderFirstRunCard(),
|
||||||
|
this.renderProfilesCard(),
|
||||||
this.renderAppsCard()
|
this.renderAppsCard()
|
||||||
]);
|
]);
|
||||||
return container;
|
return container;
|
||||||
@ -142,6 +145,44 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderProfilesCard: function() {
|
||||||
|
var profiles = this.profileList || [];
|
||||||
|
return E('div', { 'class': 'sb-wizard-card' }, [
|
||||||
|
E('div', { 'class': 'sb-wizard-title' }, ['🧱 ', _('Profiles')]),
|
||||||
|
profiles.length ? E('div', { 'class': 'sb-app-grid' }, profiles.map(this.renderProfileCard, this)) :
|
||||||
|
E('div', { 'class': 'secubox-empty-state' }, [
|
||||||
|
E('div', { 'class': 'secubox-empty-icon' }, '📭'),
|
||||||
|
E('div', { 'class': 'secubox-empty-title' }, _('No profiles available')),
|
||||||
|
E('div', { 'class': 'secubox-empty-text' }, _('Profiles are stored in /usr/share/secubox/profiles/.'))
|
||||||
|
]),
|
||||||
|
profiles.length ? E('div', { 'class': 'right', 'style': 'margin-top:12px;' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button cbi-button-action',
|
||||||
|
'click': this.rollbackProfile.bind(this)
|
||||||
|
}, _('Rollback last profile'))
|
||||||
|
]) : ''
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderProfileCard: function(profile) {
|
||||||
|
var apps = profile.apps || [];
|
||||||
|
return E('div', { 'class': 'sb-app-card' }, [
|
||||||
|
E('div', { 'class': 'sb-app-card-info' }, [
|
||||||
|
E('div', { 'class': 'sb-app-name' }, [profile.name || profile.id]),
|
||||||
|
E('div', { 'class': 'sb-app-desc' }, profile.description || ''),
|
||||||
|
E('div', { 'class': 'sb-app-desc' }, _('Network mode: %s').format(profile.network_mode || '—')),
|
||||||
|
apps.length ? E('div', { 'class': 'sb-app-desc' }, _('Apps: %s').format(apps.join(', '))) : ''
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'sb-app-actions' }, [
|
||||||
|
E('span', { 'class': 'sb-app-state' + (profile.state === 'installed' ? ' ok' : '') }, profile.state || 'n/a'),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button cbi-button-action',
|
||||||
|
'click': this.applyProfile.bind(this, profile.id)
|
||||||
|
}, _('Apply'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
renderAppCard: function(app) {
|
renderAppCard: function(app) {
|
||||||
return E('div', { 'class': 'sb-app-card' }, [
|
return E('div', { 'class': 'sb-app-card' }, [
|
||||||
E('div', { 'class': 'sb-app-card-info' }, [
|
E('div', { 'class': 'sb-app-card-info' }, [
|
||||||
@ -241,5 +282,31 @@ return view.extend({
|
|||||||
ui.addNotification(null, E('p', {}, _('Failed to apply wizard.')), 'error');
|
ui.addNotification(null, E('p', {}, _('Failed to apply wizard.')), 'error');
|
||||||
}
|
}
|
||||||
}).catch(this.showError);
|
}).catch(this.showError);
|
||||||
|
},
|
||||||
|
|
||||||
|
applyProfile: function(profileId) {
|
||||||
|
if (!profileId)
|
||||||
|
return;
|
||||||
|
ui.showModal(_('Applying profile…'), [E('div', { 'class': 'spinning' })]);
|
||||||
|
API.applyProfile(profileId).then(function(result) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (result && result.success) {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Profile applied. A reboot may be required.')), 'info');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', {}, (result && result.error) || _('Failed to apply profile')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(this.showError);
|
||||||
|
},
|
||||||
|
|
||||||
|
rollbackProfile: function() {
|
||||||
|
ui.showModal(_('Rolling back…'), [E('div', { 'class': 'spinning' })]);
|
||||||
|
API.rollbackProfile().then(function(result) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (result && result.success) {
|
||||||
|
ui.addNotification(null, E('p', {}, result.message || _('Rollback completed.')), 'info');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', {}, (result && result.error) || _('Rollback failed.')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(this.showError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -55,7 +55,11 @@ get_pkg_version() {
|
|||||||
PKG_VERSION="$(get_pkg_version)"
|
PKG_VERSION="$(get_pkg_version)"
|
||||||
|
|
||||||
PLUGIN_DIR="/usr/share/secubox/plugins"
|
PLUGIN_DIR="/usr/share/secubox/plugins"
|
||||||
|
PROFILE_DIR="/usr/share/secubox/profiles"
|
||||||
|
PROFILE_BACKUP_DIR="/etc/secubox-profiles/backups"
|
||||||
DEFAULT_STORAGE_PATH="/srv/secubox"
|
DEFAULT_STORAGE_PATH="/srv/secubox"
|
||||||
|
SECOBOX_APP="/usr/sbin/secubox-app"
|
||||||
|
OPKG_UPDATED=0
|
||||||
|
|
||||||
# Module registry - auto-detected from /usr/libexec/rpcd/
|
# Module registry - auto-detected from /usr/libexec/rpcd/
|
||||||
detect_modules() {
|
detect_modules() {
|
||||||
@ -127,6 +131,34 @@ ensure_directory() {
|
|||||||
[ -d "$dir" ] || mkdir -p "$dir"
|
[ -d "$dir" ] || mkdir -p "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_network_mode() {
|
||||||
|
local mode="$1"
|
||||||
|
[ -n "$mode" ] || return
|
||||||
|
if command -v ubus >/dev/null 2>&1; then
|
||||||
|
if ubus call luci.network-modes set_mode "{\"mode\":\"$mode\"}" >/dev/null 2>&1; then
|
||||||
|
ubus call luci.network-modes apply_mode >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_opkg_updated() {
|
||||||
|
[ "$OPKG_UPDATED" -eq 1 ] && return
|
||||||
|
if command -v opkg >/dev/null 2>&1; then
|
||||||
|
opkg update >/dev/null 2>&1 && OPKG_UPDATED=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_package_if_missing() {
|
||||||
|
local pkg="$1"
|
||||||
|
package_installed "$pkg" && return
|
||||||
|
if command -v opkg >/dev/null 2>&1; then
|
||||||
|
ensure_opkg_updated
|
||||||
|
opkg install "$pkg" >/dev/null 2>&1
|
||||||
|
elif command -v apk >/dev/null 2>&1; then
|
||||||
|
apk add "$pkg" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Check if a module is installed (supports both opkg and apk)
|
# Check if a module is installed (supports both opkg and apk)
|
||||||
check_module_installed() {
|
check_module_installed() {
|
||||||
local module="$1"
|
local module="$1"
|
||||||
@ -1324,6 +1356,136 @@ apply_app_wizard() {
|
|||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
json_dump
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list_profiles() {
|
||||||
|
ensure_directory "$PROFILE_DIR"
|
||||||
|
json_init
|
||||||
|
json_add_array "profiles"
|
||||||
|
local profile_file
|
||||||
|
for profile_file in "$PROFILE_DIR"/*.json; do
|
||||||
|
[ -f "$profile_file" ] || continue
|
||||||
|
local profile_json id name description network_mode apps state
|
||||||
|
profile_json=$(cat "$profile_file")
|
||||||
|
id=$(jsonfilter -s "$profile_json" -e '@.id' 2>/dev/null)
|
||||||
|
name=$(jsonfilter -s "$profile_json" -e '@.name' 2>/dev/null)
|
||||||
|
description=$(jsonfilter -s "$profile_json" -e '@.description' 2>/dev/null)
|
||||||
|
network_mode=$(jsonfilter -s "$profile_json" -e '@.network_mode' 2>/dev/null)
|
||||||
|
apps=$(jsonfilter -s "$profile_json" -e '@.apps[*]' 2>/dev/null)
|
||||||
|
state=$(packages_state "$profile_json")
|
||||||
|
[ -n "$id" ] || continue
|
||||||
|
json_add_object
|
||||||
|
json_add_string "id" "$id"
|
||||||
|
json_add_string "name" "$name"
|
||||||
|
json_add_string "description" "$description"
|
||||||
|
json_add_string "network_mode" "$network_mode"
|
||||||
|
json_add_string "state" "$state"
|
||||||
|
json_add_array "apps"
|
||||||
|
for app in $apps; do
|
||||||
|
json_add_string "" "$app"
|
||||||
|
done
|
||||||
|
json_close_array
|
||||||
|
json_close_object
|
||||||
|
done
|
||||||
|
json_close_array
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_profile() {
|
||||||
|
local input profile_id profile_file profile_json network_mode packages apps backup_file notes=""
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var profile_id profile_id
|
||||||
|
json_cleanup
|
||||||
|
[ -n "$profile_id" ] || {
|
||||||
|
json_init; json_add_boolean "success" 0; json_add_string "error" "profile_id required"; json_dump; return;
|
||||||
|
}
|
||||||
|
profile_file="$PROFILE_DIR/$profile_id.json"
|
||||||
|
if [ ! -f "$profile_file" ]; then
|
||||||
|
json_init; json_add_boolean "success" 0; json_add_string "error" "Profile not found"; json_dump; return;
|
||||||
|
fi
|
||||||
|
profile_json=$(cat "$profile_file")
|
||||||
|
ensure_directory "$PROFILE_BACKUP_DIR"
|
||||||
|
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||||
|
backup_file="$PROFILE_BACKUP_DIR/profile_${profile_id}_${timestamp}.tar.gz"
|
||||||
|
tar -czf "$backup_file" /etc/config 2>/dev/null
|
||||||
|
notes="${notes}Backup saved to $backup_file\n"
|
||||||
|
uci set secubox.profile.last_backup="$backup_file"
|
||||||
|
uci set secubox.profile.last_profile="$profile_id"
|
||||||
|
uci commit secubox
|
||||||
|
network_mode=$(jsonfilter -s "$profile_json" -e '@.network_mode' 2>/dev/null)
|
||||||
|
if [ -n "$network_mode" ]; then
|
||||||
|
notes="${notes}Switched network mode to $network_mode\n"
|
||||||
|
apply_network_mode "$network_mode"
|
||||||
|
fi
|
||||||
|
packages=$(jsonfilter -s "$profile_json" -e '@.packages[*]' 2>/dev/null)
|
||||||
|
for pkg in $packages; do
|
||||||
|
install_package_if_missing "$pkg"
|
||||||
|
notes="${notes}Ensured package $pkg\n"
|
||||||
|
done
|
||||||
|
apps=$(jsonfilter -s "$profile_json" -e '@.apps[*]' 2>/dev/null)
|
||||||
|
for app in $apps; do
|
||||||
|
if [ -x "$SECOBOX_APP" ]; then
|
||||||
|
$SECOBOX_APP install "$app" >/dev/null 2>&1 && notes="${notes}Installed app $app\n"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
json_load "$profile_json"
|
||||||
|
local uci_configs=""
|
||||||
|
if json_select uci >/dev/null 2>&1; then
|
||||||
|
local entries
|
||||||
|
json_get_keys entries
|
||||||
|
for entry in $entries; do
|
||||||
|
json_select "$entry"
|
||||||
|
local cfg section option value
|
||||||
|
json_get_var cfg config
|
||||||
|
json_get_var section section
|
||||||
|
json_get_var option option
|
||||||
|
json_get_var value value
|
||||||
|
section=${section:-main}
|
||||||
|
if [ -n "$cfg" ] && [ -n "$option" ]; then
|
||||||
|
uci set ${cfg}.${section}.${option}="$value"
|
||||||
|
uci_configs="${uci_configs} $cfg"
|
||||||
|
notes="${notes}Set ${cfg}.${section}.${option}=${value}\n"
|
||||||
|
fi
|
||||||
|
json_select ..
|
||||||
|
done
|
||||||
|
json_select ..
|
||||||
|
fi
|
||||||
|
json_cleanup
|
||||||
|
local committed=""
|
||||||
|
for cfg in $uci_configs; do
|
||||||
|
case " $committed " in
|
||||||
|
*" $cfg "*) continue;;
|
||||||
|
esac
|
||||||
|
uci commit "$cfg"
|
||||||
|
committed="$committed $cfg"
|
||||||
|
done
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_array "messages"
|
||||||
|
printf '%b' "$notes" | while IFS= read -r line; do
|
||||||
|
[ -n "$line" ] && json_add_string "" "$line"
|
||||||
|
done
|
||||||
|
json_close_array
|
||||||
|
json_add_string "backup" "$backup_file"
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback_profile() {
|
||||||
|
local latest_backup
|
||||||
|
latest_backup=$(ls -t "$PROFILE_BACKUP_DIR"/profile_*.tar.gz 2>/dev/null | head -1)
|
||||||
|
if [ -z "$latest_backup" ]; then
|
||||||
|
json_init; json_add_boolean "success" 0; json_add_string "error" "No backups available"; json_dump; return;
|
||||||
|
fi
|
||||||
|
cd /
|
||||||
|
tar -xzf "$latest_backup" 2>/dev/null
|
||||||
|
/etc/init.d/network reload >/dev/null 2>&1
|
||||||
|
/etc/init.d/firewall reload >/dev/null 2>&1
|
||||||
|
/etc/init.d/dnsmasq reload >/dev/null 2>&1
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "Restored $latest_backup"
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list)
|
list)
|
||||||
json_init
|
json_init
|
||||||
@ -1392,6 +1554,25 @@ case "$1" in
|
|||||||
json_add_object "apply_app_wizard"
|
json_add_object "apply_app_wizard"
|
||||||
json_add_string "app_id" "string"
|
json_add_string "app_id" "string"
|
||||||
json_close_object
|
json_close_object
|
||||||
|
json_add_object "list_profiles"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "apply_profile"
|
||||||
|
json_add_string "profile_id" "string"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "rollback_profile"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "first_run_status"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "apply_first_run"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "list_apps"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "get_app_manifest"
|
||||||
|
json_add_string "app_id" "string"
|
||||||
|
json_close_object
|
||||||
|
json_add_object "apply_app_wizard"
|
||||||
|
json_add_string "app_id" "string"
|
||||||
|
json_close_object
|
||||||
json_dump
|
json_dump
|
||||||
;;
|
;;
|
||||||
call)
|
call)
|
||||||
@ -1507,6 +1688,15 @@ case "$1" in
|
|||||||
apply_app_wizard)
|
apply_app_wizard)
|
||||||
apply_app_wizard
|
apply_app_wizard
|
||||||
;;
|
;;
|
||||||
|
list_profiles)
|
||||||
|
list_profiles
|
||||||
|
;;
|
||||||
|
apply_profile)
|
||||||
|
apply_profile
|
||||||
|
;;
|
||||||
|
rollback_profile)
|
||||||
|
rollback_profile
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo '{"error":"Unknown method"}'
|
echo '{"error":"Unknown method"}'
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -17,7 +17,8 @@
|
|||||||
"get_theme",
|
"get_theme",
|
||||||
"first_run_status",
|
"first_run_status",
|
||||||
"list_apps",
|
"list_apps",
|
||||||
"get_app_manifest"
|
"get_app_manifest",
|
||||||
|
"list_profiles"
|
||||||
],
|
],
|
||||||
"uci": [
|
"uci": [
|
||||||
"get",
|
"get",
|
||||||
@ -42,7 +43,9 @@
|
|||||||
"clear_alerts",
|
"clear_alerts",
|
||||||
"fix_permissions",
|
"fix_permissions",
|
||||||
"apply_first_run",
|
"apply_first_run",
|
||||||
"apply_app_wizard"
|
"apply_app_wizard",
|
||||||
|
"apply_profile",
|
||||||
|
"rollback_profile"
|
||||||
],
|
],
|
||||||
"uci": [
|
"uci": [
|
||||||
"set",
|
"set",
|
||||||
|
|||||||
11
profiles/gateway_dmz.json
Normal file
11
profiles/gateway_dmz.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"id": "gateway_dmz",
|
||||||
|
"name": "Gateway + DMZ",
|
||||||
|
"description": "Router with isolated DMZ segment for exposed services.",
|
||||||
|
"network_mode": "dmz",
|
||||||
|
"apps": ["zigbee2mqtt"],
|
||||||
|
"packages": ["luci-app-vhost-manager"],
|
||||||
|
"uci": [
|
||||||
|
{ "config": "secubox", "section": "vhost_manager", "option": "enabled", "value": "1" }
|
||||||
|
]
|
||||||
|
}
|
||||||
12
profiles/hardened.json
Normal file
12
profiles/hardened.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "hardened",
|
||||||
|
"name": "Hardened",
|
||||||
|
"description": "Security-focused preset enabling CrowdSec and Client Guardian.",
|
||||||
|
"network_mode": "router",
|
||||||
|
"apps": [],
|
||||||
|
"packages": ["luci-app-crowdsec-dashboard", "luci-app-client-guardian"],
|
||||||
|
"uci": [
|
||||||
|
{ "config": "secubox", "section": "crowdsec", "option": "enabled", "value": "1" },
|
||||||
|
{ "config": "secubox", "section": "client_guardian", "option": "enabled", "value": "1" }
|
||||||
|
]
|
||||||
|
}
|
||||||
12
profiles/home.json
Normal file
12
profiles/home.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "home",
|
||||||
|
"name": "Home",
|
||||||
|
"description": "Default home router with Zigbee and monitoring enabled.",
|
||||||
|
"network_mode": "router",
|
||||||
|
"apps": ["zigbee2mqtt"],
|
||||||
|
"packages": ["luci-app-netdata-dashboard"],
|
||||||
|
"uci": [
|
||||||
|
{ "config": "secubox", "section": "netdata", "option": "enabled", "value": "1" },
|
||||||
|
{ "config": "secubox", "section": "zigbee2mqtt", "option": "enabled", "value": "1" }
|
||||||
|
]
|
||||||
|
}
|
||||||
12
profiles/lab.json
Normal file
12
profiles/lab.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "lab",
|
||||||
|
"name": "Lab",
|
||||||
|
"description": "Extended monitoring and DPI for test environments.",
|
||||||
|
"network_mode": "router",
|
||||||
|
"apps": [],
|
||||||
|
"packages": ["luci-app-netifyd-dashboard", "luci-app-netdata-dashboard"],
|
||||||
|
"uci": [
|
||||||
|
{ "config": "secubox", "section": "netifyd", "option": "enabled", "value": "1" },
|
||||||
|
{ "config": "secubox", "section": "bandwidth_manager", "option": "enabled", "value": "1" }
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user