mirror of
https://github.com/itme-brain/nixos.git
synced 2026-03-23 16:29:42 -04:00
Compare commits
13 commits
887dcaf16f
...
ac95d1c23d
| Author | SHA1 | Date | |
|---|---|---|---|
| ac95d1c23d | |||
| 87687d6170 | |||
| 247b8e2066 | |||
| 95f2454465 | |||
| 60d4e53a6f | |||
| b36c53fdea | |||
| a85f993041 | |||
| 6ded432df0 | |||
| ceed49531b | |||
| 071f0fdca0 | |||
| 1feef552da | |||
| 1cfda9c67b | |||
| fbbdba1e4b |
5 changed files with 196 additions and 15 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
{ config, lib, modulesPath, ... }:
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -17,6 +17,14 @@
|
||||||
|
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
# Enable VAAPI for hardware video acceleration
|
||||||
|
hardware.graphics = {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = with pkgs; [
|
||||||
|
intel-vaapi-driver # i965 driver for Haswell
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
|
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@
|
||||||
modules.system = {
|
modules.system = {
|
||||||
nginx.enable = true;
|
nginx.enable = true;
|
||||||
forgejo.enable = true;
|
forgejo.enable = true;
|
||||||
frigate.enable = false;
|
frigate.enable = true;
|
||||||
immich.enable = true;
|
immich.enable = true;
|
||||||
bitcoin = {
|
# bitcoin = {
|
||||||
enable = true;
|
# enable = true;
|
||||||
electrum.enable = true;
|
# electrum.enable = true;
|
||||||
clightning.enable = true;
|
# clightning.enable = true;
|
||||||
};
|
# };
|
||||||
|
|
||||||
backup = {
|
backup = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -120,6 +120,19 @@
|
||||||
firewall = {
|
firewall = {
|
||||||
enable = true;
|
enable = true;
|
||||||
allowedTCPPorts = [ 22 ];
|
allowedTCPPorts = [ 22 ];
|
||||||
|
allowedUDPPorts = [ 53 67 ]; # DNS + DHCP
|
||||||
|
extraCommands = ''
|
||||||
|
# Block specific camera MACs from forwarding (instant DROP, no timeouts)
|
||||||
|
# Add each camera MAC here as you set them up
|
||||||
|
iptables -A FORWARD -m mac --mac-source 00:1f:54:c2:d1:b1 -j DROP # parking_lot
|
||||||
|
iptables -A FORWARD -m mac --mac-source 00:1f:54:b2:9b:1d -j DROP # living_room/kitchen
|
||||||
|
iptables -A FORWARD -m mac --mac-source 00:1f:54:a9:81:d1 -j DROP # doorbell
|
||||||
|
'';
|
||||||
|
extraStopCommands = ''
|
||||||
|
iptables -D FORWARD -m mac --mac-source 00:1f:54:c2:d1:b1 -j DROP || true
|
||||||
|
iptables -D FORWARD -m mac --mac-source 00:1f:54:b2:9b:1d -j DROP || true
|
||||||
|
iptables -D FORWARD -m mac --mac-source 00:1f:54:a9:81:d1 -j DROP || true
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -143,11 +156,15 @@
|
||||||
interface = "enp2s0f1";
|
interface = "enp2s0f1";
|
||||||
bind-interfaces = true;
|
bind-interfaces = true;
|
||||||
dhcp-range = "192.168.1.100,192.168.1.200,24h";
|
dhcp-range = "192.168.1.100,192.168.1.200,24h";
|
||||||
# No gateway option = cameras can't route to internet
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
# Static DHCP reservations for cameras
|
||||||
|
dhcp-host = [
|
||||||
|
"00:1f:54:c2:d1:b1,192.168.1.194,parking_lot"
|
||||||
|
"00:1f:54:b2:9b:1d,192.168.1.147,living_room_kitchen"
|
||||||
|
"00:1f:54:a9:81:d1,192.168.1.167,doorbell"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
services.fail2ban = {
|
services.fail2ban = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ let
|
||||||
# Convert absolute paths to relative for tar, preserving structure
|
# Convert absolute paths to relative for tar, preserving structure
|
||||||
# e.g., /var/lib/forgejo -> var/lib/forgejo
|
# e.g., /var/lib/forgejo -> var/lib/forgejo
|
||||||
tarPaths = map (p: removePrefix "/" p) cfg.paths;
|
tarPaths = map (p: removePrefix "/" p) cfg.paths;
|
||||||
|
excludeArgs = concatMapStrings (e: "--exclude='${e}' ") cfg.exclude;
|
||||||
|
|
||||||
backupScript = pkgs.writeShellScript "backup" ''
|
backupScript = pkgs.writeShellScript "backup" ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -22,7 +23,7 @@ let
|
||||||
echo "Paths: ${concatStringsSep " " cfg.paths}"
|
echo "Paths: ${concatStringsSep " " cfg.paths}"
|
||||||
|
|
||||||
export PATH="${pkgs.age-plugin-yubikey}/bin:$PATH"
|
export PATH="${pkgs.age-plugin-yubikey}/bin:$PATH"
|
||||||
${pkgs.gnutar}/bin/tar -C / -cf - ${concatStringsSep " " tarPaths} | \
|
${pkgs.gnutar}/bin/tar -C / ${excludeArgs}-cf - ${concatStringsSep " " tarPaths} | \
|
||||||
${pkgs.age}/bin/age ${recipientArgs} -o "$TEMP_DIR/$BACKUP_NAME"
|
${pkgs.age}/bin/age ${recipientArgs} -o "$TEMP_DIR/$BACKUP_NAME"
|
||||||
|
|
||||||
${pkgs.rclone}/bin/rclone --config /root/.config/rclone/rclone.conf copy "$TEMP_DIR/$BACKUP_NAME" "${cfg.destination}"
|
${pkgs.rclone}/bin/rclone --config /root/.config/rclone/rclone.conf copy "$TEMP_DIR/$BACKUP_NAME" "${cfg.destination}"
|
||||||
|
|
@ -49,6 +50,12 @@ in
|
||||||
description = "Absolute paths to include in backup (structure preserved)";
|
description = "Absolute paths to include in backup (structure preserved)";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exclude = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Patterns to exclude (passed to tar --exclude)";
|
||||||
|
};
|
||||||
|
|
||||||
recipients = mkOption {
|
recipients = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [];
|
default = [];
|
||||||
|
|
|
||||||
119
src/system/modules/frigate/README.md
Normal file
119
src/system/modules/frigate/README.md
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# Frigate Camera Setup
|
||||||
|
|
||||||
|
## Camera Models
|
||||||
|
|
||||||
|
| Camera | Model | MAC | IP |
|
||||||
|
|--------|-------|-----|-----|
|
||||||
|
| parking_lot | W461ASC | 00:1f:54:c2:d1:b1 | 192.168.1.194 |
|
||||||
|
| doorbell | B463AJ | 00:1f:54:a9:81:d1 | 192.168.1.167 |
|
||||||
|
| living_room | W463AQ (ch1) | 00:1f:54:b2:9b:1d | 192.168.1.147 |
|
||||||
|
| kitchen | W463AQ (ch2) | 00:1f:54:b2:9b:1d | 192.168.1.147 |
|
||||||
|
| porch | SL300 | | | |
|
||||||
|
|
||||||
|
## Network Architecture
|
||||||
|
|
||||||
|
- Camera network: 192.168.1.0/24 (isolated, no internet)
|
||||||
|
- Server NIC: enp2s0f1 @ 192.168.1.1
|
||||||
|
- WiFi AP: TP-Link RE315 @ 192.168.1.254
|
||||||
|
- DHCP range: 192.168.1.100-200
|
||||||
|
|
||||||
|
## RTSP URL Format
|
||||||
|
|
||||||
|
```
|
||||||
|
rtsp://admin:ocu?u3Su@<IP>/cam/realmonitor?channel=<CH>&subtype=0
|
||||||
|
```
|
||||||
|
|
||||||
|
- channel=1 for single-camera devices
|
||||||
|
- channel=1,2 for dual-camera devices (W463AQ)
|
||||||
|
- subtype=0 for main stream, subtype=1 for sub stream
|
||||||
|
|
||||||
|
## Camera Reset Procedures
|
||||||
|
|
||||||
|
### W461ASC (parking_lot)
|
||||||
|
1. Keep camera powered on
|
||||||
|
2. Reset button is on the back of the camera
|
||||||
|
3. Press and hold reset button for 30-60 seconds until chime sounds
|
||||||
|
|
||||||
|
### B463AJ (doorbell)
|
||||||
|
1. Remove doorbell from mount
|
||||||
|
2. Locate reset button on the back
|
||||||
|
3. Press and hold until you hear chime reset sound
|
||||||
|
4. Reconnect via Lorex app as new device
|
||||||
|
|
||||||
|
### W463AQ (living_room/kitchen)
|
||||||
|
1. Keep camera powered on
|
||||||
|
2. Rotate the lens upwards to reveal hidden reset button
|
||||||
|
3. Press and hold reset button until you hear audio prompt
|
||||||
|
4. Flashing green Smart Security Lighting confirms reset
|
||||||
|
5. Solid green = not fully reset, repeat if needed
|
||||||
|
|
||||||
|
### SL300 (porch)
|
||||||
|
1. Keep camera powered on
|
||||||
|
2. Tilt camera lens upwards to reveal reset/microSD card cover
|
||||||
|
3. Remove the cover
|
||||||
|
4. Press and hold reset button until audio prompt
|
||||||
|
5. Replace cover quickly
|
||||||
|
6. Wait for green LED flash + audio confirmation
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
1. Temporarily enable internet for camera network:
|
||||||
|
```bash
|
||||||
|
sudo iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o enp2s0f0 -j MASQUERADE
|
||||||
|
sudo sysctl -w net.ipv4.ip_forward=1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Connect camera to "cams" WiFi network
|
||||||
|
|
||||||
|
3. Use Lorex app to configure camera (requires cloud - CCP middleman)
|
||||||
|
|
||||||
|
4. Get camera MAC from DHCP leases:
|
||||||
|
```bash
|
||||||
|
cat /var/lib/dnsmasq/dnsmasq.leases
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Add DHCP reservation in `system.nix`:
|
||||||
|
```nix
|
||||||
|
dhcp-host = [
|
||||||
|
"aa:bb:cc:dd:ee:ff,192.168.1.XXX,camera_name"
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Add MAC to firewall block list in `system.nix`:
|
||||||
|
```nix
|
||||||
|
iptables -A FORWARD -m mac --mac-source aa:bb:cc:dd:ee:ff -j DROP
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Update camera IP in `frigate/default.nix` and enable
|
||||||
|
|
||||||
|
8. Deploy and disable internet:
|
||||||
|
```bash
|
||||||
|
nixos-rebuild switch --flake .#server --target-host server
|
||||||
|
sudo iptables -t nat -D POSTROUTING -s 192.168.1.0/24 -o enp2s0f0 -j MASQUERADE
|
||||||
|
sudo sysctl -w net.ipv4.ip_forward=0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Storage
|
||||||
|
|
||||||
|
Frigate data is stored on /data to avoid filling root partition:
|
||||||
|
|
||||||
|
| Path | Bind Mount | Contents |
|
||||||
|
|------|------------|----------|
|
||||||
|
| /var/lib/frigate | /data/frigate/lib | Database, recordings, clips |
|
||||||
|
| /var/cache/frigate | /data/frigate/cache | Temporary cache |
|
||||||
|
| /var/cache/nginx/frigate | /data/frigate/nginx-cache | API response cache |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Lorex cameras are cloud-only for configuration (no local web UI responds)
|
||||||
|
- RTSP works locally without internet
|
||||||
|
- Cameras phone home aggressively when internet is available - keep isolated
|
||||||
|
- Haswell CPU cannot hardware decode HEVC - using CPU decode
|
||||||
|
- Consider T400 GPU for hardware acceleration if scaling to more cameras
|
||||||
|
|
||||||
|
## Port Scan Results (W461ASC)
|
||||||
|
|
||||||
|
- 80/tcp - HTTP (non-responsive, proprietary)
|
||||||
|
- 554/tcp - RTSP (working)
|
||||||
|
- 8086/tcp - Proprietary
|
||||||
|
- 35000/tcp - Proprietary
|
||||||
|
|
@ -16,38 +16,46 @@ in
|
||||||
services.frigate = {
|
services.frigate = {
|
||||||
enable = true;
|
enable = true;
|
||||||
hostname = "frigate.${domain}";
|
hostname = "frigate.${domain}";
|
||||||
|
# vaapiDriver = "i965"; # Haswell only supports H.264, not HEVC
|
||||||
settings = {
|
settings = {
|
||||||
mqtt.enabled = false;
|
mqtt.enabled = false;
|
||||||
|
# ffmpeg.hwaccel_args = "preset-vaapi"; # Disabled - camera uses HEVC which Haswell can't decode
|
||||||
|
record.enabled = true;
|
||||||
cameras = {
|
cameras = {
|
||||||
doorbell = {
|
doorbell = {
|
||||||
|
enabled = true;
|
||||||
detect.enabled = false;
|
detect.enabled = false;
|
||||||
ffmpeg.inputs = [{
|
ffmpeg.inputs = [{
|
||||||
path = "rtsp://admin:ocu?u3Su@192.168.0.134/cam/realmonitor?channel=1&subtype=0";
|
path = "rtsp://admin:ocu?u3Su@192.168.1.167/cam/realmonitor?channel=1&subtype=0";
|
||||||
roles = [ "record" ];
|
roles = [ "record" ];
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
living_room = {
|
living_room = {
|
||||||
|
enabled = false;
|
||||||
detect.enabled = false;
|
detect.enabled = false;
|
||||||
ffmpeg.inputs = [{
|
ffmpeg.inputs = [{
|
||||||
path = "rtsp://admin:ocu?u3Su@192.168.0.181/cam/realmonitor?channel=1&subtype=0";
|
path = "rtsp://admin:ocu?u3Su@192.168.1.147/cam/realmonitor?channel=1&subtype=0";
|
||||||
roles = [ "record" ];
|
roles = [ "record" ];
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
kitchen = {
|
kitchen = {
|
||||||
|
enabled = false;
|
||||||
detect.enabled = false;
|
detect.enabled = false;
|
||||||
ffmpeg.inputs = [{
|
ffmpeg.inputs = [{
|
||||||
path = "rtsp://admin:ocu?u3Su@192.168.0.181/cam/realmonitor?channel=2&subtype=0";
|
path = "rtsp://admin:ocu?u3Su@192.168.1.147/cam/realmonitor?channel=2&subtype=0";
|
||||||
roles = [ "record" ];
|
roles = [ "record" ];
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
parking_lot = {
|
parking_lot = {
|
||||||
|
enabled = true;
|
||||||
detect.enabled = false;
|
detect.enabled = false;
|
||||||
ffmpeg.inputs = [{
|
ffmpeg.inputs = [{
|
||||||
path = "rtsp://admin:ocu?u3Su@192.168.0.59/cam/realmonitor?channel=1&subtype=0";
|
path = "rtsp://admin:ocu?u3Su@192.168.1.194/cam/realmonitor?channel=1&subtype=0";
|
||||||
roles = [ "record" ];
|
roles = [ "record" ];
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
porch = {
|
porch = {
|
||||||
|
enabled = false;
|
||||||
detect.enabled = false;
|
detect.enabled = false;
|
||||||
ffmpeg.inputs = [{
|
ffmpeg.inputs = [{
|
||||||
path = "rtsp://admin:ocu?u3Su@192.168.0.43/cam/realmonitor?channel=1&subtype=0";
|
path = "rtsp://admin:ocu?u3Su@192.168.0.43/cam/realmonitor?channel=1&subtype=0";
|
||||||
|
|
@ -64,5 +72,27 @@ in
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Bind mount caches into the 3TB frigate LVM volume
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/frigate/cache 0750 frigate frigate -"
|
||||||
|
"d /var/lib/frigate/nginx-cache 0750 nginx nginx -"
|
||||||
|
];
|
||||||
|
|
||||||
|
fileSystems."/var/cache/frigate" = {
|
||||||
|
device = "/var/lib/frigate/cache";
|
||||||
|
options = [ "bind" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/var/cache/nginx/frigate" = {
|
||||||
|
device = "/var/lib/frigate/nginx-cache";
|
||||||
|
options = [ "bind" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Backup recordings/database, exclude caches
|
||||||
|
modules.system.backup = {
|
||||||
|
paths = [ "/var/lib/frigate" ];
|
||||||
|
exclude = [ "*/cache" "*/nginx-cache" ];
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue