feat: Phase 4 module auto-installation + Phase 5 webstore backend
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
Phase 4 Contributions (Agent Golf): - Module auto-installation service (module_installer.rs) - NATS subject pattern for module installation commands - Companion agent contract documentation - API endpoint: POST /api/modules/install Phase 5 XO Direct Touch: - Webstore subscription API (PayPal recurring billing) * POST /api/webstore/subscription/create * GET /api/webstore/subscription * POST /api/webstore/subscription/cancel * POST /api/webstore/subscription/webhook - Store configuration API (CRUD for store settings) * GET /api/webstore/config * PUT /api/webstore/config - Store category/item management APIs (multi-tenant CRUD) * GET/POST/PUT/DELETE /api/webstore/categories * GET/POST/PUT/DELETE /api/webstore/items - Public store API (customer-facing, subdomain-scoped) * GET /api/public-store/:subdomain * GET /api/public-store/:subdomain/items * POST /api/public-store/:subdomain/purchase * POST /api/public-store/:subdomain/webhook - Transaction history API * GET /api/webstore/transactions - Delivery system (NATS command execution on purchase) - Migrations: payment_orders, webstore_subscriptions, store_config, store_items, store_transactions Security: - JWT auth + license_id scoping on admin endpoints - Subdomain → license_id mapping on public endpoints - Purchase limit enforcement - Command injection prevention via placeholder replacement Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
227
docs/COMPANION_AGENT_MODULE_INSTALL.md
Normal file
227
docs/COMPANION_AGENT_MODULE_INSTALL.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Companion Agent Module Installation Contract
|
||||
|
||||
**Status**: Phase 4 — Module Auto-Installation Pipeline
|
||||
**Date**: 2026-02-15
|
||||
**Author**: Sonnet (XO)
|
||||
|
||||
## Overview
|
||||
|
||||
The companion agent (bare metal server management) must implement module installation support to enable automated plugin deployment from the Corrosion admin panel.
|
||||
|
||||
This document defines the NATS subject contract for module installation commands and responses.
|
||||
|
||||
---
|
||||
|
||||
## NATS Subject Pattern
|
||||
|
||||
### Command Subject
|
||||
```
|
||||
corrosion.{license_id}.cmd.module.install
|
||||
```
|
||||
|
||||
### Response Subject
|
||||
```
|
||||
corrosion.{license_id}.module.install.result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Payload
|
||||
|
||||
The backend publishes a JSON payload to the command subject when a module installation is triggered:
|
||||
|
||||
```json
|
||||
{
|
||||
"module_id": "loot-manager",
|
||||
"download_url": "https://cdn.corrosionmgmt.com/modules/LootManager.cs",
|
||||
"filename": "LootManager.cs",
|
||||
"target_path": "oxide/plugins/"
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|----------------|--------|----------|----------------------------------------------------------------------|
|
||||
| `module_id` | string | Yes | Module slug identifier (e.g., "loot-manager") |
|
||||
| `download_url` | string | Yes | Signed URL to download the plugin file (.cs file) |
|
||||
| `filename` | string | Yes | Filename to save the plugin as (e.g., "LootManager.cs") |
|
||||
| `target_path` | string | Yes | Relative path from server root to install location (e.g., "oxide/plugins/") |
|
||||
|
||||
---
|
||||
|
||||
## Expected Agent Behavior
|
||||
|
||||
1. **Download Plugin File**
|
||||
- Make HTTP GET request to `download_url`
|
||||
- Validate response is successful (2xx status)
|
||||
- Read file contents into memory
|
||||
|
||||
2. **Install Plugin**
|
||||
- Navigate to `{server_root}/{target_path}`
|
||||
- Write file contents to `{target_path}/{filename}`
|
||||
- Ensure file permissions allow server process to read it
|
||||
|
||||
3. **Reload Plugins**
|
||||
- Execute console command: `oxide.reload *`
|
||||
- Wait for plugin to load
|
||||
- Verify plugin appears in loaded plugin list
|
||||
|
||||
4. **Publish Result**
|
||||
- Publish success/failure result to `corrosion.{license_id}.module.install.result`
|
||||
- Include error details if installation failed
|
||||
|
||||
---
|
||||
|
||||
## Response Payload
|
||||
|
||||
The agent must publish a JSON response to the result subject after installation completes (or fails):
|
||||
|
||||
### Success Response
|
||||
```json
|
||||
{
|
||||
"module_id": "loot-manager",
|
||||
"success": true,
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Failure Response
|
||||
```json
|
||||
{
|
||||
"module_id": "loot-manager",
|
||||
"success": false,
|
||||
"error": "Failed to download plugin file: connection timeout"
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------------|---------|----------|----------------------------------------------------------|
|
||||
| `module_id` | string | Yes | Echo of the module_id from the request |
|
||||
| `success` | boolean | Yes | `true` if installation succeeded, `false` if it failed |
|
||||
| `error` | string | No | Human-readable error message (required if success=false) |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
The agent should report failure for any of these conditions:
|
||||
|
||||
- **Download Failure**: HTTP request fails or returns non-2xx status
|
||||
- **File Write Failure**: Unable to write plugin file to disk (permissions, disk full, path doesn't exist)
|
||||
- **Plugin Load Failure**: Plugin file saved but failed to load when `oxide.reload *` was executed
|
||||
- **Timeout**: Operation takes longer than 60 seconds
|
||||
|
||||
The backend will timeout after 60 seconds if no response is received and mark the installation as "failed" in the database.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The backend uses NATS request/reply pattern with a 60-second timeout
|
||||
- Plugin files are small (<1MB typically), so download should be fast
|
||||
- The agent should verify `oxide/plugins/` directory exists before attempting write
|
||||
- If the plugin already exists, overwrite it (this is an update/reinstall scenario)
|
||||
- The agent does NOT need to check if the module is purchased — backend already verified this
|
||||
|
||||
---
|
||||
|
||||
## Example Implementation (Pseudocode)
|
||||
|
||||
```go
|
||||
func HandleModuleInstall(msg *nats.Msg) {
|
||||
var cmd ModuleInstallCommand
|
||||
json.Unmarshal(msg.Data, &cmd)
|
||||
|
||||
// Download plugin file
|
||||
resp, err := http.Get(cmd.DownloadURL)
|
||||
if err != nil {
|
||||
publishResult(msg.Reply, cmd.ModuleID, false, err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
publishResult(msg.Reply, cmd.ModuleID, false, "Download failed: " + resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
pluginData, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
// Write to disk
|
||||
targetFile := filepath.Join(serverRoot, cmd.TargetPath, cmd.Filename)
|
||||
err = ioutil.WriteFile(targetFile, pluginData, 0644)
|
||||
if err != nil {
|
||||
publishResult(msg.Reply, cmd.ModuleID, false, "File write failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Reload plugins
|
||||
err = sendConsoleCommand("oxide.reload *")
|
||||
if err != nil {
|
||||
publishResult(msg.Reply, cmd.ModuleID, false, "Plugin reload failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Success
|
||||
publishResult(msg.Reply, cmd.ModuleID, true, "")
|
||||
}
|
||||
|
||||
func publishResult(subject, moduleID string, success bool, errorMsg string) {
|
||||
result := ModuleInstallResult{
|
||||
ModuleID: moduleID,
|
||||
Success: success,
|
||||
Error: errorMsg,
|
||||
}
|
||||
data, _ := json.Marshal(result)
|
||||
natsClient.Publish(subject, data)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Test Procedure
|
||||
|
||||
1. Purchase a module from the Corrosion dashboard (e.g., Loot Manager)
|
||||
2. Click "Install" button on the module card
|
||||
3. Monitor NATS subject: `corrosion.{your_license_id}.cmd.module.install`
|
||||
4. Verify agent receives command payload
|
||||
5. Verify agent downloads plugin file
|
||||
6. Verify agent writes file to `oxide/plugins/LootManager.cs`
|
||||
7. Verify agent executes `oxide.reload *`
|
||||
8. Verify agent publishes success result
|
||||
9. Refresh dashboard — module status should show "installed"
|
||||
|
||||
### Failure Scenarios to Test
|
||||
|
||||
- Invalid download URL (404) → agent reports "Download failed"
|
||||
- Disk full → agent reports "File write failed"
|
||||
- Plugin syntax error → agent reports "Plugin load failed"
|
||||
- No response from agent → backend times out after 60s, marks "failed"
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- **Backend Service**: `backend/src/services/module_installer.rs`
|
||||
- **API Endpoint**: `backend/src/api/modules.rs` (POST `/api/modules/install`)
|
||||
- **Database Schema**: `backend/migrations/009_module_licensing.sql`
|
||||
- **Companion Agent Repo**: TBD (Go implementation)
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
- [x] Backend service implemented (`ModuleInstaller`)
|
||||
- [x] API endpoint wired (`POST /api/modules/install`)
|
||||
- [x] NATS contract documented
|
||||
- [ ] Companion agent implementation (Go)
|
||||
- [ ] End-to-end testing
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Implement this contract in the companion agent codebase (Go).
|
||||
Reference in New Issue
Block a user