feat: posimai-dev — aurora terminal, systemd service, atlas sync, master-architecture update
This commit is contained in:
parent
f38b76a9e9
commit
b61831d3a2
|
|
@ -37,6 +37,9 @@
|
|||
║ diff / clean / timer / digest / think / site ║
|
||||
║ events / maps / tech-events / analytics / roadmap ║
|
||||
║ ║
|
||||
║ 【セルフホスト】posimai-dev(Ubuntu PC / Tailscale) ║
|
||||
║ https://ubuntu-pc-pc-mkm21cz79ys4.tail72e846.ts.net:3333║
|
||||
║ ║
|
||||
║ 【計画中】*.posimai.soar-enrich.com ワイルドカード DNS ║
|
||||
║ → Passkey の rpID 問題を解決・Eiji に依頼予定 ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@
|
|||
:root {
|
||||
--accent: #A78BFA;
|
||||
--accent-dim: rgba(167, 139, 250, 0.15);
|
||||
--dev-bg: #0C1221;
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--accent: #7C3AED;
|
||||
--dev-bg: #0C1221;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
|
@ -48,7 +50,7 @@
|
|||
flex-direction: column;
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
background: var(--dev-bg);
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
@ -107,6 +109,10 @@
|
|||
overflow: hidden;
|
||||
padding: 12px;
|
||||
min-height: 0;
|
||||
background:
|
||||
radial-gradient(ellipse at 15% 60%, rgba(34, 211, 238, 0.07) 0%, transparent 55%),
|
||||
radial-gradient(ellipse at 85% 25%, rgba(167, 139, 250, 0.07) 0%, transparent 55%),
|
||||
var(--dev-bg);
|
||||
}
|
||||
|
||||
#terminal-container .xterm {
|
||||
|
|
@ -184,8 +190,9 @@
|
|||
fontSize: 14,
|
||||
lineHeight: 1.4,
|
||||
cursorBlink: true,
|
||||
allowTransparency: true,
|
||||
theme: {
|
||||
background: '#0D0D0D',
|
||||
background: 'rgba(12, 18, 33, 0.0)',
|
||||
foreground: '#F3F4F6',
|
||||
cursor: '#A78BFA',
|
||||
selectionBackground: 'rgba(167, 139, 250, 0.3)',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=posimai-dev portal
|
||||
After=network.target tailscaled.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ubuntu-pc
|
||||
WorkingDirectory=/home/ubuntu-pc/posimai-project/posimai-dev
|
||||
ExecStart=/home/ubuntu-pc/.npm-global/bin/node server.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
Environment=PATH=/home/ubuntu-pc/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
148
server.js
148
server.js
|
|
@ -2141,6 +2141,154 @@ ${excerpt}
|
|||
}
|
||||
});
|
||||
|
||||
// ── Atlas: GitHub scan proxy ───────────────────────────────────
|
||||
r.get('/atlas/github-scan', (req, res) => {
|
||||
const token = req.query.token;
|
||||
const org = req.query.org || '';
|
||||
if (!token) return res.status(400).json({ error: 'token required' });
|
||||
|
||||
const https = require('https');
|
||||
|
||||
function ghRequest(path, cb) {
|
||||
const options = {
|
||||
hostname: 'api.github.com',
|
||||
path,
|
||||
method: 'GET',
|
||||
family: 4,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/vnd.github+json',
|
||||
'User-Agent': 'Posimai-Atlas/1.0',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
timeout: 12000,
|
||||
};
|
||||
const r2 = https.request(options, (resp) => {
|
||||
let body = '';
|
||||
resp.on('data', chunk => { body += chunk; });
|
||||
resp.on('end', () => cb(null, resp.statusCode, body));
|
||||
});
|
||||
r2.on('timeout', () => { r2.destroy(); cb(new Error('Timeout')); });
|
||||
r2.on('error', cb);
|
||||
r2.end();
|
||||
}
|
||||
|
||||
const orgPath = org ? `/orgs/${encodeURIComponent(org)}/repos?per_page=100&sort=updated` : null;
|
||||
const userPath = `/user/repos?per_page=100&sort=updated&affiliation=owner`;
|
||||
|
||||
function handleResult(status, body) {
|
||||
if (status !== 200) return res.status(status).json({ error: body });
|
||||
try { res.json(JSON.parse(body)); }
|
||||
catch (e) { res.status(500).json({ error: 'Invalid JSON' }); }
|
||||
}
|
||||
|
||||
if (orgPath) {
|
||||
ghRequest(orgPath, (err, status, body) => {
|
||||
if (err) return res.status(500).json({ error: err.message });
|
||||
// If org not accessible, fall back to user repos
|
||||
if (status === 404 || status === 403) {
|
||||
ghRequest(userPath, (err2, status2, body2) => {
|
||||
if (err2) return res.status(500).json({ error: err2.message });
|
||||
// Signal to client that we fell back
|
||||
if (status2 === 200) {
|
||||
try {
|
||||
const data = JSON.parse(body2);
|
||||
return res.json({ repos: data, fallback: true });
|
||||
} catch (e) { return res.status(500).json({ error: 'Invalid JSON' }); }
|
||||
}
|
||||
handleResult(status2, body2);
|
||||
});
|
||||
} else {
|
||||
handleResult(status, body);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ghRequest(userPath, (err, status, body) => {
|
||||
if (err) return res.status(500).json({ error: err.message });
|
||||
handleResult(status, body);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── Atlas: Vercel scan proxy ───────────────────────────────────
|
||||
r.get('/atlas/vercel-scan', (req, res) => {
|
||||
const token = req.query.token;
|
||||
if (!token) return res.status(400).json({ error: 'token required' });
|
||||
|
||||
const https = require('https');
|
||||
const options = {
|
||||
hostname: 'api.vercel.com',
|
||||
path: '/v9/projects?limit=100',
|
||||
method: 'GET',
|
||||
family: 4,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'User-Agent': 'Posimai-Atlas/1.0',
|
||||
},
|
||||
timeout: 12000,
|
||||
};
|
||||
|
||||
const req2 = https.request(options, (r2) => {
|
||||
let body = '';
|
||||
r2.on('data', chunk => { body += chunk; });
|
||||
r2.on('end', () => {
|
||||
if (r2.statusCode !== 200) return res.status(r2.statusCode).json({ error: body });
|
||||
try { res.json(JSON.parse(body)); }
|
||||
catch (e) { res.status(500).json({ error: 'Invalid JSON' }); }
|
||||
});
|
||||
});
|
||||
req2.on('timeout', () => { req2.destroy(); res.status(500).json({ error: 'Timeout' }); });
|
||||
req2.on('error', (e) => { res.status(500).json({ error: e.message, code: e.code }); });
|
||||
req2.end();
|
||||
});
|
||||
|
||||
// ── Atlas: Tailscale scan proxy ────────────────────────────────
|
||||
r.get('/atlas/tailscale-scan', (req, res) => {
|
||||
const token = req.query.token;
|
||||
if (!token) return res.status(400).json({ error: 'token required' });
|
||||
|
||||
const https = require('https');
|
||||
const options = {
|
||||
hostname: 'api.tailscale.com',
|
||||
path: '/api/v2/tailnet/-/devices',
|
||||
method: 'GET',
|
||||
family: 4, // force IPv4; container IPv6 to tailscale times out
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
'User-Agent': 'Posimai-Atlas/1.0',
|
||||
},
|
||||
timeout: 12000,
|
||||
};
|
||||
|
||||
const req2 = https.request(options, (r2) => {
|
||||
let body = '';
|
||||
r2.on('data', chunk => { body += chunk; });
|
||||
r2.on('end', () => {
|
||||
if (r2.statusCode !== 200) {
|
||||
return res.status(r2.statusCode).json({ error: body });
|
||||
}
|
||||
try {
|
||||
res.json(JSON.parse(body));
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Invalid JSON from Tailscale' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req2.on('timeout', () => {
|
||||
req2.destroy();
|
||||
res.status(500).json({ error: 'Request timed out' });
|
||||
});
|
||||
|
||||
req2.on('error', (e) => {
|
||||
console.error('[atlas/tailscale-scan] error:', e.code, e.message);
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
});
|
||||
|
||||
req2.end();
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue