Skip to content

CCTV Camera Setup in ArchLinux

Network setup for cameras

┌──────────────────────────┐     ┌─────────────┐
│  ArchLinux Desktop       │─────│   enp5s0    │──────── (Internet/WAN)
│  (vector@ArchLinuxR72700)│     │ (Built-in   │
└──────────────────────────┘     │  Ethernet)  │
              │                  └─────────────┘
              │                 ┌─────────────┐
              └─────────────────│    br1      │ 10.0.0.1/24
                                │  (Bridge    │ DHCP Server
                                │   Switch)   │ (No Internet Access)
                                └─────────────┘
                                │             │
                                │             │ enp10s0f3u2
                                │             │ (USB-to-Ethernet adapter)
                                │             │ (Just a port, no IP address)
                                │             │
                                │          ┌─────────┐
                                │          │Physical │
                                │          │Network  │
                                │          │Switch   │
                                │          └─────────┘
                                │             │
                                │             │
                        ┌───────┘             ├──────────────┐
                        │                     │              │
                        │                     │              │
                   ┌─────────┐              ┌─────────┐    ┌─────────┐
                   │Windows  │              │6 Network│    │ArchLinux│
                   │VM       │              │Cameras  │    │Laptop   │
                   │(Virtual)│              │         │    │         │
                   └─────────┘              └─────────┘    └─────────┘
                   DHCP Clients on 10.0.0.0/24 Network

Setting up the Linux bridge br1

We run the DHCP server on a linux bridge br1 instead of running it directly on ethernet interface connected to the physical network switch. You can think of the linux bridge as a virtual device with its own IP address, here enp10s0f3u2 becomes a port of this device.

This setup can be achieved with these files:

/etc/systemd/network/br1.network
1
2
3
4
5
6
[Match]
Name=br1

[Network]
Address=10.0.0.1/24
IPForward=yes
/etc/systemd/network/br1.netdev
1
2
3
[NetDev]
Name=br1
Kind=bridge 
/etc/systemd/network/enp10s0f3u2.network
1
2
3
4
5
[Match]
Name=enp10s0f3u2

[Network]
Bridge=br1

Setting up DHCPD

sudo pacman -S dhcpd
sudo systemctl edit dhcpd4.service

Change the ExecStart line to:

[Service]
ExecStart=
ExecStart=/usr/bin/dhcpd -4 -q -cf /etc/dhcpd.conf -pf /run/dhcpd4/dhcpd.pid br1
sudo systemctl enable systemd-networkd
sudo systemctl start systemd-networkd
sudo systemctl enable dhcpd4.service
sudo systemctl start dhcpd4.service
/etc/dhcpd.conf
# dhcpd.conf
# # Global settings
default-lease-time 600;
max-lease-time 7200;
authoritative;

# Subnet configuration
subnet 10.0.0.0 netmask 255.255.255.0 {
    range 10.0.0.10 10.0.0.100;
    option routers 10.0.0.1;
    option subnet-mask 255.255.255.0;
    option domain-name-servers 8.8.8.8, 8.8.4.4;
    option broadcast-address 10.0.0.255;
}

# Fixed IP assignments for specific MAC addresses
host device-22b04b70e729-1 {
    hardware ethernet 12:23:34:37:36:37;
    fixed-address 10.0.0.15;
}

host device-525400051ff05 {
    hardware ethernet 11:22:34:35:35:36;
    fixed-address 10.0.0.78;
}

Systemd NVR

~/.config/systemd/user/nvr-dirs.service
1
2
3
4
5
6
7
[Unit]
Description=Create NVR recording directories
After=network.target

[Service]
Type=oneshot
ExecStart=/home/vector/bin/create-nvr-dirs.sh
~/.config/systemd/user/nvr-dirs.timer
[Unit]
Description=Daily NVR directory creation
Requires=nvr-dirs.service

[Timer]
OnCalendar=daily
Persistent=true
AccuracySec=1min

[Install]
WantedBy=timers.target
~/bin/create-nvr-dirs.sh
#!/bin/bash
# Create NVR recording directories for today and tomorrow

BASE_DIR="/home/vector/recordings"

# Create today's directory
TODAY=$(date +%Y%m%d)
mkdir -p "$BASE_DIR/$TODAY"

# Create tomorrow's directory (in case recording crosses midnight)
TOMORROW=$(date -d tomorrow +%Y%m%d)
mkdir -p "$BASE_DIR/$TOMORROW"

echo "Created directories: $TODAY, $TOMORROW"
~/.config/systemd/user/camera-1.service
[Unit]
Description=NVR Camera 1 Recording
After=network.target nvr-dirs.service
Wants=nvr-dirs.service

[Service]
Type=simple
ExecStart=ffmpeg -hide_banner -loglevel error \
  -rtsp_transport tcp \
  -timeout 10000000 \
  -i "rtsp://admin:thecamerapassowrd4@10.0.0.101:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif" \
  -c copy \
  -avoid_negative_ts make_zero \
  -f segment \
  -segment_time 600 \
  -segment_format mp4 \
  -segment_list_flags +live \
  -segment_list_size 10 \
  -segment_wrap 288 \
  -reset_timestamps 1 \
  -strftime 1 \
  "/home/vector/recordings/%%Y%%m%%d/camera-1_%%H%%M%%S.mp4"
Restart=always
RestartSec=5
WorkingDirectory=/home/vector/recordings

[Install]
WantedBy=default.target

Accessing camera in the LAN with internet (enp5s0 network)

Don't run this script without understanding what it does.

configure-iptables.sh
#!/bin/bash

sudo iptables -F
sudo iptables -t nat -F
sudo iptables -X
sudo iptables -t nat -X

# Reset policies to ACCEPT
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT

sudo sysctl net.ipv4.ip_forward=1

sudo iptables -t nat -A PREROUTING -p tcp --dport 1101 -j DNAT --to-destination 10.0.0.101:554
sudo iptables -t nat -A PREROUTING -p udp --dport 1101 -j DNAT --to-destination 10.0.0.101:554

sudo iptables -t nat -A PREROUTING -p tcp --dport 1102 -j DNAT --to-destination 10.0.0.102:554
sudo iptables -t nat -A PREROUTING -p udp --dport 1102 -j DNAT --to-destination 10.0.0.102:554

sudo iptables -t nat -A PREROUTING -p tcp --dport 1103 -j DNAT --to-destination 10.0.0.103:554
sudo iptables -t nat -A PREROUTING -p udp --dport 1103 -j DNAT --to-destination 10.0.0.103:554

sudo iptables -t nat -A PREROUTING -p tcp --dport 1105 -j DNAT --to-destination 10.0.0.105:554
sudo iptables -t nat -A PREROUTING -p udp --dport 1105 -j DNAT --to-destination 10.0.0.105:554

sudo iptables -t nat -A PREROUTING -p tcp --dport 1106 -j DNAT --to-destination 10.0.0.106:554
sudo iptables -t nat -A PREROUTING -p udp --dport 1106 -j DNAT --to-destination 10.0.0.106:554

# Add a more restrictive rule that only allows traffic initiated from the main network
sudo iptables -A FORWARD -i enp8s0 -o br1 -j ACCEPT
sudo iptables -A FORWARD -i br1 -o enp8s0 -m state --state RELATED,ESTABLISHED -m conntrack --ctorigdst 10.0.0.0/24 -j ACCEPT

# ESSENTIAL: MASQUERADE rule for NAT to work
# sudo iptables -t nat -A POSTROUTING -o enp8s0 -j MASQUERADE

# Only MASQUERADE traffic that was DNAT'd to the isolated network (The above command gave 10.0.0.0/24 internet access)
sudo iptables -t nat -A POSTROUTING -o enp8s0 -m conntrack --ctorigdst 10.0.0.0/24 -j MASQUERADE

# Make IP forwarding permanent
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
# Save current rules
sudo iptables-save > /etc/iptables/iptables.rules
# Enable iptables service
sudo systemctl enable --now iptables

I no longer use frigate

It seems like you either need a powerful GPU or a Coral device or running Frigate becomes too CPU intensive. I'm just keep this here for future reference.

Frigate

source: https://ipv6.rs/tutorial/Arch_Linux/Frigate/

sudo pacman -S git python ffmpeg sudo pacman -S docker docker-buildx sudo systemctl start docker sudo systemctl enable docker
git clone --depth 1 https://github.com/blakeblackshear/frigate.git
cd frigate
cp config/config.yml.example config/config.yml
DOCKER_BUILDKIT=1 docker build -f docker/main/Dockerfile -t frigate .

Check out: https://github.com/blakeblackshear/frigate/discussions/4161

make
make version

Find out the rtsp link for your camera. For mine it turned out to be:

ffmpeg -i "rtsp://admin:L288B3D8@10.0.0.12:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif" result.mp4

proto=Onvif

Without "proto=Onvif" I got no stream.

Where L288B3D8 was the safety code written on camera. In the link proto=Onvif was necessary. This page really helped: https://www.ispyconnect.com/camera/imou

~/frigate/config.yaml
mqtt:
  enabled: false

# ffmpeg:
#   hwaccel_args: preset-nvidia-h264

# detectors:
#   coral:
#     type: edgetpu
#     device: usb

record:
  enabled: True
  retain:
    days: 7
    mode: motion
  alerts:
    retain:
      days: 30
  detections:
    retain:
      days: 30

snapshots:
  enabled: True
  retain:
    default: 30

cameras:
  nomi_bf:
    enabled: true
    ffmpeg:
      inputs:
        # If your camera supports dual streams, use substream for detection
        - path: rtsp://admin:L288B3D8@10.0.0.34:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - record
        - path: rtsp://admin:L288B3D8@10.0.0.34:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 640
      height: 360
      fps: 5

detect:
  enabled: true
version: 0.16-0

Finally:

docker run --name frigate -p 5000:5000 -v $PWD:/config --restart always frigate

Comments