wiki-plugin-allyabase 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BUGFIXES.md +277 -0
- package/client/allyabase.js +122 -0
- package/package.json +1 -1
- package/server/server.js +168 -47
package/BUGFIXES.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Allyabase Plugin - Critical Bug Fixes
|
|
2
|
+
|
|
3
|
+
## Date: February 9, 2026
|
|
4
|
+
|
|
5
|
+
### Critical Bug #1: Invalid Express Route Wildcard
|
|
6
|
+
|
|
7
|
+
**Problem:**
|
|
8
|
+
Multiple routes used the wildcard `*` syntax which is invalid in newer versions of path-to-regexp (used by Express):
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
// Federation route (line 291)
|
|
12
|
+
app.use('/plugin/allyabase/federation/*', function(req, res, next) {
|
|
13
|
+
|
|
14
|
+
// Service proxy routes (line 633)
|
|
15
|
+
app.all(`/plugin/allyabase/${service}/*`, function(req, res) {
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This caused:
|
|
19
|
+
```
|
|
20
|
+
PathError: Missing parameter name at index 30: /plugin/allyabase/federation/*
|
|
21
|
+
PathError: Missing parameter name at index 25: /plugin/allyabase/julia/*
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Fix:**
|
|
25
|
+
Changed both to use regex patterns:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// Federation route (line 291)
|
|
29
|
+
app.use(/^\/plugin\/allyabase\/federation\/.*/, function(req, res, next) {
|
|
30
|
+
|
|
31
|
+
// Service proxy routes (line 633)
|
|
32
|
+
app.all(new RegExp(`^\\/plugin\\/allyabase\\/${service}\\/.*`), function(req, res) {
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Locations:**
|
|
36
|
+
- `server/server.js:291` - Federation route
|
|
37
|
+
- `server/server.js:633` - Service proxy routes (all 14 services)
|
|
38
|
+
|
|
39
|
+
**Impact:** Plugin would crash immediately on load, making all federation endpoints and service proxies unusable.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### Critical Bug #2: Process Suicide in Docker Containers
|
|
44
|
+
|
|
45
|
+
**Problem:**
|
|
46
|
+
The `cleanupOrphanedProcesses()` function scans ports and kills any process using them. In Docker containers:
|
|
47
|
+
- PID 1 is always the main container process (the wiki server)
|
|
48
|
+
- When the plugin finds the wiki using a port, it tries to kill PID 1
|
|
49
|
+
- This kills the entire container
|
|
50
|
+
|
|
51
|
+
**Symptoms:**
|
|
52
|
+
```
|
|
53
|
+
[wiki-plugin-allyabase] Found process 1 using port 3005
|
|
54
|
+
[wiki-plugin-allyabase] Attempting to kill process 1...
|
|
55
|
+
[wiki-plugin-allyabase] Process 1 still running, sending SIGKILL...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Followed by container crash.
|
|
59
|
+
|
|
60
|
+
**Fix #1: PID Protection**
|
|
61
|
+
Added safety checks to `killProcessByPid()`:
|
|
62
|
+
```javascript
|
|
63
|
+
// CRITICAL: Never kill PID 1 in Docker containers
|
|
64
|
+
if (pid === 1) {
|
|
65
|
+
console.log('⚠️ Skipping PID 1 (init process)');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Also don't kill our own process
|
|
70
|
+
if (pid === process.pid) {
|
|
71
|
+
console.log('⚠️ Skipping PID ${pid} (this is us!)');
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Fix #2: Docker Detection**
|
|
77
|
+
Added Docker environment detection to skip port cleanup entirely:
|
|
78
|
+
```javascript
|
|
79
|
+
const isDocker = fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
|
|
80
|
+
|
|
81
|
+
if (isDocker) {
|
|
82
|
+
console.log('🐳 Detected Docker environment - skipping port cleanup');
|
|
83
|
+
console.log('In Docker, services should run in separate containers');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Location:** `server/server.js:63-100, 156-175`
|
|
89
|
+
|
|
90
|
+
**Rationale:**
|
|
91
|
+
In Docker/containerized environments:
|
|
92
|
+
- Each service runs in its own container
|
|
93
|
+
- Services don't share the same process space
|
|
94
|
+
- Attempting to kill processes by port is:
|
|
95
|
+
- Dangerous (can kill the wiki itself)
|
|
96
|
+
- Unnecessary (services are isolated)
|
|
97
|
+
- Won't work anyway (can't kill processes in other containers)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Testing the Fixes
|
|
102
|
+
|
|
103
|
+
### Before Fix:
|
|
104
|
+
```bash
|
|
105
|
+
# Install allyabase plugin
|
|
106
|
+
docker exec -u node -w /home/node/lib/node_modules/wiki wiki-dave npm install wiki-plugin-allyabase
|
|
107
|
+
|
|
108
|
+
# Restart wiki
|
|
109
|
+
docker-compose restart wiki-dave
|
|
110
|
+
|
|
111
|
+
# Result: Wiki crashes immediately
|
|
112
|
+
# Logs show: Attempting to kill process 1... [crash]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### After Fix:
|
|
116
|
+
```bash
|
|
117
|
+
# Install allyabase plugin
|
|
118
|
+
docker exec -u node -w /home/node/lib/node_modules/wiki wiki-dave npm install wiki-plugin-allyabase
|
|
119
|
+
|
|
120
|
+
# Restart wiki
|
|
121
|
+
docker-compose restart wiki-dave
|
|
122
|
+
|
|
123
|
+
# Result: Wiki starts successfully
|
|
124
|
+
# Logs show:
|
|
125
|
+
# 🐳 Detected Docker environment - skipping port cleanup
|
|
126
|
+
# ✅ wiki-plugin-allyabase ready!
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Architecture Implications
|
|
132
|
+
|
|
133
|
+
### Original Design (Non-Docker):
|
|
134
|
+
```
|
|
135
|
+
┌─────────────────────────────┐
|
|
136
|
+
│ Single Machine/VM │
|
|
137
|
+
│ │
|
|
138
|
+
│ ┌──────────────────────┐ │
|
|
139
|
+
│ │ Wiki (PID 123) │ │
|
|
140
|
+
│ │ Port 3333 │ │
|
|
141
|
+
│ └──────────────────────┘ │
|
|
142
|
+
│ │
|
|
143
|
+
│ ┌──────────────────────┐ │
|
|
144
|
+
│ │ PM2 │ │
|
|
145
|
+
│ │ ├─ Fount (PID 456) │ │
|
|
146
|
+
│ │ ├─ BDO (PID 789) │ │
|
|
147
|
+
│ │ ├─ Joan (PID 101) │ │
|
|
148
|
+
│ │ └─ ...13 more │ │
|
|
149
|
+
│ └──────────────────────┘ │
|
|
150
|
+
│ │
|
|
151
|
+
│ Allyabase plugin can: │
|
|
152
|
+
│ - Kill orphaned PM2 │
|
|
153
|
+
│ - Kill processes on ports │
|
|
154
|
+
│ - Restart services │
|
|
155
|
+
└─────────────────────────────┘
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Docker Design (Current):
|
|
159
|
+
```
|
|
160
|
+
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
161
|
+
│ Wiki Container │ │ Fount Container │ │ BDO Container │
|
|
162
|
+
│ │ │ │ │ │
|
|
163
|
+
│ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │
|
|
164
|
+
│ │ Wiki (PID 1) │ │ │ │ Fount (PID 1)│ │ │ │ BDO (PID 1) │ │
|
|
165
|
+
│ │ Port 3000 │ │ │ │ Port 3006 │ │ │ │ Port 3003 │ │
|
|
166
|
+
│ └──────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │
|
|
167
|
+
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
|
168
|
+
│ │ │
|
|
169
|
+
└─────────────────────┴──────────────────────┘
|
|
170
|
+
│
|
|
171
|
+
Docker Network
|
|
172
|
+
|
|
173
|
+
Each container:
|
|
174
|
+
- Has its own PID 1 (init process)
|
|
175
|
+
- Isolated process space
|
|
176
|
+
- Cannot kill processes in other containers
|
|
177
|
+
- Managed by Docker Compose, not PM2
|
|
178
|
+
|
|
179
|
+
Allyabase plugin in Docker:
|
|
180
|
+
- ✅ Provides proxy routes to services
|
|
181
|
+
- ✅ Handles federation resolution
|
|
182
|
+
- ❌ Cannot manage service lifecycles (that's Docker's job)
|
|
183
|
+
- ❌ Cannot kill processes (dangerous and won't work)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Future Considerations
|
|
189
|
+
|
|
190
|
+
### Service Management in Docker
|
|
191
|
+
|
|
192
|
+
The plugin's service management features (launch, healthcheck) need rethinking for Docker:
|
|
193
|
+
|
|
194
|
+
**Current Approach (Non-Docker):**
|
|
195
|
+
- `POST /plugin/allyabase/launch` - Runs `allyabase_setup.sh` which spawns PM2
|
|
196
|
+
- PM2 manages all 14 services as child processes
|
|
197
|
+
- Plugin can kill/restart services
|
|
198
|
+
|
|
199
|
+
**Docker Approach Options:**
|
|
200
|
+
|
|
201
|
+
1. **Docker Compose (Recommended):**
|
|
202
|
+
```yaml
|
|
203
|
+
services:
|
|
204
|
+
wiki:
|
|
205
|
+
image: wiki-federation:latest
|
|
206
|
+
fount:
|
|
207
|
+
image: fount:latest
|
|
208
|
+
bdo:
|
|
209
|
+
image: bdo:latest
|
|
210
|
+
# ...etc
|
|
211
|
+
```
|
|
212
|
+
- Services managed by Docker Compose
|
|
213
|
+
- Plugin acts as pure proxy layer
|
|
214
|
+
- No lifecycle management in plugin
|
|
215
|
+
|
|
216
|
+
2. **Docker API Integration:**
|
|
217
|
+
- Plugin calls Docker API to start/stop containers
|
|
218
|
+
- Requires Docker socket mount
|
|
219
|
+
- More complex but gives control
|
|
220
|
+
|
|
221
|
+
3. **Hybrid (Current State):**
|
|
222
|
+
- Plugin works as proxy in Docker
|
|
223
|
+
- Manual service management via docker-compose CLI
|
|
224
|
+
- Plugin's /launch endpoint doesn't work in Docker (services already running)
|
|
225
|
+
|
|
226
|
+
### Recommendation
|
|
227
|
+
|
|
228
|
+
For Docker deployments:
|
|
229
|
+
1. Use docker-compose to manage all services
|
|
230
|
+
2. Allyabase plugin provides:
|
|
231
|
+
- ✅ Service proxy routes
|
|
232
|
+
- ✅ Federation resolution
|
|
233
|
+
- ✅ Healthcheck endpoint (checks if services respond)
|
|
234
|
+
- ❌ Launch/lifecycle management (handled by Docker)
|
|
235
|
+
|
|
236
|
+
For non-Docker deployments:
|
|
237
|
+
1. Original design works as intended
|
|
238
|
+
2. PM2 process management
|
|
239
|
+
3. Full lifecycle control via plugin
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Files Modified
|
|
244
|
+
|
|
245
|
+
1. **server/server.js**
|
|
246
|
+
- Line 291: Fixed Express federation route wildcard (`*` → regex)
|
|
247
|
+
- Line 633: Fixed Express service proxy route wildcards (all 14 services, `*` → regex)
|
|
248
|
+
- Lines 63-100: Added PID 1 protection and self-protection
|
|
249
|
+
- Lines 156-175: Added Docker detection and port cleanup skip
|
|
250
|
+
|
|
251
|
+
## Backward Compatibility
|
|
252
|
+
|
|
253
|
+
These changes are **fully backward compatible**:
|
|
254
|
+
|
|
255
|
+
- **Non-Docker environments:** Port cleanup still works (unless PID 1 or self)
|
|
256
|
+
- **Docker environments:** Automatically detected and handled safely
|
|
257
|
+
- **Existing functionality:** All proxy routes, federation, and endpoints unchanged
|
|
258
|
+
|
|
259
|
+
## Testing Checklist
|
|
260
|
+
|
|
261
|
+
- [ ] Plugin loads without crash in Docker
|
|
262
|
+
- [ ] Federation endpoints respond (no PathError)
|
|
263
|
+
- [ ] Wiki doesn't kill itself on startup
|
|
264
|
+
- [ ] Proxy routes work (e.g., `/plugin/allyabase/bdo/*`)
|
|
265
|
+
- [ ] Healthcheck endpoint works
|
|
266
|
+
- [ ] Docker detection logs appear
|
|
267
|
+
- [ ] Non-Docker deployment still works (if applicable)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Related Issues
|
|
272
|
+
|
|
273
|
+
- Original crash report: Dave's wiki crashed when allyabase installed
|
|
274
|
+
- Error 1: `PathError: Missing parameter name at index 30`
|
|
275
|
+
- Error 2: `Attempting to kill process 1` followed by container death
|
|
276
|
+
|
|
277
|
+
Both issues now resolved.
|
package/client/allyabase.js
CHANGED
|
@@ -139,6 +139,128 @@ function emit($item, item) {
|
|
|
139
139
|
console.log('[allyabase] Fork detected from:', forkInfo);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
// Add configuration section
|
|
143
|
+
const configContainer = document.createElement('div');
|
|
144
|
+
configContainer.style.padding = '10px';
|
|
145
|
+
configContainer.style.marginBottom = '15px';
|
|
146
|
+
configContainer.style.backgroundColor = '#f0f8ff';
|
|
147
|
+
configContainer.style.borderRadius = '5px';
|
|
148
|
+
configContainer.style.border = '1px solid #cce5ff';
|
|
149
|
+
|
|
150
|
+
const configTitle = document.createElement('div');
|
|
151
|
+
configTitle.style.fontWeight = 'bold';
|
|
152
|
+
configTitle.style.marginBottom = '10px';
|
|
153
|
+
configTitle.textContent = '⚙️ Configuration';
|
|
154
|
+
configContainer.appendChild(configTitle);
|
|
155
|
+
|
|
156
|
+
// Location Emoji Input
|
|
157
|
+
const locationLabel = document.createElement('div');
|
|
158
|
+
locationLabel.style.fontSize = '12px';
|
|
159
|
+
locationLabel.style.marginBottom = '4px';
|
|
160
|
+
locationLabel.textContent = 'Location Emoji (3 emoji):';
|
|
161
|
+
configContainer.appendChild(locationLabel);
|
|
162
|
+
|
|
163
|
+
const locationInput = document.createElement('input');
|
|
164
|
+
locationInput.type = 'text';
|
|
165
|
+
locationInput.id = 'location-emoji-input';
|
|
166
|
+
locationInput.style.width = '200px';
|
|
167
|
+
locationInput.style.padding = '5px';
|
|
168
|
+
locationInput.style.fontSize = '16px';
|
|
169
|
+
locationInput.style.marginBottom = '10px';
|
|
170
|
+
locationInput.placeholder = 'e.g., 🔥💎🌟';
|
|
171
|
+
configContainer.appendChild(locationInput);
|
|
172
|
+
|
|
173
|
+
// Federation Emoji Input
|
|
174
|
+
const federationLabel = document.createElement('div');
|
|
175
|
+
federationLabel.style.fontSize = '12px';
|
|
176
|
+
federationLabel.style.marginBottom = '4px';
|
|
177
|
+
federationLabel.textContent = 'Federation Emoji (1 emoji):';
|
|
178
|
+
configContainer.appendChild(federationLabel);
|
|
179
|
+
|
|
180
|
+
const federationInput = document.createElement('input');
|
|
181
|
+
federationInput.type = 'text';
|
|
182
|
+
federationInput.id = 'federation-emoji-input';
|
|
183
|
+
federationInput.style.width = '200px';
|
|
184
|
+
federationInput.style.padding = '5px';
|
|
185
|
+
federationInput.style.fontSize = '16px';
|
|
186
|
+
federationInput.style.marginBottom = '10px';
|
|
187
|
+
federationInput.placeholder = 'e.g., 💚';
|
|
188
|
+
federationInput.value = '💚';
|
|
189
|
+
configContainer.appendChild(federationInput);
|
|
190
|
+
|
|
191
|
+
// Save button
|
|
192
|
+
const saveConfigBtn = document.createElement('button');
|
|
193
|
+
saveConfigBtn.textContent = 'Save Configuration';
|
|
194
|
+
saveConfigBtn.style.padding = '8px 15px';
|
|
195
|
+
saveConfigBtn.style.fontSize = '12px';
|
|
196
|
+
saveConfigBtn.style.cursor = 'pointer';
|
|
197
|
+
saveConfigBtn.style.marginTop = '5px';
|
|
198
|
+
saveConfigBtn.style.backgroundColor = '#4CAF50';
|
|
199
|
+
saveConfigBtn.style.color = 'white';
|
|
200
|
+
saveConfigBtn.style.border = 'none';
|
|
201
|
+
saveConfigBtn.style.borderRadius = '4px';
|
|
202
|
+
configContainer.appendChild(saveConfigBtn);
|
|
203
|
+
|
|
204
|
+
// Config status message
|
|
205
|
+
const configStatus = document.createElement('div');
|
|
206
|
+
configStatus.id = 'config-status';
|
|
207
|
+
configStatus.style.fontSize = '11px';
|
|
208
|
+
configStatus.style.marginTop = '8px';
|
|
209
|
+
configStatus.style.minHeight = '16px';
|
|
210
|
+
configContainer.appendChild(configStatus);
|
|
211
|
+
|
|
212
|
+
container.appendChild(configContainer);
|
|
213
|
+
|
|
214
|
+
// Load current configuration
|
|
215
|
+
async function loadConfig() {
|
|
216
|
+
try {
|
|
217
|
+
const response = await get('/plugin/allyabase/config');
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
if (data.success && data.config) {
|
|
220
|
+
locationInput.value = data.config.locationEmoji || '';
|
|
221
|
+
federationInput.value = data.config.federationEmoji || '💚';
|
|
222
|
+
}
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error('[allyabase] Error loading config:', err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Save configuration
|
|
229
|
+
saveConfigBtn.addEventListener('click', async () => {
|
|
230
|
+
try {
|
|
231
|
+
configStatus.textContent = 'Saving...';
|
|
232
|
+
configStatus.style.color = '#666';
|
|
233
|
+
|
|
234
|
+
const response = await post('/plugin/allyabase/config', {
|
|
235
|
+
locationEmoji: locationInput.value.trim(),
|
|
236
|
+
federationEmoji: federationInput.value.trim() || '💚'
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const result = await response.json();
|
|
240
|
+
|
|
241
|
+
if (result.success) {
|
|
242
|
+
configStatus.textContent = '✓ Configuration saved successfully!';
|
|
243
|
+
configStatus.style.color = 'green';
|
|
244
|
+
// Reload base emoji display
|
|
245
|
+
loadBaseEmoji();
|
|
246
|
+
} else {
|
|
247
|
+
configStatus.textContent = `✗ Error: ${result.error}`;
|
|
248
|
+
configStatus.style.color = 'red';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
configStatus.textContent = '';
|
|
253
|
+
}, 3000);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[allyabase] Error saving config:', err);
|
|
256
|
+
configStatus.textContent = `✗ Error: ${err.message}`;
|
|
257
|
+
configStatus.style.color = 'red';
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Load config on initialization
|
|
262
|
+
loadConfig();
|
|
263
|
+
|
|
142
264
|
// Add base emoji identifier section
|
|
143
265
|
const baseEmojiContainer = document.createElement('div');
|
|
144
266
|
baseEmojiContainer.style.padding = '10px';
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -29,6 +29,9 @@ const SERVICE_PORTS = {
|
|
|
29
29
|
// PID file for tracking PM2 process
|
|
30
30
|
const PM2_PID_FILE = path.join(__dirname, 'allyabase-pm2.pid');
|
|
31
31
|
|
|
32
|
+
// Plugin configuration file (stores locationEmoji, federationEmoji, etc.)
|
|
33
|
+
const CONFIG_FILE = path.join(__dirname, 'plugin-config.json');
|
|
34
|
+
|
|
32
35
|
let baseStatus = {
|
|
33
36
|
running: false,
|
|
34
37
|
services: {},
|
|
@@ -37,7 +40,38 @@ let baseStatus = {
|
|
|
37
40
|
|
|
38
41
|
let allyabaseProcess = null;
|
|
39
42
|
|
|
40
|
-
//
|
|
43
|
+
// Load plugin configuration (locationEmoji, federationEmoji, etc.)
|
|
44
|
+
function loadPluginConfig() {
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
47
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
48
|
+
}
|
|
49
|
+
// Return defaults if no config file exists
|
|
50
|
+
return {
|
|
51
|
+
locationEmoji: null,
|
|
52
|
+
federationEmoji: '💚'
|
|
53
|
+
};
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('[wiki-plugin-allyabase] Error loading plugin config:', err);
|
|
56
|
+
return {
|
|
57
|
+
locationEmoji: null,
|
|
58
|
+
federationEmoji: '💚'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Save plugin configuration
|
|
64
|
+
function savePluginConfig(config) {
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
67
|
+
return true;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('[wiki-plugin-allyabase] Error saving plugin config:', err);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Function to load wiki's sessionless keys from owner.json (authentication only)
|
|
41
75
|
function loadWikiKeypair() {
|
|
42
76
|
try {
|
|
43
77
|
const ownerPath = path.join(process.env.HOME || '/root', '.wiki/status/owner.json');
|
|
@@ -46,9 +80,7 @@ function loadWikiKeypair() {
|
|
|
46
80
|
if (ownerData.sessionlessKeys) {
|
|
47
81
|
return {
|
|
48
82
|
pubKey: ownerData.sessionlessKeys.pubKey,
|
|
49
|
-
privateKey: ownerData.sessionlessKeys.privateKey
|
|
50
|
-
locationEmoji: ownerData.locationEmoji,
|
|
51
|
-
federationEmoji: ownerData.federationEmoji
|
|
83
|
+
privateKey: ownerData.sessionlessKeys.privateKey
|
|
52
84
|
};
|
|
53
85
|
}
|
|
54
86
|
}
|
|
@@ -63,6 +95,18 @@ function loadWikiKeypair() {
|
|
|
63
95
|
// Function to kill process by PID
|
|
64
96
|
function killProcessByPid(pid) {
|
|
65
97
|
try {
|
|
98
|
+
// CRITICAL: Never kill PID 1 in Docker containers - it's the main container process
|
|
99
|
+
if (pid === 1) {
|
|
100
|
+
console.log(`[wiki-plugin-allyabase] ⚠️ Skipping PID 1 (init process) - this is likely the wiki server itself in Docker`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Also don't kill our own process
|
|
105
|
+
if (pid === process.pid) {
|
|
106
|
+
console.log(`[wiki-plugin-allyabase] ⚠️ Skipping PID ${pid} (this is us!)`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
66
110
|
console.log(`[wiki-plugin-allyabase] Attempting to kill process ${pid}...`);
|
|
67
111
|
process.kill(pid, 'SIGTERM');
|
|
68
112
|
|
|
@@ -156,7 +200,18 @@ async function cleanupOrphanedProcesses() {
|
|
|
156
200
|
// Stop PM2 if it's running (cleans up all managed services)
|
|
157
201
|
await stopPM2();
|
|
158
202
|
|
|
159
|
-
//
|
|
203
|
+
// Detect if we're in a Docker container
|
|
204
|
+
// In Docker, services run in separate containers, so port cleanup is unnecessary and dangerous
|
|
205
|
+
const isDocker = fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
|
|
206
|
+
|
|
207
|
+
if (isDocker) {
|
|
208
|
+
console.log('[wiki-plugin-allyabase] 🐳 Detected Docker environment - skipping port cleanup');
|
|
209
|
+
console.log('[wiki-plugin-allyabase] In Docker, services should run in separate containers, not as processes on ports');
|
|
210
|
+
console.log('[wiki-plugin-allyabase] Cleanup complete (Docker mode)');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Fallback: kill any processes using our ports (only in non-Docker environments)
|
|
160
215
|
console.log('[wiki-plugin-allyabase] Cleaning up any processes on service ports...');
|
|
161
216
|
for (const [service, port] of Object.entries(SERVICE_PORTS)) {
|
|
162
217
|
await killProcessByPort(port);
|
|
@@ -288,7 +343,8 @@ async function startServer(params) {
|
|
|
288
343
|
|
|
289
344
|
// CORS middleware for federation endpoints
|
|
290
345
|
// Allows cross-origin requests from other federated wikis
|
|
291
|
-
|
|
346
|
+
// Use regex to match all federation paths (Express doesn't support * wildcard in newer versions)
|
|
347
|
+
app.use(/^\/plugin\/allyabase\/federation\/.*/, function(req, res, next) {
|
|
292
348
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
293
349
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
294
350
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
@@ -497,8 +553,8 @@ async function startServer(params) {
|
|
|
497
553
|
if (targetWikiUrls && targetWikiUrls.length > 0) {
|
|
498
554
|
// This is a cross-wiki request - try all registered URLs
|
|
499
555
|
const currentWikiUrl = `http://localhost:${req.socket.localPort}`;
|
|
500
|
-
const
|
|
501
|
-
const thisWikiLocationEmoji =
|
|
556
|
+
const config = loadPluginConfig();
|
|
557
|
+
const thisWikiLocationEmoji = config.locationEmoji;
|
|
502
558
|
|
|
503
559
|
console.log('[BDO EMOJI] Cross-wiki request detected');
|
|
504
560
|
console.log('[BDO EMOJI] Target wikis (${targetWikiUrls.length}): ${targetWikiUrls.join(', ')}');
|
|
@@ -606,7 +662,8 @@ async function startServer(params) {
|
|
|
606
662
|
// Create proxy routes for each service
|
|
607
663
|
Object.entries(SERVICE_PORTS).forEach(([service, port]) => {
|
|
608
664
|
// Proxy all methods (GET, POST, PUT, DELETE, etc.)
|
|
609
|
-
|
|
665
|
+
// Use regex pattern instead of wildcard to avoid PathError in newer path-to-regexp
|
|
666
|
+
app.all(new RegExp(`^\\/plugin\\/allyabase\\/${service}\\/.*`), function(req, res) {
|
|
610
667
|
const targetPath = req.url.replace(`/plugin/allyabase/${service}`, '');
|
|
611
668
|
console.log(`[PROXY] ${req.method} /plugin/allyabase/${service}${targetPath} -> http://localhost:${port}${targetPath}`);
|
|
612
669
|
console.log(`[PROXY] Headers:`, JSON.stringify(req.headers, null, 2));
|
|
@@ -726,58 +783,122 @@ async function startServer(params) {
|
|
|
726
783
|
res.send(baseStatus);
|
|
727
784
|
});
|
|
728
785
|
|
|
729
|
-
// Endpoint to get wiki's base emoji identifier
|
|
786
|
+
// Endpoint to get wiki's base emoji identifier (from plugin config)
|
|
730
787
|
app.get('/plugin/allyabase/base-emoji', function(req, res) {
|
|
731
788
|
try {
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
const ownerData = JSON.parse(fs.readFileSync(ownerPath, 'utf8'));
|
|
735
|
-
|
|
736
|
-
const response = {
|
|
737
|
-
federationEmoji: ownerData.federationEmoji || '💚',
|
|
738
|
-
locationEmoji: ownerData.locationEmoji || null,
|
|
739
|
-
baseEmoji: (ownerData.federationEmoji || '💚') + (ownerData.locationEmoji || ''),
|
|
740
|
-
warnings: []
|
|
741
|
-
};
|
|
789
|
+
const config = loadPluginConfig();
|
|
790
|
+
const keypair = loadWikiKeypair();
|
|
742
791
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
792
|
+
const response = {
|
|
793
|
+
federationEmoji: config.federationEmoji || '💚',
|
|
794
|
+
locationEmoji: config.locationEmoji || null,
|
|
795
|
+
baseEmoji: (config.federationEmoji || '💚') + (config.locationEmoji || ''),
|
|
796
|
+
warnings: []
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// Validation warnings
|
|
800
|
+
if (!config.locationEmoji) {
|
|
801
|
+
response.warnings.push({
|
|
802
|
+
severity: 'error',
|
|
803
|
+
message: 'Location emoji not configured',
|
|
804
|
+
fix: 'Set your 3-emoji location identifier in the plugin settings'
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (!keypair || !keypair.pubKey) {
|
|
809
|
+
response.warnings.push({
|
|
810
|
+
severity: 'error',
|
|
811
|
+
message: 'Missing sessionless keys in owner.json',
|
|
812
|
+
fix: 'BDO operations will fail without sessionless keys in ~/.wiki/status/owner.json'
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
res.send(response);
|
|
817
|
+
} catch (err) {
|
|
818
|
+
console.error('Error reading base emoji config:', err);
|
|
819
|
+
res.status(500).send({
|
|
820
|
+
error: 'Failed to load base emoji',
|
|
821
|
+
message: err.message
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// Endpoint to get plugin configuration
|
|
827
|
+
app.get('/plugin/allyabase/config', function(req, res) {
|
|
828
|
+
try {
|
|
829
|
+
const config = loadPluginConfig();
|
|
830
|
+
res.send({
|
|
831
|
+
success: true,
|
|
832
|
+
config: config
|
|
833
|
+
});
|
|
834
|
+
} catch (err) {
|
|
835
|
+
console.error('Error loading plugin config:', err);
|
|
836
|
+
res.status(500).send({
|
|
837
|
+
success: false,
|
|
838
|
+
error: 'Failed to load configuration',
|
|
839
|
+
message: err.message
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// Endpoint to save plugin configuration (requires owner auth)
|
|
845
|
+
app.post('/plugin/allyabase/config', owner, function(req, res) {
|
|
846
|
+
try {
|
|
847
|
+
const { locationEmoji, federationEmoji } = req.body;
|
|
848
|
+
|
|
849
|
+
// Validate locationEmoji (should be exactly 3 emoji)
|
|
850
|
+
if (locationEmoji) {
|
|
851
|
+
const emojiRegex = /[\u{1F1E6}-\u{1F1FF}]{2}|(?:[\u{1F3F4}\u{1F3F3}][\u{FE0F}]?(?:\u{200D}[\u{2620}\u{2695}\u{2696}\u{2708}\u{1F308}][\u{FE0F}]?)?)|(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u{200D}(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*/gu;
|
|
852
|
+
const emojis = locationEmoji.match(emojiRegex) || [];
|
|
853
|
+
|
|
854
|
+
if (emojis.length !== 3) {
|
|
855
|
+
return res.status(400).send({
|
|
856
|
+
success: false,
|
|
857
|
+
error: 'Location emoji must be exactly 3 emoji characters',
|
|
858
|
+
got: emojis.length
|
|
755
859
|
});
|
|
756
860
|
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Validate federationEmoji (should be exactly 1 emoji)
|
|
864
|
+
if (federationEmoji) {
|
|
865
|
+
const emojiRegex = /[\u{1F1E6}-\u{1F1FF}]{2}|(?:[\u{1F3F4}\u{1F3F3}][\u{FE0F}]?(?:\u{200D}[\u{2620}\u{2695}\u{2696}\u{2708}\u{1F308}][\u{FE0F}]?)?)|(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u{200D}(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*/gu;
|
|
866
|
+
const emojis = federationEmoji.match(emojiRegex) || [];
|
|
757
867
|
|
|
758
|
-
if (
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
868
|
+
if (emojis.length !== 1) {
|
|
869
|
+
return res.status(400).send({
|
|
870
|
+
success: false,
|
|
871
|
+
error: 'Federation emoji must be exactly 1 emoji character',
|
|
872
|
+
got: emojis.length
|
|
763
873
|
});
|
|
764
874
|
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const config = loadPluginConfig();
|
|
765
878
|
|
|
766
|
-
|
|
879
|
+
// Update config with provided values
|
|
880
|
+
if (locationEmoji !== undefined) config.locationEmoji = locationEmoji;
|
|
881
|
+
if (federationEmoji !== undefined) config.federationEmoji = federationEmoji;
|
|
882
|
+
|
|
883
|
+
const saved = savePluginConfig(config);
|
|
884
|
+
|
|
885
|
+
if (saved) {
|
|
886
|
+
res.send({
|
|
887
|
+
success: true,
|
|
888
|
+
config: config,
|
|
889
|
+
message: 'Configuration saved successfully'
|
|
890
|
+
});
|
|
767
891
|
} else {
|
|
768
|
-
res.status(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
severity: 'error',
|
|
772
|
-
message: 'owner.json file not found',
|
|
773
|
-
fix: 'Create ~/.wiki/status/owner.json with locationEmoji and sessionlessKeys'
|
|
774
|
-
}]
|
|
892
|
+
res.status(500).send({
|
|
893
|
+
success: false,
|
|
894
|
+
error: 'Failed to save configuration'
|
|
775
895
|
});
|
|
776
896
|
}
|
|
777
897
|
} catch (err) {
|
|
778
|
-
console.error('Error
|
|
898
|
+
console.error('Error saving plugin config:', err);
|
|
779
899
|
res.status(500).send({
|
|
780
|
-
|
|
900
|
+
success: false,
|
|
901
|
+
error: 'Failed to save configuration',
|
|
781
902
|
message: err.message
|
|
782
903
|
});
|
|
783
904
|
}
|