feat(storage): support configurable install root

Add a LuCI install-root input, persist the selected path in UCI,
and route install, status, backup, uninstall, and runtime scripts
through the configured storage root for new installs.

Reference: custom install root flow
This commit is contained in:
2026-03-18 13:48:07 +08:00
parent ee10bb0bd5
commit 68f24e6658
17 changed files with 739 additions and 122 deletions

View File

@@ -0,0 +1,144 @@
# Custom Install Root Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Let new OpenClaw installs choose a LuCI-specified storage root and keep all runtime files under that configured root.
**Architecture:** Add a persisted UCI install-root setting, a shared shell path helper, and a small Lua path helper so UI checks and runtime scripts all derive the same directories. Keep `/opt` as the default and preserve the existing `/openclaw/{node,global,data}` subtree under whichever root the user selects.
**Tech Stack:** LuCI Lua, POSIX shell, Node.js runtime scripts, OpenWrt UCI
---
### Task 1: Add failing path derivation tests
**Files:**
- Create: `tests/test_openclaw_paths.lua`
- Create: `luasrc/openclaw/paths.lua`
- [ ] **Step 1: Write the failing test**
Create Lua assertions for:
- default root -> `/opt`
- `/mnt/emmc/` -> `/mnt/emmc`
- derived paths under `<root>/openclaw`
- invalid relative paths fall back to `/opt`
- [ ] **Step 2: Run test to verify it fails**
Run: `lua /Users/lingyuzeng/project/luci-app-openclaw/tests/test_openclaw_paths.lua`
Expected: FAIL because `luasrc/openclaw/paths.lua` does not exist yet.
- [ ] **Step 3: Write minimal implementation**
Add a pure-Lua helper exposing normalization and derived-path functions.
- [ ] **Step 4: Run test to verify it passes**
Run: `lua /Users/lingyuzeng/project/luci-app-openclaw/tests/test_openclaw_paths.lua`
Expected: PASS
### Task 2: Add shared shell path helper
**Files:**
- Create: `root/usr/libexec/openclaw-paths.sh`
- Modify: `Makefile`
- [ ] **Step 1: Write the failing test**
Extend the Lua test or add a shell smoke command that expects the helper to emit normalized install root and derived directories.
- [ ] **Step 2: Run test to verify it fails**
Run: `sh -c '. /Users/lingyuzeng/project/luci-app-openclaw/root/usr/libexec/openclaw-paths.sh; oc_load_paths'`
Expected: FAIL because the helper does not exist yet.
- [ ] **Step 3: Write minimal implementation**
Implement helper functions for normalization, derived directories, and `/opt`-specific OverlayFS handling guards. Install the helper in the package Makefile.
- [ ] **Step 4: Run test to verify it passes**
Run: `sh -n /Users/lingyuzeng/project/luci-app-openclaw/root/usr/libexec/openclaw-paths.sh`
Expected: PASS
### Task 3: Wire LuCI dialog and APIs
**Files:**
- Modify: `luasrc/model/cbi/openclaw/basic.lua`
- Modify: `luasrc/controller/openclaw.lua`
- Modify: `root/etc/config/openclaw`
- [ ] **Step 1: Add failing coverage mindset**
Use the existing Lua path test as the guardrail for normalization and manually confirm current UI/API code still hard-codes `/opt`.
- [ ] **Step 2: Implement API and dialog changes**
Add:
- UCI-backed default install root
- dialog input and explanatory copy
- `check_system` support for `install_root`
- `setup` support for persisting the root and refusing live path switches
- [ ] **Step 3: Verify behavior**
Run:
- `lua -e 'assert(loadfile("/Users/lingyuzeng/project/luci-app-openclaw/luasrc/controller/openclaw.lua"))'`
- `lua -e 'assert(loadfile("/Users/lingyuzeng/project/luci-app-openclaw/luasrc/model/cbi/openclaw/basic.lua"))'`
Expected: PASS
### Task 4: Route runtime scripts through the configured root
**Files:**
- Modify: `root/usr/bin/openclaw-env`
- Modify: `root/etc/init.d/openclaw`
- Modify: `root/etc/profile.d/openclaw.sh`
- Modify: `root/etc/uci-defaults/99-openclaw`
- Modify: `root/usr/share/openclaw/oc-config.sh`
- [ ] **Step 1: Update scripts**
Source the shared shell helper, derive paths from UCI or `OPENCLAW_INSTALL_ROOT`, and keep the `/opt` workaround only for the default root.
- [ ] **Step 2: Verify syntax**
Run:
- `sh -n /Users/lingyuzeng/project/luci-app-openclaw/root/usr/bin/openclaw-env`
- `sh -n /Users/lingyuzeng/project/luci-app-openclaw/root/etc/init.d/openclaw`
- `sh -n /Users/lingyuzeng/project/luci-app-openclaw/root/etc/profile.d/openclaw.sh`
- `sh -n /Users/lingyuzeng/project/luci-app-openclaw/root/etc/uci-defaults/99-openclaw`
- `sh -n /Users/lingyuzeng/project/luci-app-openclaw/root/usr/share/openclaw/oc-config.sh`
Expected: PASS
### Task 5: Fix remaining controller/runtime path call sites and verify
**Files:**
- Modify: `luasrc/controller/openclaw.lua`
- Modify: `luasrc/model/cbi/openclaw/basic.lua`
- Modify: `README.md`
- [ ] **Step 1: Replace remaining hard-coded `/opt/openclaw` references used by user-facing flows**
Cover status, uninstall, backup/restore, error hints, and install dialog copy.
- [ ] **Step 2: Run full verification**
Run:
- `lua /Users/lingyuzeng/project/luci-app-openclaw/tests/test_openclaw_paths.lua`
- `lua -e 'assert(loadfile("/Users/lingyuzeng/project/luci-app-openclaw/luasrc/controller/openclaw.lua"))'`
- `lua -e 'assert(loadfile("/Users/lingyuzeng/project/luci-app-openclaw/luasrc/model/cbi/openclaw/basic.lua"))'`
- `rg -n "/opt/openclaw" /Users/lingyuzeng/project/luci-app-openclaw`
Expected:
- tests and syntax checks PASS
- remaining `/opt/openclaw` hits are limited to historical docs or intentional compatibility text
- [ ] **Step 3: Commit**
```bash
git -C /Users/lingyuzeng/project/luci-app-openclaw add .
git -C /Users/lingyuzeng/project/luci-app-openclaw commit -m "feat: support configurable install root"
```

View File

@@ -0,0 +1,85 @@
# Custom Install Root Design
## Goal
Allow new OpenClaw installations to choose a user-specified storage root from the LuCI install dialog, use that path for pre-install disk checks, and store all runtime files under the chosen root instead of forcing `/opt`.
## Scope
- Add an install-root input to the LuCI install dialog with guidance for mounted eMMC paths such as `/mnt/emmc`.
- Persist the selected root in UCI so follow-up actions use the same location.
- Route installation, runtime startup, status checks, backup/restore, and uninstall through the configured root.
- Keep the behavior migration-free: changing the root does not move existing `/opt/openclaw` data.
## Non-Goals
- Automatic migration of an existing installation between storage roots.
- Support for relative paths or multiple active installation roots.
- Reworking historical docs that describe `/opt/openclaw` beyond the most user-facing references touched by this feature.
## Design
### Configuration model
Persist a new UCI option `openclaw.main.install_root`, defaulting to `/opt`. The value represents the parent mount path chosen by the user. Runtime directories continue to be derived in a fixed layout:
- `<install_root>/openclaw/node`
- `<install_root>/openclaw/global`
- `<install_root>/openclaw/data`
This preserves the existing internal layout while letting the outer storage location move to eMMC or other mounted storage.
### UI behavior
The install dialog will expose an "安装根目录 / 检测目录" input with inline guidance that users should enter the mounted storage path, for example `/mnt/emmc`, and that OpenClaw files will be created under `<path>/openclaw/`.
On confirm:
- The frontend sends the selected root to the system-check API.
- The frontend shows both the detection path and the actual install path in the log panel.
- The same root is sent to the setup API.
If the runtime is already installed and the requested root differs from the configured root, setup should refuse and instruct the user to uninstall first. That enforces the "new installs only" rule.
### Path resolution
Introduce a shared shell helper for runtime scripts plus a small Lua helper for controller code. Both normalize the configured install root by:
- Requiring an absolute path.
- Trimming trailing slashes except for `/`.
- Falling back to `/opt` when the value is empty or invalid.
Shell scripts consume the helper directly. Lua code uses the Lua helper so controller actions can derive paths without hard-coding `/opt/openclaw`.
### System check
The system-check API accepts an optional `install_root` parameter. It checks:
- Total physical memory against the existing 1024 MB threshold.
- Disk space on the requested root, or the nearest existing ancestor when the exact directory does not exist yet.
The API returns the normalized install root, actual OpenClaw install path, and the path used for `df`.
### Runtime changes
All runtime entry points should use the configured root:
- `openclaw-env`
- init script
- profile environment
- config TTY script
- controller actions for status, backup, restore, uninstall
The `/opt` OverlayFS workaround remains only when the configured install root is `/opt`.
## Verification
- Add lightweight tests for path normalization and derived path layout.
- Run the path tests locally.
- Run Lua syntax checks on modified Lua files.
- Run shell syntax checks on modified shell scripts.
## Risks
- Existing users can still repoint the setting without migration if they force a new install path after uninstalling. That is acceptable for this request but should be documented in UI copy.
- Backup/restore and uninstall touch many path call sites, so a missed hard-coded `/opt/openclaw` reference would cause partial regressions. Search-based verification is required before completion.