# AGENTS.md This file provides guidance for AI coding assistants working on the Panoptikon project. ## Project Overview Panoptikon is a NixOS module that monitors website content and command output changes. It periodically runs scripts, compares outputs, and reports changes to various destinations. **Key Components:** - `flake.nix` - Flake definition with modules, overlays, and VM app - `nix/module.nix` - Main NixOS module implementation - `nix/overlay.nix` - Overlay providing `panoptikonWatchers` and `panoptikonReporters` helpers - `examples/` - Example configurations - `README.md` - User-facing documentation ## Coding Conventions ### Nix Code Style - Use Nix expression language (not NixOS modules have `lib` available) - Prefer `lib.attrsets.mapAttrs'` with `lib.nameValuePair` for transforming attrsets - Use `lib.strings.concatMapStringsSep` for joining strings with separators - Maintain consistent formatting (2-space indentation) ### Systemd Configuration - Services run as dedicated `panoptikon` system user - Use `RuntimeDirectory` for temporary per-run isolation - Use `ReadWritePaths` to restrict filesystem access - Enable hardening flags: `ProtectSystem`, `ProtectHome`, `PrivateTmp`, `NoNewPrivileges` ### State Management - State files stored in `/var/lib/panoptikon/` - Each watcher uses `${watcherName}` and `${watcherName}.old` - Scripts ensure `.old` exists before diff - Rotate state after comparing ### Security Practices - Never log sensitive data (tokens, credentials) - Use `set -efu` in shell scripts (avoid `-x`) - Use `LoadCredential=` for passing secrets - Validate file existence before reading tokens - Escape shell arguments with `lib.escapeShellArg` ## Testing ### Manual Testing 1. Start the VM with all examples: ```bash nix run .#panoptikon-vm ``` 2. Inside the VM, check service status: ```bash systemctl status panoptikon-* journalctl -u panoptikon-bitcoin-price -f ``` 3. Trigger a manual run: ```bash systemctl start panoptikon-bitcoin-price ``` ### Linting and Formatting ```bash # Check Nix syntax nix flake check # Format Nix code nix fmt ``` ## Debugging ### Service Issues 1. Check if service is enabled and running: ```bash systemctl status panoptikon- ``` 2. View logs: ```bash journalctl -u panoptikon- -f ``` 3. Test scripts manually (as panoptikon user): ```bash sudo -u panoptikon /nix/store/...-script ``` ### State File Issues - Check state directory: `/var/lib/panoptikon/` - State files persist across reboots - Delete state file to force change detection on next run ### Timer Issues - Timer uses `RandomizedDelaySec` of 1 hour to prevent thundering herd - This means services may be delayed up to 1 hour from the scheduled time - Check active timers: `systemctl list-timers "panoptikon-*"` ## Architecture Notes ### Data Flow 1. systemd timer triggers service 2. Script runs, stdout saved to `${watcherName}` 3. Diff against `${watcherName}.old` 4. If changes detected: pipe diff to each reporter 5. Rotate: current → old ### Reporter Contract - Reporters receive the diff via stdin - Can access watcher name via `$PANOPTIKON_WATCHER` - Should handle errors gracefully (exit code ignored in main script) - Run in isolated empty `/run/panoptikon/` directory ### Watcher Contract - Must be an executable path (script or binary) - Output is stored verbatim for diffing - Should be idempotent (running twice produces same output) - Avoid side effects; output only relevant data ## Extension Points ### Adding New Watcher Helpers Add to `panoptikonWatchers` in `overlay.nix`: ```nix panoptikonWatchers = (prev.panoptikonWatchers or { }) // { myWatcher = args: prev.writers.writeDash "watch-my" '' ${prev.somePackage}/bin/tool --option ${lib.escapeShellArg args} ''; }; ``` ### Adding New Reporter Helpers Similar pattern in `panoptikonReporters`: ```nix panoptikonReporters = (prev.panoptikonReporters or { }) // { myReporter = { requiredArg, optionalArg ? "default" }: prev.writers.writeDash "report-my" '' cat | ${prev.tool}/bin/send --to ''${requiredArg} ''; }; ``` ### Common Dependencies - `writers.writeDash` - Write shell scripts - `curl` - HTTP requests - `jq` - JSON processing - `htmlq` - HTML selectors - `diffutils` - Compare outputs ## Known Limitations - Large outputs may cause memory pressure in diff - No built-in rate limiting for external APIs - Reporters cannot access the original script output, only the diff - State stored in plain files (no compression/rotation) ## Future Considerations - Adding watchdog timeouts - Metrics collection (execution time, success rate) - Backoff strategies for failing watchers - Support for structured output formats (JSON events)