A lightweight self-hosted service dashboard. Automatically discovers HTTP and HTTPS services running on your host by probing listening ports for HTML responses — no config files, no manual service registration.
-
scan.shruns on the host via cron. It:- Reads all listening TCP ports via
ss -tlnp - Probes each port: tries HTTPS first, then HTTP — checks if the response body contains
<html - If HTML is found → fetches the page title and favicon, writes a cache entry
- Also probes the host's LAN IP (
host_ipin config) — if reachable, a 🌐 link appears on the tile - Non-HTTP ports are looked up via Python's
socket.getservbyport()for a service name - Writes
last_scan.txtwith the timestamp of the last run
- Reads all listening TCP ports via
-
index.phprenders the dashboard:- Tiles — only ports where scan.sh confirmed an HTTP/HTTPS response
- Other Listening Ports — everything else, with service name where known
- Auto-refresh via configurable interval (pure page reload, no server-side scripts)
- Last scan timestamp shown in the header
- PHP (served by any web server, e.g. Caddy, nginx, Apache)
curl,python3,ss(iproute2) on the host- The web server can run inside a container —
scan.shmust run on the host
Copy config.ini.example to config.ini and fill in your values:
[caddydash]
host_ip = 192.168.1.55
ca_cert = /path/to/certs/cert.pemhost_ip— LAN IP of the host, used for network-reachability probes and the 🌐 linkca_cert— optional CA certificate for verifying HTTPS on the LAN IP (local probes always use-k)
Run scan.sh on the host every minute (or every 30 seconds with two entries):
* * * * * /home/user/CITADEL/scan.sh
* * * * * sleep 30 && /home/user/CITADEL/scan.sh
Tip: Run
sudo scan.shoccasionally outside of cron to get full process names for all ports. Without sudo,sscan only show process names for sockets owned by the current user — with sudo you'll see everything (cloudflared, tailscaled, etc.) in the Other Listening Ports table.
Icons are fetched automatically from each service's favicon during the first scan. You can also place icons manually in icons/ named by port number:
icons/3306.png ← shown for port 3306
icons/51820.svg ← shown for port 51820
Supported formats: png, svg, webp, gif, ico
scan.sh writes one JSON file per discovered HTTP/HTTPS service to cache/:
{
"title": "Grafana",
"icon": "3000.png",
"scheme": "https",
"network_ip": "192.168.1.55"
}To force a re-scan of a service (e.g. after a title change), delete its cache file:
rm cache/3000.jsonCITADEL/
├── scan.sh # cron script — port discovery + HTTP probing + icon fetching
├── index.php # dashboard UI
├── config.php # reads config.ini, exposes paths
├── config.ini # your local config (not committed)
├── config.ini.example # template
├── cache/ # one JSON per discovered HTTP/HTTPS service
├── icons/ # favicons (auto-fetched or manually placed)
├── last_scan.txt # timestamp of last scan.sh run
└── ss.json # raw port list from last scan
