◆ ForgeVis
Skip to content

Configuration Reference ​

Minimal working configuration config.yaml:

yaml
record:
  format: fmp4
  buffer_size_mb: 4
  segmentDuration: 900

cameraDefaults:
  record_path: "./recordings/%path/%Y-%m-%d_%H-%M-%S_%f.mp4"
  record: true

rtsp:
  enabled: true
  port: 8554

cameras:
  camera_001:
    source: "rtsp://admin:password@192.168.1.100:554/stream1"

Configuration File ​

By default, ForgeVis looks for config.yaml in the current directory. You can specify a different file:

bash
./forgevis my-config.yaml

Settings ​

cameraDefaults ​

Default settings for all cameras. Can be overridden per camera. Any camera setting from Cameras except source, sub and snapshot can be set here.

record_path ​

Path template for recordings with placeholders:

  • %path — camera identifier (key from the cameras map)
  • %Y, %m, %d, %H, %M, %S — the segment's start time: 4-digit year, 2-digit month, day, hour, minute, second
  • %f — fragment number within the segment (6 digits, zero-padded)

Example: ./recordings/%path/%Y-%m-%d_%H-%M-%S_%f.mp4 becomes
./recordings/camera_001/2025-12-04_22-00-00_000000.mp4

To change the path: create the directory, give it to the service user (chown forgevis:forgevis <directory>), change record_path and restart the service. Recordings under the old path are not seen in the archive — it is looked up by the new template; move them into the new directory with the same layout, or leave them where they are.

retention_days ​

How many days of recordings to keep:

yaml
cameraDefaults:
  retention_days: 30

Recordings are deleted by the forgevis --retention command, not by the service — put it in cron. With no retention_days nothing is deleted. See Archive retention.

record ​

Enable or disable recording by default for all cameras:

  • record (boolean, optional, inherited from defaults) — when true, camera is recorded unless overridden per camera

To disable recording by default, set record: false:

yaml
cameraDefaults:
  record: false  # disable recording for all cameras

reconnect ​

Pauses between attempts to connect to a camera that does not answer or dropped its stream. One schedule for the node — recording, HLS, the RTSP restream and viewers; failures are counted separately for each camera's main stream and substream. The defaults:

yaml
reconnect:
  healthy_reset_sec: 30
  jitter_percent: 20
  backoff:
    - { delay_sec: 0,  attempts: 1 }
    - { delay_sec: 5,  attempts: 5 }
    - { delay_sec: 10, attempts: 5 }
    - { delay_sec: 15, attempts: 5 }
    - { delay_sec: 30, attempts: 0 }
  • backoff — steps in order: attempts attempts with a pause of delay_sec seconds before each. attempts: 0 on the last step means "from here on"; the last step's pause must be above zero
  • jitter_percent (0 to 100) — each pause is stretched or shortened at random by up to this share, so cameras that went down together do not reconnect in the same second
  • healthy_reset_sec — how many seconds a stream has to run for its failure count to reset

A pause is the time between attempts; an attempt itself takes up to 15 seconds. During a pause the node leaves the camera alone, and a request for its stream is refused at once with the cause and the time of the next attempt. Changing a camera's settings starts the count afresh. The schedule is applied without restarting the service.

record ​

Global recording settings:

yaml
record:
  format: fmp4
  buffer_size_mb: 4
  segmentDuration: 900
  runOnSegmentCreate: "./hooks/on-create.sh"
  runOnSegmentComplete: "./hooks/on-complete.sh"
  • format (string, default fmp4) — recording format: fmp4 (fragmented MP4, playable while recording) or mp4 (plain MP4)
  • buffer_size_mb (integer, default 4) — writer buffer size per camera (MB)
  • segmentDuration (integer, default 900) — segment duration in seconds
  • runOnSegmentCreate (string, optional) — shell command executed when a new segment starts
  • runOnSegmentComplete (string, optional) — shell command executed when a segment is finalized
  • runPeriodically — periodic hook: command, intervalSec (default 60), maxConcurrent (default 50). See Event Hooks

Hook environment variables:

  • SF_PATH — camera ID
  • SF_SEGMENT_PATH — full segment file path
  • SF_SEGMENT_DURATION — segment duration in seconds (only for runOnSegmentComplete)

RTSP Server ​

Configuration for restreaming server:

  • enabled (boolean, default true) — enable/disable RTSP server
  • port (integer, default 8554) — TCP port for RTSP server
  • udp_rtp_port (integer, default 8000) — UDP port for RTP packets
  • udp_rtcp_port (integer, default 8001) — UDP port for RTCP packets
  • idle_timeout_sec (integer, default 10) — disconnect from camera after N seconds of no clients

The RTSP server supports both TCP (interleaved) and UDP transports. The restream URLs will be: rtsp://localhost:8554/{camera_id}

Management API ​

Configuration for the HTTP management API:

yaml
api:
  enabled: true
  address: "0.0.0.0:9997"
  allowOrigin: "*"
  rateLimit:
    enabled: true
    perMinute: 600
    burst: 60
  archiveJobTimeoutSec: 3600
  archiveExportDir: "/var/lib/forgevis/exports"
  • enabled (boolean, default true) — enable Management API
  • address (string, default "0.0.0.0:9997") — listen address and port
  • allowOrigin (string, default "*") — CORS allowed origin. * is any origin, otherwise one or several separated by commas (https://a.example, https://b.example); a value that is not an origin is ignored with a warning in the log
  • rateLimit.enabled (boolean, default true) — per-IP throttling on API
  • rateLimit.perMinute (int, default 600) — sustained rate per IP
  • rateLimit.burst (int, default 60) — token bucket capacity (initial burst)
  • archiveJobTimeoutSec (int, default 3600) — longest one ffmpeg run of an archive export or timelapse may take, in seconds; a run past it is stopped and the job fails
  • archiveExportDir (string, default /var/lib/forgevis/exports) — directory holding finished export and timelapse files, along with the parts an assembling node downloads from its peers. Created on the first job, accessible to the service alone. The path is absolute: these files run to gigabytes, and a relative one would depend on the directory the service was started from

When the bucket is empty the API returns 429 Too Many Requests. The defaults are intentionally generous for internal-network deployments; tighten if you expose the API to less-trusted networks.

tls ​

TLS for every HTTP listener on the node at once: the management API, HLS, WebRTC signalling, archive playback and metrics.

yaml
tls:
  enabled: false
  cert: /etc/forgevis/tls/node.pem
  key:  /etc/forgevis/tls/node.key
  • enabled (boolean, default false) — serve every HTTP listener over HTTPS
  • cert (string) — PEM certificate chain
  • key (string) — PEM private key, PKCS#8 or PKCS#1

One certificate for the node rather than one per subsystem: a browser that loaded the interface over https will not fetch an HLS playlist or a WHEP endpoint over http.

enabled: true without a readable cert and key fails the startup. The certificate is read once at startup, so replacing it needs a restart.

For the details — issuing a self-signed certificate, and how this differs from cluster.tls — see Security.

Stream Security ​

Optional access control for live streams, API and metrics. By default, if the security block is omitted or contains no users, all requests are allowed as in previous versions.

yaml
security:
  auth_method: internal

  users:
    # Local admin: API and metrics from localhost without credentials
    - user: any
      pass:
      ips: ["127.0.0.1/32", "::1/128"]
      permissions:
        - action: api
        - action: metrics

    # Viewer for all live streams (RTSP / HLS / WebSocket)
    - user: viewer
      pass: "change-me"
      ips: []
      permissions:
        - action: read
  • auth_method (string, default internal) — authentication backend; currently only internal is implemented.
  • users — list of user rules:
    • user — username, special value any means any (anonymous) user.
    • pass — optional password. Supports plain text or sha256:<base64_sha256> format (ignored when user: any).
    • ips — optional list of IPs or CIDRs allowed for this user (empty = any IP).
    • permissions — list of permissions:
      • action — one of publish, read, api, metrics.
      • path — optional camera/path restriction (empty or omitted = any).
  • lockout — protection against password guessing:
    • attempts (number, default 10) — failed sign-ins in a row; 0 turns the protection off.
    • duration_sec (number, default 300) — how many seconds the user is then locked out from that address.

SHA256 credentials ​

If you don't want to store plain credentials in config, hash them with SHA256 and Base64:

bash
echo -n "mypass" | openssl dgst -binary -sha256 | openssl base64

Then use sha256: prefix in config:

yaml
security:
  auth_method: internal
  users:
    - user: sha256:jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg=
      pass: sha256:6nHCWnpgIka0w5gkuFVniJSpb0O7m3ExnDlwCh4EUiI=
      permissions:
        - action: read

For full examples and migration strategy, see Security & Authentication.

When at least one user is defined in security.users:

  • all live read endpoints (RTSP restreaming, HLS, WebSocket /websocket/{camera_id}) require a matching permission with action: read (and optional path for camera scoping), and
  • the /metrics endpoint requires a permission with action: metrics.

The Management API then requires a permission with action: api.

WebSocket Streaming ​

Configuration for Low-Latency MSE Streaming (WebSocket):

yaml
websocket:
  enabled: true
  allowOrigin: "*"
  • enabled (boolean, default true) — enable WebSocket low-latency streaming
  • allowOrigin (string, default "*") — pages allowed to open the WebSocket. Browsers apply no CORS to a WebSocket, so the node checks the Origin header itself and answers 403 to a page not on the list; a request without Origin (not from a browser) is let through. * is any page, otherwise one or several origins separated by commas

WebRTC ​

Configuration for WebRTC signaling and browser playback:

yaml
webrtc:
  enabled: true
  address: ":8889"
  allowOrigin: "*"
  localUdpAddress: ":8189"
  localTcpAddress: ""
  ipsFromInterfaces: true
  ipsFromInterfacesList: []
  additionalHosts: []
  iceServers2:
    - url: "stun:stun.l.google.com:19302"
      username: ""
      password: ""
      client_only: false
  handshakeTimeout: "10s"
  trackGatherTimeout: "2s"
  stunGatherTimeout: "5s"
  • enabled (boolean, default false) — enable WebRTC server
  • address (string, default :8889) — WebRTC signaling HTTP listen address
  • allowOrigin (string, default *) — CORS allowed origin for signaling endpoints. * is any origin, otherwise one or several separated by commas (https://a.example, https://b.example); a value that is not an origin is ignored with a warning in the log
  • localUdpAddress (string, default :8189) — local UDP bind address for ICE
  • localTcpAddress (string, default empty) — local TCP bind address for ICE TCP
  • ipsFromInterfaces (boolean, default true) — include host candidates from local interfaces
  • ipsFromInterfacesList (string[], default []) — allowlist of interfaces used for host candidates
  • additionalHosts (string[], default []) — additional public/private host candidates
  • iceServers2 — ICE servers list (STUN/TURN):
    • url (string, required) — ICE server URL (e.g. stun:..., turn:...)
    • username (string, optional) — username for TURN auth
    • password (string, optional) — password for TURN auth
    • client_only (boolean, default false) — expose this ICE server only to clients
  • handshakeTimeout (string, default 10s) — max WebRTC handshake duration
  • trackGatherTimeout (string, default 2s) — max wait for track/media readiness
  • stunGatherTimeout (string, default 5s) — max wait for STUN candidate gathering

For endpoint usage and browser flow, see WebRTC Guide.

HLS ​

Browser viewing over HLS. In detail, with the quality choice and the 720p step — HLS Streaming.

yaml
hls:
  enabled: true
  address: ":8888"
  variant: lowLatency
  segmentCount: 7
  segmentDuration: 1s
  partDuration: 200ms
  muxerCloseAfter: 60s
  transcodeMaxConcurrent: 2
  • enabled (boolean, default false) — enable the HLS server
  • address (string, default :8888) — listen address and port
  • allowOrigin (string, not set by default) — value of the Access-Control-Allow-Origin header; without it the header is not sent. * is any origin, otherwise one or several separated by commas (https://a.example, https://b.example); a value that is not an origin is ignored with a warning in the log
  • alwaysRemux (boolean, default false) — keep HLS ready for every camera instead of starting it on the first request
  • variant (string, default lowLatency) — lowLatency (Low-Latency HLS) or fmp4 (plain HLS)
  • segmentCount (integer, default 7) — how many segments the playlist keeps, no fewer than three target durations
  • segmentDuration (string, default 1s) — minimum segment duration; a segment ends on a keyframe, so in practice it equals the camera's keyframe interval when that is longer
  • partDuration (string, default 200ms) — Low-Latency HLS part duration
  • muxerCloseAfter (string, default 60s) — how long without requests before a camera's HLS (and its 720p step) stops
  • transcodeMaxConcurrent (integer, default 2) — how many 720p steps a node may encode at once; 0 offers none

Playback ​

Archive recordings over HTTP, playable from any moment. In detail — Archive Playback.

  • enabled (boolean, default false) — enable the playback server
  • address (string, default :9996) — listen address and port
  • allowOrigin (string, not set by default) — value of the Access-Control-Allow-Origin header. * is any origin, otherwise one or several separated by commas (https://a.example, https://b.example); a value that is not an origin is ignored with a warning in the log

Metrics ​

Prometheus metrics. In detail — Monitoring.

  • enabled (boolean, default false) — enable the metrics endpoint
  • address (string, default :9998) — listen address and port
  • allowOrigin (string, not set by default) — value of the Access-Control-Allow-Origin header. * is any origin, otherwise one or several separated by commas (https://a.example, https://b.example); a value that is not an origin is ignored with a warning in the log

Logging ​

The service log. In detail — Monitoring.

  • directory (string, default /var/log/forgevis) — log file directory
  • level (string, default info) — error, warn, info, debug, trace
  • maxSizeMb (integer, default 100) — log file size after which a new one starts
  • maxBackups (integer, default 10) — how many old files to keep
  • console (boolean, default true) — also write the log to the console

Database ​

Cameras and recording metadata from PostgreSQL. In detail — Shared Database.

  • enabled (boolean, default false) — connect to the database
  • url (string) — connection string, postgres://user:pass@host:5432/db
  • watch (boolean, default true) — apply camera changes in the database on the fly

Cluster ​

Several nodes with cameras moved on failure. In detail — Clustering & HA.

  • enabled (boolean, default false) — enable cluster mode
  • node_id (integer) — the node's identifier in the cluster
  • rpc_addr (string) — address nodes use to talk to each other
  • dataDir (string, default /var/lib/forgevis/data) — where the node keeps cluster state; an absolute path
  • tls.caCert, tls.cert, tls.key (strings) — certificates for node-to-node traffic

nodeName ​

Node name at the top level of the config (string, defaults to the system hostname). A node picks its cameras from the database by it (cameras.node_name), so two nodes must not share one.

Cameras ​

Map of camera configurations. Key is the camera identifier used in path templates and restream URLs.

  • source (string, required) — RTSP URL of the main stream, credentials included
  • sub (string, optional) — RTSP URL of the substream
  • snapshot (string, optional) — HTTP URL of the camera's still image, served by /api/cameras/{id}/snapshot; the login is taken from this URL or, if it has none, from source
  • record (bool) — whether to record the camera
  • retention_days (number) — how many days of this camera's recordings to keep; see Archive retention
  • audio (bool, default false) — record and serve audio
  • rtsp (bool, default true) — serve the camera through the RTSP server
  • hls (bool, default true) — serve the camera over HLS
  • alwaysRemux (bool) — keep this camera's HLS ready at all times (overrides hls.alwaysRemux)
  • ptz (bool, default false) — the camera has PTZ control
  • onvif.endpoint, onvif.username, onvif.password (strings) — ONVIF address and credentials for PTZ; by default the address is built from the source host, and the login and password are taken from source
  • connection_timeout_sec (number, default 5, 30 in a cluster) — how many seconds without subscribers before disconnecting from the camera
  • node_name (string) — which cluster node the camera should preferably run on

Any of these except source, sub and snapshot can be set for every camera in cameraDefaults.

Source URL Format ​

The source field must be a valid RTSP URL. Credentials are embedded in the URL:

rtsp://[username[:password]@]host[:port]/path

Examples:

yaml
# With authentication
source: "rtsp://admin:password123@192.168.1.100:554/stream1"

# Without authentication
source: "rtsp://192.168.1.101:554/stream1"

# Custom port
source: "rtsp://user:pass@camera.local:8554/live"

Complete Example ​

yaml
# Global recording settings
record:
  format: fmp4
  buffer_size_mb: 4
  segmentDuration: 900

# Default settings for all cameras
cameraDefaults:
  record_path: "./recordings/%path/%Y-%m-%d_%H-%M-%S_%f.mp4"
  record: true
  audio: true  # Enable audio by default

# RTSP server configuration
rtsp:
  enabled: true
  port: 8554
  idle_timeout_sec: 300

# Camera configurations
cameras:
  # HD camera with substream and audio
  camera_001:
    source: "rtsp://admin:secret@192.168.1.100:554/stream1"
    sub: "rtsp://admin:secret@192.168.1.100:554/stream2"
    audio: true  # Record with audio

  # Restream-only camera (recording disabled)
  camera_002:
    source: "rtsp://192.168.1.101:554/stream1"
    record: false

  # Camera with recording and audio
  important_camera:
    source: "rtsp://user:pass@192.168.1.102:554/h264"
    record: true
    audio: true

Modular Configuration (conf.d) ​

For large deployments with many cameras, you can split camera configurations into separate files in the conf.d/ directory:

yaml
# conf.d/warehouse.yaml
warehouse_cam_01:
  source: "rtsp://user:pass@192.168.1.200:554/stream1"
  
warehouse_cam_02:
  source: "rtsp://user:pass@192.168.1.201:554/stream1"
yaml
# conf.d/parking.yaml
parking_entrance:
  source: "rtsp://user:pass@192.168.1.210:554/stream1"
  sub: "rtsp://user:pass@192.168.1.210:554/stream2"

Benefits:

  • Modularity: One file per camera or location
  • Organization: Group cameras by building, floor, or zone
  • Scalability: Easy to add/remove cameras without editing main config
  • Team workflow: Different people can manage different camera configs
  • Security: Keep credentials separate (optional .gitignore)

Loading order:

  1. Cameras from config.yaml are loaded first
  2. Cameras from conf.d/*.yaml are loaded alphabetically
  3. If a camera ID appears in both, conf.d version takes precedence (with warning)

Best Practices ​

Storage ​

  • fmp4 format is recommended (supports playback while recording)
  • Use hierarchical path structure: /rpool/%path/%Y/%m/%d/stream-%path_%Y-%m-%d_%H-%M-%S.mp4
  • Adjust segment duration based on use case:
    • 60s for live monitoring
    • 300-900s for archival storage

Cameras ​

  • Use descriptive identifiers (e.g., front_door, parking_north)
  • Disable recording per camera with record: false
  • Test RTSP URLs with ffprobe before adding to config

RTSP Server ​

  • Default port 8554 is standard for RTSP
  • Change port if conflicts occur
  • Firewall: Allow TCP port for RTSP server

Hot Reload ​

ForgeVis automatically watches for configuration changes and applies them without restart.

Monitored Files ​

  • config.yaml - Main configuration file
  • conf.d/*.yaml - Individual camera configuration files

What Can Be Changed ​

  • Add cameras - Create new YAML files in conf.d/ or add to config.yaml
  • Remove cameras - Delete camera files or remove from config
  • Update camera sources - Change RTSP URL for existing cameras
  • Enable/disable recording - Toggle record option per camera

How It Works ​

  1. Configuration files are monitored for changes
  2. Changes are debounced (500ms) to handle rapid edits
  3. New configuration is loaded and validated
  4. Running cameras are compared with new configuration:
    • Removed cameras are stopped and cleaned up
    • Changed sources trigger camera restart
    • New cameras are started automatically
  5. RTSP/HLS streams are updated immediately

Example Workflow ​

bash
# 1. Add new camera
echo "camera_003:
  source: rtsp://admin:password@192.168.1.103:554/stream
  sub: rtsp://admin:password@192.168.1.103:554/substream
" > conf.d/camera_003.yaml

# Configuration reloads automatically, camera_003 starts recording

# 2. Update camera source
# Edit conf.d/camera_002.yaml and save
# Camera restarts with new source automatically

# 3. Remove camera
rm conf.d/camera_001.yaml
# Camera stops, streams cleaned up, no longer accessible

Stream Management ​

When a camera is removed:

  • Recording stops gracefully
  • RTSP clients are disconnected
  • HLS muxers are cleaned up
  • Stream becomes unavailable (returns 404)
  • No restart required

Restreaming ​

Access camera streams via RTSP server:

bash
# Format
rtsp://localhost:{port}/{camera_id}

# Examples
ffplay -rtsp_transport tcp rtsp://localhost:8554/front_door
vlc rtsp://localhost:8554/parking

Operating Modes ​

The same binary can run in three distinct modes by flipping record and the rtsp server on/off.

Mode 1: Record only (no RTSP restreaming) ​

yaml
record:
  format: fmp4
  buffer_size_mb: 4
  segmentDuration: 900

cameraDefaults:
  record: true

rtsp:
  enabled: false  # restreaming disabled

cameras:
  camera_001:
    source: "rtsp://..."

Mode 2: Restream only (no recording) ​

yaml
cameraDefaults:
  record: false  # disable recording for all cameras

record:
  format: fmp4
  buffer_size_mb: 4
  segmentDuration: 900

rtsp:
  enabled: true
  port: 8554

cameras:
  camera_001:
    source: "rtsp://..."

Mode 3: Record + restream ​

yaml
record:
  format: fmp4
  buffer_size_mb: 4
  segmentDuration: 900

cameraDefaults:
  record: true

rtsp:
  enabled: true
  port: 8554

cameras:
  camera_001:
    source: "rtsp://..."

Validating the Configuration ​

ForgeVis validates the configuration on startup:

bash
./forgevis config.yaml

Watch the logs for errors, including:

  • Invalid RTSP URLs
  • Inaccessible recording paths
  • Duplicate camera_id entries
  • Malformed YAML

See Also ​

Proprietary software.