Track what happens to every certificate, and show it on the Certs tab
NPM knows what a certificate is today and nothing about what it was, so ten of them could fail renewal for months — 1001 certbot runs, zero successes — without anything on any page saying so. Counts start at zero and are only ever observed; only first_seen is seeded, from NPM's own date.
This commit is contained in:
@@ -139,6 +139,82 @@ if ($action === 'npm') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Per-domain history, totals, and the DDNS containers ──────────────────────
|
||||
// Read-only. Tools/cert_history.sh is what writes the store; serving it from here would mean the
|
||||
// counters only advance when somebody happens to have the tab open.
|
||||
if ($action === 'history') {
|
||||
$file = rtrim(defined('DB_DIR') ? DB_DIR : (DATA_DIR . '/db'), '/') . '/cert_history.json';
|
||||
$hist = is_file($file) ? (json_decode((string) @file_get_contents($file), true) ?: []) : [];
|
||||
$doms = is_array($hist['domains'] ?? null) ? $hist['domains'] : [];
|
||||
|
||||
$now = time();
|
||||
$rows = [];
|
||||
$tot = ['tracked' => 0, 'active' => 0, 'retired' => 0, 'removed' => 0,
|
||||
'renewals' => 0, 'failures' => 0, 'checks' => 0, 'oldest' => null];
|
||||
|
||||
foreach ($doms as $domain => $r) {
|
||||
$first = (int) ($r['first_seen'] ?? $now);
|
||||
$retired = !empty($r['retired_at']);
|
||||
$removed = !empty($r['removed_at']);
|
||||
$rows[] = [
|
||||
'domain' => (string) $domain,
|
||||
'first_seen'=> $first,
|
||||
'tracked' => vv_cert_span_php($first, $now),
|
||||
'checks' => (int) ($r['checks'] ?? 0),
|
||||
'renewals' => (int) ($r['renewals'] ?? 0),
|
||||
'failures' => (int) ($r['failures'] ?? 0),
|
||||
'strikes' => (int) ($r['strikes'] ?? 0),
|
||||
'expires' => $r['last_expiry'] ?? null,
|
||||
'last_renewal' => $r['last_renewal'] ?? null,
|
||||
'provider' => $r['provider'] ?? null,
|
||||
'state' => $removed ? 'removed' : ($retired ? 'retired' : 'active'),
|
||||
];
|
||||
$tot['tracked']++;
|
||||
$tot[$removed ? 'removed' : ($retired ? 'retired' : 'active')]++;
|
||||
$tot['renewals'] += (int) ($r['renewals'] ?? 0);
|
||||
$tot['failures'] += (int) ($r['failures'] ?? 0);
|
||||
$tot['checks'] += (int) ($r['checks'] ?? 0);
|
||||
if ($tot['oldest'] === null || $first < $tot['oldest']) $tot['oldest'] = $first;
|
||||
}
|
||||
// Longest-tracked first: the domains with the most history are the ones the card is for.
|
||||
usort($rows, fn($a, $b) => $a['first_seen'] <=> $b['first_seen']);
|
||||
$tot['oldest_span'] = $tot['oldest'] ? vv_cert_span_php($tot['oldest'], $now) : '—';
|
||||
|
||||
// DDNS is on this tab because it is the other half of the same story: a certificate is issued
|
||||
// against a name, and the name only points here while DDNS keeps it pointed. The ten dead
|
||||
// certificates removed on 2026-08-15 all failed with NXDOMAIN.
|
||||
$hostUp = strtoupper(vv_detect_host());
|
||||
$names = vv_parse_bash_array(vv_read_conf_raw(strtolower($hostUp) . '.conf'),
|
||||
$hostUp . '_DDNS_CONTAINERS');
|
||||
$running = [];
|
||||
foreach (vv_docker_containers() as $c) $running[$c['name']] = $c['status'];
|
||||
$ddns = [];
|
||||
foreach ($names as $n) {
|
||||
$n = trim((string) $n);
|
||||
if ($n === '') continue;
|
||||
$ddns[] = ['name' => $n,
|
||||
'running' => isset($running[$n]),
|
||||
'status' => $running[$n] ?? 'not running'];
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true, 'rows' => $rows, 'totals' => $tot, 'ddns' => $ddns,
|
||||
'last_pass' => $hist['last_pass'] ?? null,
|
||||
'strike_limit' => (int) (vv_conf_vars()['CERT_HISTORY_STRIKES'] ?? 5)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Same shape as the tool's own formatter — years, months and days, because "3 years 6 months and
|
||||
// 22 days" is how the question gets asked and 1298 days is the same fact nobody thinks in.
|
||||
function vv_cert_span_php(int $from, int $to): string {
|
||||
if ($to < $from) return '0d';
|
||||
$d = (new DateTime())->setTimestamp($from)->diff((new DateTime())->setTimestamp($to));
|
||||
$out = [];
|
||||
if ($d->y) $out[] = $d->y . 'y';
|
||||
if ($d->m) $out[] = $d->m . 'mo';
|
||||
if ($d->d || !$out) $out[] = $d->d . 'd';
|
||||
return implode(' ', $out);
|
||||
}
|
||||
|
||||
// ── Read configured domains (without running checks) ─────────────────────────
|
||||
if ($action === 'domains') {
|
||||
$hostId = vv_detect_host();
|
||||
|
||||
Reference in New Issue
Block a user