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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../server.js');
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
+ }