chore: ignore runtime data + scratch files, remove dead root routes/
Working tree accumulated 172 untracked/modified files from the auto-updater:
- 19 secret/runtime files in dashcaddy-api/data/ that should never be tracked
- 199 byte-identical duplicates of tracked files dumped at root by an
outdated rsync/cp step
- 6 scratch debug scripts (cm_check.js, login_test.js, full_test.js, ...)
- 7 .bak-* files from start.sh and dashcaddy-update.sh rollback branches
- Root-level routes/ directory: dead code, container COPYs dashcaddy-api/routes/
.gitignore now ignores:
- dashcaddy-api/data/ (runtime: credentials, secrets, history)
- start.sh.bak*, scripts/*.bak* (auto-updater rollback backups)
- updates/ (auto-updater runtime state)
- cm_check*.js, *_test.js (scratch debug scripts)
Removed dead code:
- routes/openclaw.js (replaced by dashcaddy-api/routes/openclaw.js)
Recreated runtime scripts that were deleted with their duplicates:
- start.sh (canonical container-start, 47-line full config)
- scripts/dashcaddy-update.sh was already untracked; fixed the tracked
dashcaddy-api/scripts/dashcaddy-update.sh instead (see next commit)
Net change: 172 → 17 files in working tree.
This commit is contained in:
+15
@@ -2,6 +2,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Runtime state/config files (generated, not source)
|
# Runtime state/config files (generated, not source)
|
||||||
|
# Note: data/ subdir contains runtime state (credentials, secrets, history) — never commit
|
||||||
|
dashcaddy-api/data/
|
||||||
dashcaddy-api/credentials.json
|
dashcaddy-api/credentials.json
|
||||||
dashcaddy-api/.env
|
dashcaddy-api/.env
|
||||||
.env
|
.env
|
||||||
@@ -17,6 +19,19 @@ dashcaddy-api/update-config.json
|
|||||||
dashcaddy-api/update-history.json
|
dashcaddy-api/update-history.json
|
||||||
dashcaddy-api/dashcaddy-errors.log
|
dashcaddy-api/dashcaddy-errors.log
|
||||||
|
|
||||||
|
# Auto-updater backups (created by dashcaddy-update.sh when rolling back)
|
||||||
|
start.sh.bak*
|
||||||
|
scripts/*.bak*
|
||||||
|
|
||||||
|
# Auto-updater runtime state (history + secrets + staging)
|
||||||
|
updates/
|
||||||
|
|
||||||
|
# Scratch / debug scripts (left over from past sessions)
|
||||||
|
cm_check*.js
|
||||||
|
full_test.js
|
||||||
|
login_test.js
|
||||||
|
login_backup_test.js
|
||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
dashcaddy-installer/build-output/
|
dashcaddy-installer/build-output/
|
||||||
dashcaddy-installer/dist/
|
dashcaddy-installer/dist/
|
||||||
|
|||||||
@@ -1,272 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const http = require('http');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenClaw management routes
|
|
||||||
* Proxies gateway API calls through DashCaddy so the token never leaves the server.
|
|
||||||
*
|
|
||||||
* GET /openclaw/status → container info + gateway health
|
|
||||||
* POST /openclaw/deploy → deploy OpenClaw container
|
|
||||||
* GET /openclaw/proxy/* → proxy GET to gateway
|
|
||||||
* POST /openclaw/proxy/* → proxy POST to gateway
|
|
||||||
* DELETE /openclaw → remove container
|
|
||||||
*/
|
|
||||||
module.exports = function openClawRoutes(ctx) {
|
|
||||||
const router = express.Router();
|
|
||||||
const docker = ctx.docker;
|
|
||||||
const asyncHandler = ctx.asyncHandler;
|
|
||||||
const log = ctx.log || console;
|
|
||||||
|
|
||||||
// ── helpers ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async function findOpenClawContainer() {
|
|
||||||
const containers = await docker.client.listContainers({ all: true });
|
|
||||||
return containers.find(function(c) {
|
|
||||||
return c.Image === 'ghcr.io/nousresearch/openclaw:latest' ||
|
|
||||||
(c.Labels && c.Labels['dashcaddy.managed'] === 'true' &&
|
|
||||||
c.Names.some(function(n) { return n.includes('openclaw'); }));
|
|
||||||
}) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getGatewayToken(containerId) {
|
|
||||||
try {
|
|
||||||
const info = await docker.client.containerInfo(containerId);
|
|
||||||
const entry = (info.Config.Env || []).find(function(e) {
|
|
||||||
return e.startsWith('OPENCLAW_GATEWAY_TOKEN=');
|
|
||||||
});
|
|
||||||
return entry ? entry.split('=')[1] : null;
|
|
||||||
} catch(err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getContainerPort(containerId) {
|
|
||||||
try {
|
|
||||||
const containers = await docker.client.listContainers({ all: true });
|
|
||||||
const c = containers.find(function(x) {
|
|
||||||
return x.Id === containerId || x.Id.startsWith(containerId);
|
|
||||||
});
|
|
||||||
if (c && c.Ports) {
|
|
||||||
const p = c.Ports.find(function(x) { return x.PrivatePort === 18792; });
|
|
||||||
if (p && p.PublicPort) return String(p.PublicPort);
|
|
||||||
}
|
|
||||||
return '18792';
|
|
||||||
} catch(err) {
|
|
||||||
return '18792';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function gatewayHealth(baseUrl, token) {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
const headers = {};
|
|
||||||
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
||||||
const req = http.get(baseUrl + '/health', { headers: headers }, function(res) {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', function(d) { data += d; });
|
|
||||||
res.on('end', function() {
|
|
||||||
try { resolve({ ok: true, data: JSON.parse(data) }); }
|
|
||||||
catch(e) { resolve({ ok: true, data: data }); }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', function(e) { resolve({ ok: false, error: e.message }); });
|
|
||||||
req.setTimeout(5000, function() { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function proxyRequest(req, res, targetBase, path, token) {
|
|
||||||
const headers = {};
|
|
||||||
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
||||||
headers['X-Forwarded-For'] = req.ip;
|
|
||||||
headers['X-Forwarded-Proto'] = req.protocol;
|
|
||||||
|
|
||||||
const url = targetBase + '/' + path;
|
|
||||||
const method = req.method;
|
|
||||||
|
|
||||||
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
||||||
const body = JSON.stringify(req.body);
|
|
||||||
headers['Content-Type'] = 'application/json';
|
|
||||||
headers['Content-Length'] = Buffer.byteLength(body);
|
|
||||||
|
|
||||||
const proxyReq = http.request(url, { method: method, headers: headers }, function(proxyRes) {
|
|
||||||
res.set(proxyRes.headers);
|
|
||||||
res.status(proxyRes.statusCode);
|
|
||||||
proxyRes.on('data', function(d) { res.write(d); });
|
|
||||||
proxyRes.on('end', function() { res.end(); });
|
|
||||||
});
|
|
||||||
proxyReq.on('error', function(e) { res.status(502).json({ success: false, error: e.message }); });
|
|
||||||
proxyReq.setTimeout(15000, function() { proxyReq.destroy(); res.status(504).json({ success: false, error: 'gateway timeout' }); });
|
|
||||||
proxyReq.write(body);
|
|
||||||
proxyReq.end();
|
|
||||||
} else {
|
|
||||||
const proxyReq = http.get(url, { headers: headers }, function(proxyRes) {
|
|
||||||
res.set(proxyRes.headers);
|
|
||||||
res.status(proxyRes.statusCode);
|
|
||||||
proxyRes.on('data', function(d) { res.write(d); });
|
|
||||||
proxyRes.on('end', function() { res.end(); });
|
|
||||||
});
|
|
||||||
proxyReq.on('error', function(e) { res.status(502).json({ success: false, error: e.message }); });
|
|
||||||
proxyReq.setTimeout(15000, function() { proxyReq.destroy(); res.status(504).json({ success: false, error: 'gateway timeout' }); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── GET /openclaw/status ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
router.get('/status', asyncHandler(async function(req, res) {
|
|
||||||
const container = await findOpenClawContainer();
|
|
||||||
|
|
||||||
if (!container) {
|
|
||||||
return res.json({ success: true, deployed: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = await getGatewayToken(container.Id);
|
|
||||||
const port = await getContainerPort(container.Id);
|
|
||||||
const baseUrl = 'http://localhost:' + port;
|
|
||||||
const health = await gatewayHealth(baseUrl, token);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
deployed: true,
|
|
||||||
container: {
|
|
||||||
id: container.Id.slice(0, 12),
|
|
||||||
name: container.Name,
|
|
||||||
state: container.State,
|
|
||||||
status: container.Status,
|
|
||||||
created: container.Created,
|
|
||||||
image: container.Image
|
|
||||||
},
|
|
||||||
gateway: {
|
|
||||||
url: baseUrl,
|
|
||||||
port: port,
|
|
||||||
healthy: health.ok,
|
|
||||||
healthData: health.data || null,
|
|
||||||
tokenSet: !!token
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
// ── POST /openclaw/deploy ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
router.post('/deploy', asyncHandler(async function(req, res) {
|
|
||||||
const existing = await findOpenClawContainer();
|
|
||||||
if (existing) {
|
|
||||||
return res.status(409).json({ success: false, error: 'OpenClaw is already deployed' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = 'ghcr.io/nousresearch/openclaw:latest';
|
|
||||||
const name = 'openclaw-' + Date.now();
|
|
||||||
const gatewayToken = generateToken();
|
|
||||||
|
|
||||||
// Pull image
|
|
||||||
log.info('Pulling ' + image + '...');
|
|
||||||
try {
|
|
||||||
await new Promise(function(resolve, reject) {
|
|
||||||
docker.client.pull(image, function(err, stream) {
|
|
||||||
if (err) return reject(err);
|
|
||||||
docker.client.modem.followProgress(stream, function(err2) {
|
|
||||||
if (err2) return reject(err2);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
log.error('OpenClaw pull failed: ' + e.message);
|
|
||||||
return res.status(500).json({ success: false, error: 'Failed to pull image: ' + e.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create + start container
|
|
||||||
try {
|
|
||||||
const container = await docker.client.createContainer({
|
|
||||||
name: name,
|
|
||||||
Image: image,
|
|
||||||
Env: [
|
|
||||||
'OPENCLAW_GATEWAY_MODE=local',
|
|
||||||
'OPENCLAW_GATEWAY_TOKEN=' + gatewayToken
|
|
||||||
],
|
|
||||||
HostConfig: {
|
|
||||||
PortBindings: { '18792/tcp': [{ HostPort: '18792' }] },
|
|
||||||
RestartPolicy: { Name: 'unless-stopped' },
|
|
||||||
Labels: {
|
|
||||||
'dashcaddy.managed': 'true',
|
|
||||||
'dashcaddy.app': 'openclaw'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ExposedPorts: { '18792/tcp': {} }
|
|
||||||
});
|
|
||||||
|
|
||||||
await container.start();
|
|
||||||
log.info('OpenClaw deployed: ' + container.id.slice(0, 12));
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
deployed: true,
|
|
||||||
container: { id: container.id.slice(0, 12), name: name },
|
|
||||||
gateway: {
|
|
||||||
url: 'http://localhost:18792',
|
|
||||||
token: gatewayToken
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
log.error('OpenClaw deploy failed: ' + e.message);
|
|
||||||
res.status(500).json({ success: false, error: 'Deploy failed: ' + e.message });
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// ── GET /openclaw/proxy/* ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
router.get('/proxy/*', asyncHandler(async function(req, res) {
|
|
||||||
const container = await findOpenClawContainer();
|
|
||||||
if (!container) return res.status(404).json({ success: false, error: 'OpenClaw not deployed' });
|
|
||||||
|
|
||||||
const token = await getGatewayToken(container.Id);
|
|
||||||
const port = await getContainerPort(container.Id);
|
|
||||||
const baseUrl = 'http://localhost:' + port;
|
|
||||||
const path = req.params[0];
|
|
||||||
|
|
||||||
proxyRequest(req, res, baseUrl, path, token);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// ── POST /openclaw/proxy/* ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
router.post('/proxy/*', asyncHandler(async function(req, res) {
|
|
||||||
const container = await findOpenClawContainer();
|
|
||||||
if (!container) return res.status(404).json({ success: false, error: 'OpenClaw not deployed' });
|
|
||||||
|
|
||||||
const token = await getGatewayToken(container.Id);
|
|
||||||
const port = await getContainerPort(container.Id);
|
|
||||||
const baseUrl = 'http://localhost:' + port;
|
|
||||||
const path = req.params[0];
|
|
||||||
|
|
||||||
proxyRequest(req, res, baseUrl, path, token);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// ── DELETE /openclaw ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
router.delete('/', asyncHandler(async function(req, res) {
|
|
||||||
const container = await findOpenClawContainer();
|
|
||||||
if (!container) return res.status(404).json({ success: false, error: 'OpenClaw not deployed' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const c = docker.client.container(container.Id);
|
|
||||||
await c.stop().catch(function() {});
|
|
||||||
await c.remove({ force: true });
|
|
||||||
log.info('OpenClaw container ' + container.Id.slice(0, 12) + ' removed');
|
|
||||||
res.json({ success: true, message: 'OpenClaw removed' });
|
|
||||||
} catch(e) {
|
|
||||||
log.error('Failed to remove OpenClaw: ' + e.message);
|
|
||||||
res.status(500).json({ success: false, error: e.message });
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return router;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── token generator ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function generateToken() {
|
|
||||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
let result = '';
|
|
||||||
for (let i = 0; i < 32; i++) {
|
|
||||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user