docs: add Netdata module design spec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
abd0aaa6f2
commit
d6a4dbeb45
2 changed files with 181 additions and 0 deletions
86
docs/superpowers/specs/2026-03-21-netdata-design.md
Normal file
86
docs/superpowers/specs/2026-03-21-netdata-design.md
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Netdata Module Design
|
||||||
|
|
||||||
|
**Date:** 2026-03-21
|
||||||
|
**Status:** Draft
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add Netdata real-time monitoring to the NixOS VPS, accessible only from the Headscale/Tailscale network.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Netdata monitoring service running on the VPS
|
||||||
|
- Accessible via nginx reverse proxy with automatic HTTPS
|
||||||
|
- Restricted to Tailscale network only (100.64.0.0/10) and localhost
|
||||||
|
- Direct access on Tailscale IP (port 19999) also available
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Module: `modules/netdata.nix`
|
||||||
|
|
||||||
|
Create a new module following the existing pattern.
|
||||||
|
|
||||||
|
**Header comment block:**
|
||||||
|
```nix
|
||||||
|
# Netdata Module
|
||||||
|
# Provides: Real-time system monitoring dashboard
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# myModules.netdata = {
|
||||||
|
# enable = true;
|
||||||
|
# domain = "netdata.example.com";
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# Access is restricted to Tailscale network only via nginx internalOnly.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `enable` - Enable Netdata monitoring
|
||||||
|
- `domain` - Domain for nginx reverse proxy (e.g., `netdata.ashisgreat.xyz`)
|
||||||
|
- `port` - Internal port (default: 19999), description: "Internal port for Netdata to listen on"
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Enable `services.netdata` with default settings
|
||||||
|
- Bind Netdata to `0.0.0.0` to allow direct Tailscale access (not just localhost)
|
||||||
|
- Register domain with `myModules.nginx.domains` using `internalOnly = true`
|
||||||
|
- Set `contentSecurityPolicy = null` - Netdata dashboard has its own CSP requirements
|
||||||
|
- No firewall changes needed (nginx handles external access, direct Tailscale access works via mesh network)
|
||||||
|
|
||||||
|
### Usage in configuration.nix
|
||||||
|
|
||||||
|
```nix
|
||||||
|
myModules.netdata = {
|
||||||
|
enable = true;
|
||||||
|
domain = "netdata.ashisgreat.xyz";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access Control
|
||||||
|
|
||||||
|
- **Via domain:** Only accessible from IPs in `100.64.0.0/10` (Tailscale) or `127.0.0.0/8` (localhost)
|
||||||
|
- **Direct Tailscale:** `http://<tailscale-ip>:19999` (Tailscale mesh handles access control)
|
||||||
|
|
||||||
|
### Backup Decision
|
||||||
|
|
||||||
|
Netdata metrics data is **not backed up**. Rationale:
|
||||||
|
- Metrics are ephemeral and regeneratable
|
||||||
|
- Historical data is downsampled over time (not critical)
|
||||||
|
- `/var/lib/netdata` excluded from backup paths
|
||||||
|
|
||||||
|
### Secrets
|
||||||
|
|
||||||
|
No SOPS secrets required. Netdata operates without authentication at the service level - access control is enforced via nginx/Tailscale network restrictions.
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
| File | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `modules/netdata.nix` | Create |
|
||||||
|
| `modules/default.nix` | Add import |
|
||||||
|
| `configuration.nix` | Enable module |
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- No public internet access - blocked at nginx level
|
||||||
|
- No authentication required at Netdata level (network-level access control)
|
||||||
|
- Automatic HTTPS via Let's Encrypt
|
||||||
95
modules/open-webui-podman.nix
Normal file
95
modules/open-webui-podman.nix
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
# OpenWebUI Podman Module
|
||||||
|
# Provides: Web interface for LLMs using official Docker image
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# myModules.open-webui-podman = {
|
||||||
|
# enable = true;
|
||||||
|
# port = 9000;
|
||||||
|
# domain = "ai.example.com";
|
||||||
|
# ollamaUrl = "http://100.64.0.1:11434";
|
||||||
|
# };
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.myModules.open-webui-podman;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.myModules.open-webui-podman = {
|
||||||
|
enable = lib.mkEnableOption "OpenWebUI for LLMs via Podman";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 9000;
|
||||||
|
description = "Port to expose OpenWebUI on localhost";
|
||||||
|
};
|
||||||
|
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "ai.example.com";
|
||||||
|
description = "Public domain name for OpenWebUI";
|
||||||
|
};
|
||||||
|
|
||||||
|
ollamaUrl = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "http://127.0.0.1:11434";
|
||||||
|
example = "http://100.64.0.1:11434";
|
||||||
|
description = "URL of the Ollama API endpoint";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# Ensure podman is enabled
|
||||||
|
myModules.podman.enable = true;
|
||||||
|
|
||||||
|
# Podman container for OpenWebUI
|
||||||
|
virtualisation.oci-containers.containers.open-webui = {
|
||||||
|
image = "ghcr.io/open-webui/open-webui:main";
|
||||||
|
ports = ["127.0.0.1:${toString cfg.port}:8080"];
|
||||||
|
environment = {
|
||||||
|
OLLAMA_API_BASE_URL = cfg.ollamaUrl;
|
||||||
|
WEBUI_URL = "https://${cfg.domain}";
|
||||||
|
};
|
||||||
|
environmentFiles = [config.sops.templates."openwebui-podman.env".path];
|
||||||
|
volumes = [
|
||||||
|
"open-webui-data:/app/backend/data"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# SOPS template for secrets
|
||||||
|
sops.templates."openwebui-podman.env" = {
|
||||||
|
content = ''
|
||||||
|
WEBUI_SECRET_KEY=${config.sops.placeholder.openwebui_secret_key}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets.openwebui_secret_key = { };
|
||||||
|
|
||||||
|
# Nginx configuration
|
||||||
|
myModules.nginx.domains.${cfg.domain} = {
|
||||||
|
port = cfg.port;
|
||||||
|
extraConfig = ''
|
||||||
|
client_max_body_size 100M;
|
||||||
|
'';
|
||||||
|
# WebSocket support for /ws/
|
||||||
|
extraLocations."/ws/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_read_timeout 86400;
|
||||||
|
proxy_send_timeout 86400;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# Relaxed CSP for OpenWeb UI
|
||||||
|
contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: https:; frame-ancestors 'self'";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue