lobstakit-cloud 1.0.0
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/bin/lobstakit.js +2 -0
- package/lib/config.js +176 -0
- package/lib/gateway.js +104 -0
- package/lib/proxy.js +33 -0
- package/package.json +16 -0
- package/public/css/styles.css +579 -0
- package/public/index.html +507 -0
- package/public/js/app.js +198 -0
- package/public/js/login.js +93 -0
- package/public/js/manage.js +1274 -0
- package/public/js/setup.js +755 -0
- package/public/login.html +73 -0
- package/public/manage.html +734 -0
- package/server.js +1357 -0
package/bin/lobstakit.js
ADDED
package/lib/config.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LobstaKit Cloud — Config Manager
|
|
3
|
+
*
|
|
4
|
+
* Reads/writes the OpenClaw gateway config at /root/.openclaw/openclaw.json.
|
|
5
|
+
* Preserves existing gateway auth token (written by cloud-init).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
const CONFIG_PATH = '/root/.openclaw/openclaw.json';
|
|
13
|
+
const CONFIG_DIR = path.dirname(CONFIG_PATH);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read the current config file. Returns null if not found.
|
|
17
|
+
*/
|
|
18
|
+
function readConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err.code === 'ENOENT') return null;
|
|
24
|
+
console.error('[config] Failed to read config:', err.message);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Write config to disk. Merges with existing config to preserve
|
|
31
|
+
* gateway auth token and other cloud-init values.
|
|
32
|
+
*/
|
|
33
|
+
function writeConfig({ apiKey, model, channel, telegramBotToken, telegramUserId, discordBotToken, discordServerId, privateMemory }) {
|
|
34
|
+
// Ensure config directory exists
|
|
35
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
36
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Read existing config to preserve gateway auth token
|
|
40
|
+
const existing = readConfig() || {};
|
|
41
|
+
const existingToken = existing?.gateway?.auth?.token || '';
|
|
42
|
+
|
|
43
|
+
// Build channels config based on selected channel
|
|
44
|
+
const channels = {};
|
|
45
|
+
const selectedChannel = channel || 'web';
|
|
46
|
+
|
|
47
|
+
if (selectedChannel === 'telegram' && telegramBotToken && telegramUserId) {
|
|
48
|
+
channels.telegram = {
|
|
49
|
+
enabled: true,
|
|
50
|
+
botToken: telegramBotToken,
|
|
51
|
+
allowFrom: [telegramUserId],
|
|
52
|
+
dmPolicy: 'allowlist'
|
|
53
|
+
};
|
|
54
|
+
} else if (selectedChannel === 'discord' && discordBotToken && discordServerId) {
|
|
55
|
+
channels.discord = {
|
|
56
|
+
enabled: true,
|
|
57
|
+
botToken: discordBotToken,
|
|
58
|
+
allowedGuilds: [discordServerId]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Web channel: no channel plugins needed (empty channels object)
|
|
62
|
+
|
|
63
|
+
// Build agent defaults
|
|
64
|
+
const agentDefaults = {
|
|
65
|
+
model: {
|
|
66
|
+
primary: model
|
|
67
|
+
},
|
|
68
|
+
workspace: '/root/clawd'
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Add memorySearch config if private memory is enabled (default: true)
|
|
72
|
+
if (privateMemory !== false) {
|
|
73
|
+
agentDefaults.memorySearch = {
|
|
74
|
+
provider: 'local',
|
|
75
|
+
fallback: 'none',
|
|
76
|
+
local: {
|
|
77
|
+
modelPath: 'hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf'
|
|
78
|
+
},
|
|
79
|
+
query: {
|
|
80
|
+
hybrid: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
vectorWeight: 0.7,
|
|
83
|
+
textWeight: 0.3
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
cache: {
|
|
87
|
+
enabled: true,
|
|
88
|
+
maxEntries: 50000
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const config = {
|
|
94
|
+
gateway: {
|
|
95
|
+
port: 3001,
|
|
96
|
+
mode: 'local',
|
|
97
|
+
bind: 'loopback',
|
|
98
|
+
auth: {
|
|
99
|
+
mode: 'token',
|
|
100
|
+
token: existingToken
|
|
101
|
+
},
|
|
102
|
+
tls: {
|
|
103
|
+
enabled: false
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
agents: {
|
|
107
|
+
defaults: agentDefaults
|
|
108
|
+
},
|
|
109
|
+
channels,
|
|
110
|
+
env: {
|
|
111
|
+
vars: {
|
|
112
|
+
ANTHROPIC_API_KEY: apiKey
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
|
118
|
+
console.log('[config] Config written to', CONFIG_PATH);
|
|
119
|
+
return config;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Write raw config object to disk (for channel management updates).
|
|
124
|
+
*/
|
|
125
|
+
function writeRawConfig(configObj) {
|
|
126
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
127
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(configObj, null, 2), 'utf8');
|
|
130
|
+
console.log('[config] Raw config written to', CONFIG_PATH);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if the gateway is configured (has channels + API key).
|
|
135
|
+
*/
|
|
136
|
+
function isConfigured() {
|
|
137
|
+
const config = readConfig();
|
|
138
|
+
if (!config) return false;
|
|
139
|
+
|
|
140
|
+
const hasApiKey = !!config?.env?.vars?.ANTHROPIC_API_KEY;
|
|
141
|
+
const hasModel = !!config?.agents?.defaults?.model?.primary;
|
|
142
|
+
|
|
143
|
+
// With web channel, we just need API key + model. No channel plugin required.
|
|
144
|
+
return hasApiKey && hasModel;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the subdomain/domain of this server.
|
|
149
|
+
* Tries to read from Caddyfile first, falls back to hostname.
|
|
150
|
+
*/
|
|
151
|
+
function getSubdomain() {
|
|
152
|
+
try {
|
|
153
|
+
// Try reading domain from Caddyfile
|
|
154
|
+
const caddyfile = fs.readFileSync('/etc/caddy/Caddyfile', 'utf8');
|
|
155
|
+
const match = caddyfile.match(/^(\S+\.redlobsta\.cloud)\s*\{/m);
|
|
156
|
+
if (match) return match[1];
|
|
157
|
+
|
|
158
|
+
// Try extracting from hostname (lobsta-xxx → xxx)
|
|
159
|
+
const hostname = os.hostname();
|
|
160
|
+
if (hostname.startsWith('lobsta-')) {
|
|
161
|
+
return hostname.replace('lobsta-', '') + '.redlobsta.cloud';
|
|
162
|
+
}
|
|
163
|
+
return hostname;
|
|
164
|
+
} catch {
|
|
165
|
+
return os.hostname() || 'unknown';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
readConfig,
|
|
171
|
+
writeConfig,
|
|
172
|
+
writeRawConfig,
|
|
173
|
+
isConfigured,
|
|
174
|
+
getSubdomain,
|
|
175
|
+
CONFIG_PATH
|
|
176
|
+
};
|
package/lib/gateway.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LobstaKit Cloud — Gateway Manager
|
|
3
|
+
*
|
|
4
|
+
* Controls the openclaw-gateway systemd service.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const SERVICE_NAME = 'openclaw-gateway';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if the gateway service is currently running.
|
|
13
|
+
*/
|
|
14
|
+
function isGatewayRunning() {
|
|
15
|
+
try {
|
|
16
|
+
const result = execSync(`systemctl is-active ${SERVICE_NAME}`, {
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
timeout: 5000
|
|
19
|
+
}).trim();
|
|
20
|
+
return result === 'active';
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Start the gateway service.
|
|
28
|
+
*/
|
|
29
|
+
function startGateway() {
|
|
30
|
+
try {
|
|
31
|
+
execSync(`systemctl start ${SERVICE_NAME}`, {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
timeout: 15000
|
|
34
|
+
});
|
|
35
|
+
console.log('[gateway] Service started');
|
|
36
|
+
return { success: true };
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error('[gateway] Failed to start:', err.message);
|
|
39
|
+
return { success: false, error: err.message };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stop the gateway service.
|
|
45
|
+
*/
|
|
46
|
+
function stopGateway() {
|
|
47
|
+
try {
|
|
48
|
+
execSync(`systemctl stop ${SERVICE_NAME}`, {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
timeout: 15000
|
|
51
|
+
});
|
|
52
|
+
console.log('[gateway] Service stopped');
|
|
53
|
+
return { success: true };
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('[gateway] Failed to stop:', err.message);
|
|
56
|
+
return { success: false, error: err.message };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Restart the gateway service (enables it first if not already enabled).
|
|
62
|
+
*/
|
|
63
|
+
function restartGateway() {
|
|
64
|
+
try {
|
|
65
|
+
// Ensure service is enabled (cloud-init doesn't enable it — LobstaKit does after setup)
|
|
66
|
+
execSync(`systemctl enable ${SERVICE_NAME}`, {
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
timeout: 10000
|
|
69
|
+
});
|
|
70
|
+
execSync(`systemctl restart ${SERVICE_NAME}`, {
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
timeout: 15000
|
|
73
|
+
});
|
|
74
|
+
console.log('[gateway] Service enabled and restarted');
|
|
75
|
+
return { success: true };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('[gateway] Failed to restart:', err.message);
|
|
78
|
+
return { success: false, error: err.message };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get recent gateway logs from journalctl.
|
|
84
|
+
*/
|
|
85
|
+
function getGatewayLogs(lines = 50) {
|
|
86
|
+
try {
|
|
87
|
+
const logs = execSync(`journalctl -u ${SERVICE_NAME} -n ${lines} --no-pager`, {
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
timeout: 5000
|
|
90
|
+
});
|
|
91
|
+
return logs;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error('[gateway] Failed to get logs:', err.message);
|
|
94
|
+
return `Error retrieving logs: ${err.message}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
isGatewayRunning,
|
|
100
|
+
startGateway,
|
|
101
|
+
stopGateway,
|
|
102
|
+
restartGateway,
|
|
103
|
+
getGatewayLogs
|
|
104
|
+
};
|
package/lib/proxy.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LobstaKit Cloud — Reverse Proxy
|
|
3
|
+
*
|
|
4
|
+
* Proxies requests to the OpenClaw gateway running on port 3001.
|
|
5
|
+
* Supports both HTTP and WebSocket forwarding.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
9
|
+
|
|
10
|
+
const GATEWAY_URL = 'http://127.0.0.1:3001';
|
|
11
|
+
|
|
12
|
+
const proxyMiddleware = createProxyMiddleware({
|
|
13
|
+
target: GATEWAY_URL,
|
|
14
|
+
changeOrigin: true,
|
|
15
|
+
ws: true,
|
|
16
|
+
// Don't log every proxied request
|
|
17
|
+
logLevel: 'warn',
|
|
18
|
+
// Handle proxy errors gracefully
|
|
19
|
+
on: {
|
|
20
|
+
error: (err, req, res) => {
|
|
21
|
+
console.error('[proxy] Error:', err.message);
|
|
22
|
+
if (res.writeHead) {
|
|
23
|
+
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
24
|
+
res.end(JSON.stringify({
|
|
25
|
+
error: 'Gateway unavailable',
|
|
26
|
+
message: 'The OpenClaw gateway is not responding. Check the management dashboard.'
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
module.exports = proxyMiddleware;
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lobstakit-cloud",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "LobstaKit Cloud — Setup wizard and management for LobstaCloud gateways",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lobstakit": "./bin/lobstakit.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node server.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"express": "^4.21.0",
|
|
14
|
+
"http-proxy-middleware": "^3.0.0"
|
|
15
|
+
}
|
|
16
|
+
}
|