GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。 NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは 別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
115 lines
4.1 KiB
JavaScript
115 lines
4.1 KiB
JavaScript
const express = require('express');
|
|
const { checkKeycloakToken } = require('./keycloakCheck');
|
|
const { initialState, evaluate } = require('./alertState');
|
|
const { sendAlert } = require('./notifier');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
const config = {
|
|
keycloakBaseUrl: process.env.KEYCLOAK_BASE_URL,
|
|
keycloakRealm: process.env.KEYCLOAK_REALM,
|
|
keycloakClientId: process.env.KEYCLOAK_CLIENT_ID,
|
|
keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
|
|
checkIntervalMs: Number(process.env.CHECK_INTERVAL_SECONDS || 60) * 1000,
|
|
alertThreshold: Number(process.env.ALERT_THRESHOLD || 3),
|
|
realertIntervalMs: Number(process.env.REALERT_INTERVAL_MINUTES || 30) * 60 * 1000,
|
|
};
|
|
|
|
let alertState = initialState();
|
|
let lastCheck = { ok: null, at: null, error: null };
|
|
|
|
async function tick() {
|
|
const result = await checkKeycloakToken({
|
|
baseUrl: config.keycloakBaseUrl,
|
|
realm: config.keycloakRealm,
|
|
clientId: config.keycloakClientId,
|
|
clientSecret: config.keycloakClientSecret,
|
|
});
|
|
lastCheck = { ok: result.ok, at: new Date().toISOString(), error: result.error || null };
|
|
|
|
const { newState, action } = evaluate(alertState, result.ok, Date.now(), {
|
|
alertThreshold: config.alertThreshold,
|
|
realertIntervalMs: config.realertIntervalMs,
|
|
});
|
|
alertState = newState;
|
|
|
|
const target = `${config.keycloakBaseUrl}/realms/${config.keycloakRealm}`;
|
|
|
|
if (action === 'alert-down') {
|
|
console.error(`[ALERT] Keycloak異常検知(${config.alertThreshold}回連続失敗): ${result.error}`);
|
|
await sendAlert({
|
|
subject: '[緊急] Keycloak SSO 異常検知',
|
|
text: [
|
|
`Keycloakのトークン発行が${config.alertThreshold}回連続で失敗しました。`,
|
|
'',
|
|
`エラー: ${result.error}`,
|
|
`発生時刻: ${lastCheck.at}`,
|
|
`監視対象: ${target}`,
|
|
'',
|
|
'プリザンター・XWiki等のSSOログインに影響している可能性があります。',
|
|
].join('\n'),
|
|
}).catch((e) => console.error('通知メール送信失敗:', e.message));
|
|
} else if (action === 'alert-still-down') {
|
|
console.error(`[ALERT] Keycloak異常継続中: ${result.error}`);
|
|
await sendAlert({
|
|
subject: '[継続] Keycloak SSO 異常継続中',
|
|
text: [
|
|
'Keycloakのトークン発行が引き続き失敗しています。',
|
|
'',
|
|
`エラー: ${result.error}`,
|
|
`確認時刻: ${lastCheck.at}`,
|
|
`監視対象: ${target}`,
|
|
].join('\n'),
|
|
}).catch((e) => console.error('通知メール送信失敗:', e.message));
|
|
} else if (action === 'alert-recovered') {
|
|
console.log('[RECOVER] Keycloak復旧確認');
|
|
await sendAlert({
|
|
subject: '[復旧] Keycloak SSO 復旧',
|
|
text: [
|
|
'Keycloakのトークン発行が復旧しました。',
|
|
'',
|
|
`復旧確認時刻: ${lastCheck.at}`,
|
|
`監視対象: ${target}`,
|
|
].join('\n'),
|
|
}).catch((e) => console.error('通知メール送信失敗:', e.message));
|
|
}
|
|
}
|
|
|
|
app.get('/', (req, res) => {
|
|
res.json({ app: 'keycloak-monitor', status: 'ok' });
|
|
});
|
|
|
|
// Dokploy/Traefikのヘルスチェックが叩くエンドポイント。
|
|
// 監視プロセス自身の生存確認であり、Keycloakの状態そのものではない(それは/status参照)。
|
|
app.get('/health', (req, res) => {
|
|
res.status(200).json({ status: 'healthy' });
|
|
});
|
|
|
|
app.get('/status', (req, res) => {
|
|
res.json({
|
|
lastCheck,
|
|
consecutiveFailures: alertState.consecutiveFailures,
|
|
alerted: alertState.alerted,
|
|
checkIntervalSeconds: config.checkIntervalMs / 1000,
|
|
alertThreshold: config.alertThreshold,
|
|
realertIntervalMinutes: config.realertIntervalMs / 60000,
|
|
notifyEmails: (process.env.NOTIFY_EMAILS || '').split(',').map((s) => s.trim()).filter(Boolean),
|
|
});
|
|
});
|
|
|
|
/* c8 ignore start */
|
|
if (require.main === module) {
|
|
app.listen(PORT, () => {
|
|
console.log(`keycloak-monitor listening on port ${PORT}`);
|
|
if (!config.keycloakClientSecret) {
|
|
console.warn('KEYCLOAK_CLIENT_SECRET未設定です。監視は失敗し続けます。');
|
|
}
|
|
tick();
|
|
setInterval(tick, config.checkIntervalMs);
|
|
});
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
module.exports = { app, tick };
|