fix: proxy HTTP health checks via server to avoid mixed-content block

- Add /api/check?url= endpoint to server.js for server-side HTTP checks
- Gitea and Syncthing use proxy:true to route through this endpoint
- Fixes Gitea/Syncthing showing DOWN due to https→http mixed content

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
posimai 2026-03-31 21:24:13 +09:00
parent 4ba31525cb
commit 15b87f3722
2 changed files with 37 additions and 7 deletions

View File

@ -100,6 +100,24 @@ app.get('/api/health', (req, res) => {
}, 100);
});
// ── サービス死活チェックプロキシ (/api/check?url=...) ──────────
// ブラウザの mixed-content 制限を回避するためサーバー側から HTTP チェック
app.get('/api/check', async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
const { url } = req.query;
if (!url) return res.status(400).json({ ok: false, error: 'url required' });
const t0 = Date.now();
try {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 5000);
const r = await fetch(url, { method: 'HEAD', signal: ctrl.signal });
clearTimeout(timer);
res.json({ ok: true, status: r.status, latency_ms: Date.now() - t0 });
} catch (e) {
res.json({ ok: false, error: e.message, latency_ms: Date.now() - t0 });
}
});
// Tailscale証明書を自動検出
function findCert() {
const home = os.homedir();

View File

@ -311,8 +311,8 @@ const HISTORY_MAX = 20;
const SERVICES = [
{id:'posimai-dev',name:'posimai-dev', desc:'ブラウザターミナル + Claude Code',url:HEALTH_URL, isHealth:true},
{id:'posimai-api',name:'Posimai API',desc:'Node.js / Express — VPS 本番', url:'https://api.soar-enrich.com', isHealth:false},
{id:'gitea', name:'Gitea', desc:'ローカル Git バックアップ', url:'http://100.76.7.3:3000', isHealth:false},
{id:'syncthing', name:'Syncthing', desc:'ファイル同期 GUI', url:'http://100.77.11.43:8384', isHealth:false},
{id:'gitea', name:'Gitea', desc:'ローカル Git バックアップ', url:'/api/check?url=http://100.76.7.3:3000', isHealth:false, proxy:true},
{id:'syncthing', name:'Syncthing', desc:'ファイル同期 GUI', url:'/api/check?url=http://100.77.11.43:8384', isHealth:false, proxy:true},
{id:'vercel', name:'Vercel', desc:'PWA ホスティング (27本)', url:'https://vercel.com', isHealth:false},
{id:'github', name:'GitHub', desc:'ソースコード管理', url:'https://github.com/posimai', isHealth:false},
];
@ -461,11 +461,23 @@ async function checkService(svc){
const t0=Date.now();
try{
const ctrl=new AbortController(),timer=setTimeout(()=>ctrl.abort(),7000);
await fetch(svc.url,{method:'HEAD',mode:'no-cors',signal:ctrl.signal});
clearTimeout(timer);
badge.className='service-badge ok'; badge.textContent='OK';
latEl.textContent=`${Date.now()-t0}ms`;
pushSvcHistory(svc.id,true);
if(svc.proxy){
// サーバー経由プロキシチェックmixed-content 回避)
const r=await fetch(svc.url,{signal:ctrl.signal});
clearTimeout(timer);
const data=await r.json();
const ok=data.ok||(data.status&&data.status<500);
badge.className='service-badge '+(ok?'ok':'crit');
badge.textContent=ok?'OK':'DOWN';
latEl.textContent=data.latency_ms?`${data.latency_ms}ms`:'';
pushSvcHistory(svc.id,!!ok);
}else{
await fetch(svc.url,{method:'HEAD',mode:'no-cors',signal:ctrl.signal});
clearTimeout(timer);
badge.className='service-badge ok'; badge.textContent='OK';
latEl.textContent=`${Date.now()-t0}ms`;
pushSvcHistory(svc.id,true);
}
}catch(e){
badge.className='service-badge crit'; badge.textContent='DOWN'; latEl.textContent='';
pushSvcHistory(svc.id,false);