df73fc25e62098e6670254518a2d174d8e53d213
Panoptikon – Watch the world from NixOS
A NixOS module for monitoring website content and command output changes.
Overview
Panoptikon is a generic command output and website watcher that periodically runs scripts and reports changes. It's designed to be flexible and can monitor anything from API endpoints to system metrics.
Features
- Flexible Watchers: Monitor any command output or website content
- Custom Frequencies: Run scripts at any interval using systemd.timer syntax
- Multiple Reporters: Report changes to various destinations (IRC, Telegram, Prometheus, etc.)
- Secret Support: Securely pass credentials to scripts without exposing them in the Nix store
- Stateful Tracking: Automatically tracks previous output and reports only changes
- Modular Design: Easy to extend with custom watchers and reporters
Installation
Add Panoptikon to your NixOS configuration:
{ config, pkgs, ... }:
{
imports = [
# Add the Panoptikon module
(pkgs.callPackage (builtins.fetchTarball https://github.com/kfm/panoptikon-library/archive/main.tar.gz) { }).nixosModules.default
];
# Enable Panoptikon service
services.panoptikon.enable = true;
# Configure your watchers
services.panoptikon.watchers = {
# Your watcher configurations go here
};
}
Configuration
Basic Watcher Configuration
{
services.panoptikon.enable = true;
services.panoptikon.watchers = {
# Monitor GitHub metadata
github-meta = {
script = pkgs.writers.writeDash "github-meta" '''
${pkgs.curl}/bin/curl -sSL https://api.github.com/meta | ${pkgs.jq}/bin/jq
''';
frequency = "*:0/5"; # Every 5 minutes
reporters = [
# Report changes to Telegram
(pkgs.writers.writeDash "telegram-reporter" '''
${pkgs.curl}/bin/curl -X POST https://api.telegram.org/bot''${TOKEN}/sendMessage \
-d chat_id=123456 \
-d text="$(cat)"
''')
# Also show desktop notifications
(pkgs.writers.writeDash "notify" '''
${pkgs.libnotify}/bin/notify-send "$PANOPTIKON_WATCHER has changed."
''')
];
};
# Monitor a website for specific content
nixos-updates = {
script = pkgs.panoptikon.urlSelector "#news h2" "https://nixos.org/blog/";
frequency = "daily";
reporters = [
# Report to IRC
(pkgs.panoptikon.kpaste-irc {
target = "#nixos";
server = "irc.libera.chat";
messagePrefix = "New NixOS blog post: ";
})
];
};
# Monitor a local command
disk-space = {
script = pkgs.writers.writeDash "disk-space" '''
df -h / | tail -1 | awk '{print $5 " used"}'
''';
frequency = "*:0/30"; # Every 30 minutes
reporters = [
# Log to systemd journal
(pkgs.writers.writeDash "journal-log" '''
journalctl -t panoptikon-disk-space --since "1 hour ago" | tail -5
''')
];
};
};
}
Advanced Configuration
Using Secrets
{
services.panoptikon.watchers = {
private-api = {
script = pkgs.writers.writeDash "private-api" '''
${pkgs.curl}/bin/curl -sSL \
-H "Authorization: Bearer $API_TOKEN" \
https://api.example.com/data
''';
frequency = "hourly";
loadCredential = [ "API_TOKEN" ];
reporters = [
(pkgs.writers.writeDash "secure-reporter" '''
${pkgs.curl}/bin/curl -X POST https://secure.example.com/report \
-d "$(cat)"
''')
];
};
};
}
JSON API Monitoring
{
services.panoptikon.watchers = {
crypto-prices = {
script = pkgs.panoptikon.urlJSON {
jqScript = ".[0] | { name: .name, price: .quote.USD.price }";
} "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin";
frequency = "*:0/15"; # Every 15 minutes
reporters = [
(pkgs.writers.writeDash "price-alert" '''
price=$(echo "$(cat)" | ${pkgs.jq}/bin/jq -r '.price')
if (( $(echo "$price > 60000" | bc -l) )); then
${pkgs.libnotify}/bin/notify-send "Bitcoin price: $$price"
fi
''')
];
};
};
}
Available Helper Scripts
Panoptikon provides several helper scripts in the overlay:
URL Monitoring
pkgs.panoptikon.url: Fetch and convert HTML to textpkgs.panoptikon.urlSelector: Extract specific HTML elements using CSS selectorspkgs.panoptikon.urlJSON: Fetch JSON and apply jq transformations
Reporters
pkgs.panoptikon.kpaste-irc: Report changes to IRC via kpastetarget: IRC target (channel or user)server: IRC server (default: irc.r)messagePrefix: Prefix for messages (default: "change detected: ")nick: Nick to use (default: watcher name + "-watcher")retiolumLink: Use retiolum link (default: false)
Service Management
Systemd Integration
Each watcher gets its own systemd service and timer:
# List all Panoptikon services
systemctl list-units "panoptikon-*"
# Check a specific watcher
systemctl status panoptikon-github-meta
# View logs
journalctl -u panoptikon-github-meta -f
# Trigger a manual run
systemctl start panoptikon-github-meta
Timer Configuration
Timers use systemd.timer syntax. Common examples:
*:0/5- Every 5 minutesdaily- Once per dayMon..Fri 9:00-17:00- Weekdays during business hours*:0/15- Every 15 minutesweekly- Once per week
See systemd.time(7) for full syntax.
Security Considerations
- Watchers run as the
panoptikonsystem user - Scripts are executed in
/var/lib/panoptikon - Use
loadCredentialto securely pass secrets - Scripts should be written defensively (use
set -euo pipefail)
Troubleshooting
Common Issues
- No output: Check if the script runs correctly manually
- Permission denied: Ensure the panoptikon user can access required resources
- Network issues: Add
network-online.targetas a dependency - Rate limiting: Add randomized delays using
RandomizedDelaySec
Debug Mode
Enable debug logging:
services.panoptikon.watchers.github-meta = {
# ...
script = ''
set -x # Enable debug output
${pkgs.curl}/bin/curl -sSL https://api.github.com/meta | ${pkgs.jq}/bin/jq
'';
};
Examples
Monitor Cryptocurrency Prices
{
services.panoptikon.enable = true;
services.panoptikon.watchers = {
bitcoin-price = {
script = pkgs.panoptikon.urlJSON {
jqScript = ".[0] | { name: .name, price: .quote.USD.price, change: .quote.USD.percent_change_24h }";
} "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin";
frequency = "*:0/30";
reporters = [
(pkgs.writers.writeDash "btc-alert" '''
price=$(echo "$(cat)" | ${pkgs.jq}/bin/jq -r '.price')
change=$(echo "$(cat)" | ${pkgs.jq}/bin/jq -r '.change')
${pkgs.libnotify}/bin/notify-send \
"Bitcoin: $$price ($$change%)"
''')
];
};
};
}
Monitor System Metrics
{
services.panoptikon.enable = true;
services.panoptikon.watchers = {
load-average = {
script = pkgs.writers.writeDash "load-average" '''
uptime | awk -F'load average:' '{print $2}' | awk '{print $1 " (1m), " $2 " (5m), " $3 " (15m)"}'
''';
frequency = "*:0/2"; # Every 2 minutes
reporters = [
(pkgs.writers.writeDash "load-alert" '''
load=$(echo "$(cat)" | awk '{print $1}' | tr -d ',')
if (( $(echo "$load > 2.0" | bc -l) )); then
${pkgs.libnotify}/bin/notify-send "High load: $$load"
fi
''')
];
};
};
}
Monitor NixOS Updates
{
services.panoptikon.enable = true;
services.panoptikon.watchers = {
nixos-updates = {
script = pkgs.panoptikon.url "https://nixos.org/blog/";
frequency = "daily";
reporters = [
(pkgs.writers.writeDash "update-notify" '''
${pkgs.libnotify}/bin/notify-send "NixOS blog updated: $(cat | head -5)"
''')
];
};
};
}
Development
Adding Custom Reporters
Create a new reporter in the overlay:
final: prev: {
panoptikon = prev.panoptikon // {
my-custom-reporter = {
endpoint,
authToken ? "",
...
}:
prev.writers.writeDash "my-reporter" ''
${prev.curl}/bin/curl -X POST ''${endpoint} \
-H "Authorization: Bearer ''${authToken} \
-d "$(cat)"
'';
};
};
Contributing
- Fork the repository
- Create a feature branch
- Add tests if applicable
- Submit a pull request
License
MIT License
Languages
Nix
100%
