Files
panoptikon/README.md

310 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Panoptikon Watch the world from NixOS
A NixOS module for monitoring website content and command output changes.
![](./panoptikon.jpg)
## Overview
Panoptikon is a flexible, secure, and modular system for monitoring changes to websites, API endpoints, and command outputs. It runs scripts at configurable intervals, detects changes by comparing outputs, and reports differences to various destinations.
**Perfect for:**
- Monitoring service status pages (GitHub, cloud providers)
- Tracking website content changes (blogs, news)
- Watching API endpoints (cryptocurrency prices, weather)
- System metrics monitoring (disk space, load, processes)
- Security canaries and breach detection
## Features
- **Flexible Watchers**: Execute any script or command; monitor HTTP endpoints with built-in helpers
- **Custom Frequencies**: Use systemd timer syntax for any schedule (*/5 minutes, daily, weekly, etc.)
- **Multiple Reporters**: Notify via IRC, Telegram, Matrix, email, desktop notifications, wall, or custom scripts
- **Secret Support**: Securely pass credentials using `LoadCredential=` without exposing them in the Nix store
- **Stateful Tracking**: Automatic diffing; only reports actual changes
- **Modular Design**: Built-in helpers for HTML, JSON, and plain text; easy to create custom ones
- **Security Hardened**: Dedicated system user, filesystem isolation, and process protection
- **systemd Native**: Full integration with systemd for reliable scheduling and logging
## Quick Start
1. **Enable Panoptikon** in your NixOS configuration:
```nix
{ config, pkgs, ... }:
{
services.panoptikon.enable = true;
}
```
2. **Add a simple watcher**:
```nix
services.panoptikon.watchers = {
example = {
script = pkgs.panoptikonWatchers.plain "https://example.com";
frequency = "hourly";
reporters = [ (pkgs.panoptikonReporters.wall { }) ];
};
};
```
3. **Deploy and monitor**:
```bash
# Check status
sudo systemctl status panoptikon-example
# View logs
sudo journalctl -u panoptikon-example -f
# Trigger manually
sudo systemctl start panoptikon-example
```
## How It Works
```
┌─────────────┐
│ systemd │
│ timer │─── triggers ───┐
└─────────────┘ │
┌─────────────────────┐
│ Panoptikon Service │
│ (oneshot) │
└─────────────────────┘
┌─────────┴─────────┐
▼ │
┌──────────────────┐ │
│ Run watcher │ │
│ script → current │ │
└──────────────────┘ │
│ │
▼ │
┌──────────────────┐ │
│ Compare with │ │
│ .old state │ │
└──────────────────┘ │
│ │
diff? ─┼─── Yes ────────┤
│ No │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Exit quietly │ │ Pipe diff to │
└──────────────┘ │ reporters │
└──────┬───────┘
┌────────────┴────────────┐
▼ ▼
[reporter 1] [reporter 2]
│ │
└──────────┬───────────────┘
[Notifications sent]
┌──────────────────┐
│ Rotate state: │
│ current → old │
└──────────────────┘
```
**Key Points:**
- Each watcher runs as its own systemd service with a dedicated timer
- Output is saved to `/var/lib/panoptikon/<watcher-name>` and `<watcher-name>.old`
- Only diffs are sent to reporters (full output never transmitted)
- Reporters run in a clean, empty `/run` directory for isolation
- State persists across reboots
## Configuration Reference
### Watcher Options
| Option | Type | Description |
|--------|------|-------------|
| `script` | path | **Required.** Executable whose stdout will be monitored. |
| `frequency` | string | systemd.time(7) timer expression. Default: `"daily"` |
| `reporters` | list of paths | **Required.** Scripts that receive the diff via stdin. |
| `loadCredential` | list of strings | Credentials to pass from systemd (`LoadCredential=`). |
### Timer Syntax
Common patterns:
| Pattern | Meaning |
|---------|---------|
| `*:0/5` | Every 5 minutes |
| `*:0/15` | Every 15 minutes |
| `hourly` | At minute 0 of every hour |
| `daily` | Once per day at midnight |
| `*-*-1 0:0:0` | First day of month |
| `Mon *-*-* 0:0:0` | Every Monday |
| `Sat,Sun *-*-* 0:0:0` | Weekends |
See: `man systemd.time`
### Environment Variables
- `PANOPTIKON_WATCHER` - Name of the watcher (available to both watchers and reporters)
## Built-in Helpers
The overlay provides convenient watcher and reporter constructors:
### Watcher Helpers
```nix
# Fetch raw content
pkgs.panoptikonWatchers.plain "https://example.com"
# Convert HTML to plain text
pkgs.panoptikonWatchers.html "https://example.com"
# Extract specific HTML elements using CSS selector
pkgs.panoptikonWatchers.htmlSelector "#news h2" "https://example.com"
# Process JSON with jq
pkgs.panoptikonWatchers.json { jqScript = ".data[] | .value" } "https://api.example.com"
```
### Reporter Helpers
```nix
# Send to wall (broadcast to all logged-in users)
pkgs.panoptikonReporters.wall { }
# Send email
pkgs.panoptikonReporters.mail {
recipient = "admin@example.org";
subjectPrefix = "[Alert]";
}
# Telegram Bot API
pkgs.panoptikonReporters.telegram {
chatId = "123456";
tokenPath = "/run/keys/telegram-token"; # Use LoadCredential=
messagePrefix = "Change detected: ";
}
# Matrix (via REST API)
pkgs.panoptikonReporters.matrix {
homeserver = "https://matrix.org";
roomId = "!roomid:matrix.org";
tokenPath = "/run/keys/matrix-token";
}
# IRC (simple netcat-based)
pkgs.panoptikonReporters.irc {
target = "#channel";
server = "irc.libera.chat";
port = "6667";
nick = "panoptikon-bot";
}
# kpaste + ircsink (for retiolum)
pkgs.panoptikonReporters.kpaste-irc {
target = "#nixos";
server = "irc.r";
retiolumLink = true; # Generate retiolum link
}
```
**Important:** For security, always use `LoadCredential=` for tokens instead of embedding them in your Nix store.
## Advanced Examples
### Monitor Bitcoin Price with Telegram Alert
See [examples/bitcoin.nix](./examples/bitcoin.nix)
### Website Content Change with Email Notification
See [examples/nixos.nix](./examples/nixos.nix)
### System Metrics with Console Notifications
See [examples/system.nix](./examples/system.nix)
### Custom Script with Multiple Reporters
See [examples/simple.nix](./examples/simple.nix) for more.
## Testing
### Run a VM with Example Configurations
```bash
nix run .#panoptikon-vm
```
This builds and boots a NixOS VM with all example watchers pre-configured.
### Inside the VM
```bash
# List all panoptikon services
systemctl list-units "panoptikon-*"
# Check Bitcoin watcher status
systemctl status panoptikon-bitcoin-price
# Follow its logs
journalctl -u panoptikon-bitcoin-price -f
# Force a run (useful for testing)
systemctl start panoptikon-bitcoin-price
# Check state directory
ls -la /var/lib/panoptikon/
```
### Manual Script Testing
Run the watcher script as the panoptikon user to see its output:
```bash
sudo -u panoptikon /nix/store/<hash>-watch-bitcoin
```
Check that it produces clean output without errors.
## FAQ
**Q: Can Panoptikon monitor FTP or SSH?**
A: Yes! Write a custom watcher script that uses `curl` (for SFTP), `ssh`, or any CLI tool. Example:
```nix
script = pkgs.writers.writeDash "ssh-check" ''
${pkgs.openssh}/bin/ssh user@host "uptime"
'';
```
**Q: What happens if the watcher script fails?**
A: The service will be marked as failed and will restart on next timer activation (unless configured otherwise). The error is logged to the systemd journal. Reporters are skipped.
**Q: Can I run multiple reporters for the same event?**
A: Yes! Reporters are executed sequentially. If one fails, others still run (errors are suppressed with `|| :`).
**Q: How do I handle JSON pretty-printing?**
A: Use jq:
```nix
script = pkgs.panoptikonWatchers.json { jqScript = "." } "https://api.example.com/data";
```
Or in a custom script:
```bash
curl -s ... | jq -S . # -S sorts keys
```
**Q: Can I send rich formatting (HTML, Markdown) to reporters?**
A: Yes! Reporters receive the raw diff. For IRC, use colors sparingly. For Telegram/Matrix, you can send Markdown or HTML by constructing appropriate payloads in custom reporters.