-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinode_bridge.js
More file actions
104 lines (89 loc) · 3.76 KB
/
Copy pathlinode_bridge.js
File metadata and controls
104 lines (89 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* Linode API v4 Proxy & Telemetry Bridge (Full Control Plane)
* Run with: LINODE_TOKEN="your_pat_token" node linode_bridge.js
*/
const http = require('http');
const PORT = process.env.PORT || 3005;
const LINODE_TOKEN = process.env.LINODE_TOKEN || '';
async function fetchLinode(path, method = 'GET', body = null, tokenOverride = null) {
const token = tokenOverride || LINODE_TOKEN;
if (!token) {
throw new Error('LINODE_TOKEN is not provided. Set env var or pass token header.');
}
const options = {
method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
};
if (body) {
options.body = JSON.stringify(body);
}
const res = await fetch(`https://api.linode.com/v4${path}`, options);
if (!res.ok) {
const errText = await res.text();
throw new Error(`Linode API ${res.status}: ${errText}`);
}
return await res.json();
}
const server = http.createServer(async (req, res) => {
// CORS Headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const authHeader = req.headers['authorization'];
const tokenOverride = authHeader ? authHeader.replace('Bearer ', '') : null;
try {
const url = new URL(req.url, `http://localhost:${PORT}`);
// GET /api/linode/instances
if (url.pathname === '/api/linode/instances' && req.method === 'GET') {
const data = await fetchLinode('/linode/instances', 'GET', null, tokenOverride);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
return;
}
// GET /api/linode/transfer
if (url.pathname === '/api/linode/transfer' && req.method === 'GET') {
const data = await fetchLinode('/account/transfer', 'GET', null, tokenOverride);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
return;
}
// GET /api/linode/instances/:id/stats
const statsMatch = url.pathname.match(/^\/api\/linode\/instances\/(\d+)\/stats$/);
if (statsMatch && req.method === 'GET') {
const instanceId = statsMatch[1];
const data = await fetchLinode(`/linode/instances/${instanceId}/stats`, 'GET', null, tokenOverride);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
return;
}
// POST /api/linode/instances/:id/:action (reboot|shutdown|boot)
const actionMatch = url.pathname.match(/^\/api\/linode\/instances\/(\d+)\/(reboot|shutdown|boot)$/);
if (actionMatch && req.method === 'POST') {
const [, instanceId, action] = actionMatch;
const data = await fetchLinode(`/linode/instances/${instanceId}/${action}`, 'POST', null, tokenOverride);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, action, instanceId, data }));
return;
}
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: `Route ${url.pathname} not found.` }));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: err.message }));
}
});
server.listen(PORT, () => {
console.log(`\n======================================================`);
console.log(`🚀 Linode Control Bridge active at http://localhost:${PORT}`);
console.log(`👉 Instances route: http://localhost:${PORT}/api/linode/instances`);
console.log(`👉 Account transfer: http://localhost:${PORT}/api/linode/transfer`);
console.log(`======================================================\n`);
});