Initial commit

This commit is contained in:
Brus 2026-01-11 17:25:10 +01:00
commit c3f8908cb5
8 changed files with 660 additions and 0 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
# Runtime data
/data/
*.db
# Secrets
config/config.php
.env
# Logs
*.log
# OS / editor
.DS_Store
.idea/
.vscode/

34
Dockerfile Normal file
View file

@ -0,0 +1,34 @@
FROM php:8.2-apache
# Install required system packages
RUN apt-get update && apt-get install -y \
cron \
sqlite3 \
libsqlite3-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo pdo_sqlite
# Enable Apache rewrite (optional)
RUN a2enmod rewrite
# Copy application
COPY index.php /var/www/html/index.php
# COPY history.php /var/www/html/history.php
COPY check_ssl.php /var/www/check_ssl.php
COPY domains.txt /var/www/domains.txt
# Create data directory
RUN mkdir -p /var/www/data \
&& chown -R www-data:www-data /var/www
# Cron job (every 6 hours)
RUN echo "0 */6 * * * php /var/www/check_ssl.php >> /var/log/cron.log 2>&1" \
> /etc/cron.d/ssl \
&& chmod 0644 /etc/cron.d/ssl \
&& crontab /etc/cron.d/ssl
# Start cron + Apache
CMD cron && apache2-foreground

67
check_ssl.php Normal file
View file

@ -0,0 +1,67 @@
<?php
set_time_limit(0);
$db = new PDO("sqlite:/var/www/data/ssl.db");
$db->exec("
CREATE TABLE IF NOT EXISTS certs (
domain TEXT PRIMARY KEY,
expires INTEGER,
checked_at INTEGER,
error TEXT
)");
$domains = file("/var/www/domains.txt", FILE_IGNORE_NEW_LINES);
function check_ssl($domain) {
$ctx = stream_context_create([
"socket" => ["bindto" => "0.0.0.0:0"],
"ssl" => [
"capture_peer_cert" => true,
"verify_peer" => false,
"verify_peer_name" => false,
"SNI_enabled" => true,
"peer_name" => $domain
]
]);
$client = @stream_socket_client(
"ssl://$domain:443",
$errno,
$errstr,
5,
STREAM_CLIENT_CONNECT,
$ctx
);
if (!$client) return [null, $errstr];
$params = stream_context_get_params($client);
if (!isset($params["options"]["ssl"]["peer_certificate"])) {
return [null, "No certificate"];
}
$cert = openssl_x509_parse($params["options"]["ssl"]["peer_certificate"]);
return [$cert["validTo_time_t"] ?? null, null];
}
foreach ($domains as $domain) {
[$expiry, $error] = check_ssl($domain);
$stmt = $db->prepare("
INSERT INTO certs(domain, expires, checked_at, error)
VALUES(:d,:e,:c,:er)
ON CONFLICT(domain) DO UPDATE SET
expires=:e, checked_at=:c, error=:er
");
$stmt->execute([
":d" => $domain,
":e" => $expiry,
":c" => time(),
":er" => $error
]);
usleep(200000); // anti-rate-limit (0.2s)
}
echo "SSL check completed\n";

17
docker-compose.yml Normal file
View file

@ -0,0 +1,17 @@
version: "3.9"
services:
ssl-monitor:
image: ssl-monitor:latest
container_name: ssl-monitor
build: .
ports:
- "8080:80"
volumes:
- ./data:/app/data
- ./config:/app/config
environment:
DB_PATH: /app/data/ssl.db
ALERT_DAYS: 14
ADMIN_EMAIL: admin@example.com
restart: unless-stopped

77
domains.txt Normal file
View file

@ -0,0 +1,77 @@
cms-pews.geosphere.at
cms-vis-pews.geosphere.at
pews.geosphere.at
hub-pews.geosphere.at
cobs.geosphere.at
swap.geosphere.at
portale.geosphere.at
edrop.geosphere.at
taguing.geosphere.at
appshiny.geosphere.at
eshiny-proxy.geosphere.at
klimaportal.geosphere.at
gitlab.geosphere.at
projects.gitlab.geosphere.at
4cast.gitlab.geosphere.at
ieatask51-austria.gitlab.geosphere.at
atmoi4ren-4cast.gitlab.geosphere.at
www.gitlab.geosphere.at
wmo.gitlab.geosphere.at
energyprotect.gitlab.geosphere.at
projectpages.geosphere.at
risklab.geosphere.at
mapi.geosphere.at
autodiscover.geosphere.at
webmail.geosphere.at
eva.geosphere.at
self-service.geosphere.at
vpn.geosphere.at
www.geosphere.at
eduid.geosphere.at
maintenance.hub.geosphere.at
metadata.hub.geosphere.at
datenzentrum.geosphere.at
s3.hub.geosphere.at
station.api.hub.geosphere.at
keycloak.hub.geosphere.at
data.hub.geosphere.at
frontend.dataset.api.staging.hub.geosphere.at
s3direct.hub.geosphere.at
dataset.api.hub.geosphere.at
thredds.staging.hub.geosphere.at
metadata.form.hub.geosphere.at
ckan.metadata.hub.geosphere.at
datenzentrum.hub.geosphere.at
www.dataset.api.hub.geosphere.at
openapi.hub.geosphere.at
s3.staging.hub.geosphere.at
keycloak.staging.hub.geosphere.at
thredds.hub.geosphere.at
public.hub.geosphere.at
mapping.api.staging.hub.geosphere.at
iris.geosphere.at
catalog.geosphere.at
geothermieatlas.geosphere.at
meldung-geodaten.geosphere.at
maps.geosphere.at
resource.geosphere.at
ardigeos.geosphere.at
gis.geosphere.at
smw.geosphere.at
webstat.geosphere.at
oculus.geosphere.at
coturn.geosphere.at
signaling.geosphere.at
recording.geosphere.at
rhea.geosphere.at
rocky-austria.geosphere.at
bibliothek.geosphere.at
bibliothek-admin.geosphere.at
bibliothek-test-admin.geosphere.at
bibliothek-test.geosphere.at
gitea.geosphere.at
jobs.geosphere.at
thesaurus.geolba.ac.at
resource.geolba.ac.at
gemas.geolba.ac.at
catalog.zamg.ac.at

235
domains_ungueltig.txt Normal file
View file

@ -0,0 +1,235 @@
eon2.geosphere.at
keycloak.production.hub-dev.geosphere.at
k8s-prod1.geosphere.at
zaaspure1.geosphere.at
mx1mgmt.geosphere.at
vsents5.geosphere.at
zabbix.geosphere.at
connect.geosphere.at
www.maps.geosphere.at
www.testmattermost.geosphere.at
manual.k8s-review.geosphere.at
status.staging.hub-dev.geosphere.at
www.self-service.geosphere.at
www.resource.geosphere.at
data.staging.hub-dev.geosphere.at
metadata.staging.hub-dev.geosphere.at
backend-test.k8s-dev.geosphere.at
jupyterhub.k8s-dev.geosphere.at
keycloak.dhr.geosphere.at
ise4.geosphere.at
testgitlab.geosphere.at
146www.geosphere.at
foobartest123.geosphere.at
enexyohub-dev.geosphere.at
argocd.k8s-dev.geosphere.at
ckan.metadata.staging.hub-dev.geosphere.at
ise1.geosphere.at
s3.hub-infra.geosphere.at
www.myserver.geosphere.at
klimaportal-dev.geosphere.at
prod.hub-dev.geosphere.at
openapi.staging.hub-dev.geosphere.at
ionbe1502ilom.geosphere.at
gsasmtp.geosphere.at
metadata.form.staging.hub-dev.geosphere.at
my-test.whatever.geosphere.at
hub-staging.geosphere.at
keycloak.hub.staging.geosphere.at
testurl-rancher.geosphere.at
ise3.geosphere.at
www.webmail.geosphere.at
ise.geosphere.at
climate-indicators.geosphere.at
plausible.infra.hub-dev.geosphere.at
www.enexyohub-dev.geosphere.at
argocd.hub-infra.geosphere.at
vsents1.geosphere.at
grafana.k8s-prod1.geosphere.at
interflex.geosphere.at
dataset.api.staging.hub-dev.geosphere.at
grafana.k8s-staging1.geosphere.at
radius2.geosphere.at
k8s-staging1.geosphere.at
www-dev.geosphere.at
s3.hub.staging.geosphere.at
www.harbor.geosphere.at
enimbusd.geosphere.at
tagui.geosphere.at
vsenttest1.geosphere.at
k8s-utility1.geosphere.at
prometheus.k8s-prod1.geosphere.at
ionbe1503ilom.geosphere.at
www.signaling.geosphere.at
www-dev2.geosphere.at
rancher-dev.geosphere.at
vsents2b-l1c.geosphere.at
www.umwanalyse.geosphere.at
www.geothermieatlas.geosphere.at
satosa.k8s-dev.geosphere.at
k8s-staging.geosphere.at
vsentkeycloak.geosphere.at
s3.netapp.geosphere.at
www.nessus.geosphere.at
www.gsasmtp.geosphere.at
www.enimbusd.geosphere.at
plone-backend.k8s-staging1.geosphere.at
public.staging.hub-dev.geosphere.at
staging.hub-dev.geosphere.at
www.interflex.geosphere.at
harbor-dev.k8s-dev.geosphere.at
www.iot.geosphere.at
mapping.api.hub.staging.geosphere.at
thredds.production.hub-dev.geosphere.at
test.geosphere.at
www.prod.hub-dev.geosphere.at
www.www-dev.geosphere.at
rsa2.geosphere.at
wlc1mgmt.geosphere.at
maintenance-staging.geosphere.at
ise1mgmt.geosphere.at
uncached.k8s-prod1.geosphere.at
ewww-dev.geosphere.at
ipam.geosphere.at
plausible.staging.hub-dev.geosphere.at
wlc2mgmt.geosphere.at
www.www.testseite.geosphere.at
rsa1.geosphere.at
www.k8s-prod1.geosphere.at
dataset.api.production.hub-dev.geosphere.at
metadata.form.production.hub-dev.geosphere.at
semaphore.geosphere.at
www.climate-indicators.geosphere.at
foreman.k8s-dev.geosphere.at
ise2.geosphere.at
nessus.geosphere.at
kafka-ui.k8s-dev.geosphere.at
dhr-test.geosphere.at
atom.rancher.geosphere.at
www.enexyohub.geosphere.at
datenzentrum-staging.geosphere.at
geprüft:
jobs.geosphere.at
ftp.geosphere.at
go.geosphere.at
cms-pews-test.geosphere.at
cms-vis-pews-test.geosphere.at
pews-test.geosphere.at
sftp.geosphere.at
mx1.geosphere.at
mx2.geosphere.at
pep725.geosphere.at
adfs.geosphere.at
eva-dev.geosphere.at
iot.geosphere.at
ns1.geosphere.at
ns2.geosphere.at
vdi.geosphere.at
beta.geosphere.at
hub.geosphere.at
api.hub.geosphere.at
dataset.api.staging.hub.geosphere.at
www.klimaportal-dev.geosphere.at
testurl-foo-rancher.geosphere.at
keycloak.staging.hub-dev.geosphere.at
vsents2a-l1c.geosphere.at
radius3.geosphere.at
harbor.geosphere.at
datenzentrum.staging.hub-dev.geosphere.at
k8s-review.geosphere.at
hub-infra.geosphere.at
openapi.production.hub-dev.geosphere.at
s3.production.hub-dev.geosphere.at
www2-dev.geosphere.at
bitwarden.geosphere.at
www.staging.hub-dev.geosphere.at
umwanalyse.geosphere.at
eeva.geosphere.at
www.dhr.geosphere.at
vsents3.geosphere.at
vcharbor.geosphere.at
www.testseite.geosphere.at
epep725.geosphere.at
cached-dev.k8s-dev.geosphere.at
wlan.geosphere.at
www.testgitlab.geosphere.at
monitoring.hub-infra.geosphere.at
dafne.dhr.geosphere.at
www.datenzentrum-staging.geosphere.at
gw.k8s-review.geosphere.at
mattermost.geosphere.at
www.recording.geosphere.at
www.infra.hub-dev.geosphere.at
quarantine.geosphere.at
www.dhr-test.geosphere.at
ionbe1501ilom.geosphere.at
maintenance.production.hub-dev.geosphere.at
cached-dev.k8s-prod1.geosphere.at
testseite.geosphere.at
eeva-dev.geosphere.at
plausible.hub-infra.geosphere.at
s3.infra.hub-dev.geosphere.at
spf.geosphere.at
thredds.hub.staging.geosphere.at
vnessus01.geosphere.at
api.eon2.geosphere.at
vsents2-l2a.geosphere.at
pdp.geosphere.at
sentinel.geosphere.at
www.sentinel.geosphere.at
data.production.hub-dev.geosphere.at
metadata.production.hub-dev.geosphere.at
cached-dev.k8s-staging1.geosphere.at
argocd.infra.hub-dev.geosphere.at
uncached.k8s-staging1.geosphere.at
station.api.staging.hub-dev.geosphere.at
maintenance.staging.hub-dev.geosphere.at
www.test.geosphere.at
dafne-backend.dhr.geosphere.at
cpm.geosphere.at
sma1.geosphere.at
mx2mgmt.geosphere.at
vsenttest.geosphere.at
www.go.geosphere.at
radius1.geosphere.at
cert-test.k8s-review.geosphere.at
www.rsa2.geosphere.at
loki.atom.rancher.geosphere.at
ise2mgmt.geosphere.at
prometheus.k8s-staging1.geosphere.at
ckan.metadata.hub.staging.geosphere.at
s3.staging.hub-dev.geosphere.at
grafana.atom.rancher.geosphere.at
grafana.k8s-dev.geosphere.at
plone-backend.k8s-prod1.geosphere.at
dhr.geosphere.at
enexyohub.geosphere.at
cps2.geosphere.at
maintenance.geosphere.at
dataset.api.hub.staging.geosphere.at
myserver.geosphere.at
plone-backend.k8s-dev.geosphere.at
dafne.geosphere.at
www.rsa1.geosphere.at
trivy.k8s-dev.geosphere.at
thredds.staging.hub-dev.geosphere.at
station.api.production.hub-dev.geosphere.at
vsentgss.geosphere.at
cached.k8s-dev.geosphere.at
www.mattermost.geosphere.at
dependency-track.k8s-dev.geosphere.at
www.harbor-dev.k8s-dev.geosphere.at
ise3mgmt.geosphere.at
ckan.metadata.production.hub-dev.geosphere.at
rancher.geosphere.at
dafne-backend.geosphere.at
testmattermost.geosphere.at
infra.hub-dev.geosphere.at
irgendwas.geosphere.at
cps1.geosphere.at
public.production.hub-dev.geosphere.at
itop.geosphere.at
wifi.geosphere.at
hub-pews-test.geosphere.at
rocky.geosphere.at

161
history.php Normal file
View file

@ -0,0 +1,161 @@
<?php
$db = new PDO("sqlite:/var/www/data/ssl.db");
$domain = $_GET["domain"] ?? "";
if (!$domain) die("Domain missing");
$stmt = $db->prepare("
SELECT checked_at, expires
FROM history
WHERE domain = :d
ORDER BY checked_at ASC
");
$stmt->execute([":d" => $domain]);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$labels = [];
$values = [];
foreach ($data as $row) {
$labels[] = date("Y-m-d H:i", $row["checked_at"]);
$values[] = round(($row["expires"] - $row["checked_at"]) / 86400, 1);
}
?>
<!DOCTYPE html>
<html>
<head>
<title>SSL History <?= htmlspecialchars($domain) ?></title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--bg: #f4f4f4;
--text: #000;
--card: #fff;
--border: #ddd;
--header: #222;
--link: #0066cc;
}
body.dark {
--bg: #121212;
--text: #e0e0e0;
--card: #1e1e1e;
--border: #333;
--header: #000;
--link: #4ea3ff;
}
body {
font-family: Arial;
background: var(--bg);
color: var(--text);
}
table {
border-collapse: collapse;
width: 80%;
margin: auto;
background: var(--card);
}
th, td {
padding: 10px;
border: 1px solid var(--border);
text-align: center;
}
th {
background: var(--header);
color: #fff;
}
a {
color: var(--link);
}
.ok { color: #4caf50; font-weight: bold; }
.warn { color: #ff9800; font-weight: bold; }
.critical { color: #f44336; font-weight: bold; }
.toggle {
position: fixed;
top: 15px;
right: 15px;
cursor: pointer;
font-size: 20px;
}
body { font-family: Arial; background:#f4f4f4; text-align:center }
canvas { background:#fff; padding:20px; }
</style>
</head>
<body>
<div class="toggle" onclick="toggleDark()">🌙</div>
<script>
function applyTheme() {
const dark = localStorage.getItem("darkmode") === "1";
document.body.classList.toggle("dark", dark);
document.querySelector(".toggle").textContent = dark ? "☀️" : "🌙";
}
function toggleDark() {
localStorage.setItem(
"darkmode",
localStorage.getItem("darkmode") === "1" ? "0" : "1"
);
applyTheme();
}
applyTheme();
</script>
<h2>Certificate History: <?= htmlspecialchars($domain) ?></h2>
<canvas id="chart" width="900" height="400"></canvas>
<script>
new Chart(document.getElementById('chart'), {
type: 'line',
data: {
labels: <?= json_encode($labels) ?>,
datasets: [{
label: 'Days until expiration',
data: <?= json_encode($values) ?>,
borderColor: getComputedStyle(document.body)
.getPropertyValue('--link'),
backgroundColor: 'transparent',
tension: 0.1
}]
},
options: {
scales: {
x: { ticks: { color: getComputedStyle(document.body).color } },
y: { ticks: { color: getComputedStyle(document.body).color } }
},
plugins: {
legend: {
labels: {
color: getComputedStyle(document.body).color
}
}
}
}
options: {
scales: {
y: {
title: { display: true, text: 'Days Remaining' }
},
x: {
title: { display: true, text: 'Check Time' }
}
}
}
});
</script>
<p><a href="/"> Back</a></p>
</body>
</html>

54
index.php Normal file
View file

@ -0,0 +1,54 @@
<?php
$db = new PDO("sqlite:/var/www/data/ssl.db");
$rows = $db->query("SELECT * FROM certs ORDER BY expires ASC")->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html>
<head>
<title>SSL Monitor</title>
<style>
body { font-family: Arial; background:#f4f4f4 }
table { border-collapse:collapse; width:80%; margin:auto; background:#fff }
th,td { padding:10px; border:1px solid #ddd; text-align:center }
th { background:#222; color:#fff }
.ok { color:green; font-weight:bold }
.warn { color:orange; font-weight:bold }
.critical { color:red; font-weight:bold }
</style>
</head>
<body>
<h2 style="text-align:center">SSL Certificate Monitor</h2>
<table>
<tr>
<th>Domain</th>
<th>Expires</th>
<th>Days Left</th>
<th>Status</th>
<th>Last Check</th>
</tr>
<?php foreach ($rows as $r):
if (!$r["expires"]) {
echo "<tr><td>{$r['domain']}</td><td colspan=4 class='critical'>{$r['error']}</td></tr>";
continue;
}
$days = floor(($r["expires"] - time()) / 86400);
if ($days > 30) $c="ok";
elseif ($days > 7) $c="warn";
else $c="critical";
?>
<tr>
<td><?= htmlspecialchars($r["domain"]) ?></td>
<td><?= date("Y-m-d H:i:s", $r["expires"]) ?></td>
<td><?= $days ?></td>
<td class="<?= $c ?>"><?= strtoupper($c) ?></td>
<td><?= date("Y-m-d H:i:s", $r["checked_at"]) ?></td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>