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 ║
|
║ diff / clean / timer / digest / think / site ║
|
||||||
║ events / maps / tech-events / analytics / roadmap ║
|
║ 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 ║
|
║ 【計画中】*.posimai.soar-enrich.com ワイルドカード DNS ║
|
||||||
║ → Passkey の rpID 問題を解決・Eiji に依頼予定 ║
|
║ → Passkey の rpID 問題を解決・Eiji に依頼予定 ║
|
||||||
╚══════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════╝
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,11 @@
|
||||||
:root {
|
:root {
|
||||||
--accent: #A78BFA;
|
--accent: #A78BFA;
|
||||||
--accent-dim: rgba(167, 139, 250, 0.15);
|
--accent-dim: rgba(167, 139, 250, 0.15);
|
||||||
|
--dev-bg: #0C1221;
|
||||||
}
|
}
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
--accent: #7C3AED;
|
--accent: #7C3AED;
|
||||||
|
--dev-bg: #0C1221;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
@ -48,7 +50,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg);
|
background: var(--dev-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|
@ -107,6 +109,10 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
min-height: 0;
|
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 {
|
#terminal-container .xterm {
|
||||||
|
|
@ -184,8 +190,9 @@
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
allowTransparency: true,
|
||||||
theme: {
|
theme: {
|
||||||
background: '#0D0D0D',
|
background: 'rgba(12, 18, 33, 0.0)',
|
||||||
foreground: '#F3F4F6',
|
foreground: '#F3F4F6',
|
||||||
cursor: '#A78BFA',
|
cursor: '#A78BFA',
|
||||||
selectionBackground: 'rgba(167, 139, 250, 0.3)',
|
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;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue