Running CaptureGem Recorderd Standalone on Linux
This guide explains how to run CaptureGem's recording daemon, recorderd, on a Linux x86_64 machine without the desktop interface. It is intended for a private, headless machine that you control. The daemon's HTTP API has no authentication, so do not expose its HTTP, WebSocket, or proxy ports directly to the internet.
What You Need
- A Linux x86_64 system, such as Ubuntu 20.04 or newer.
- Storage for recordings, plus at least 1 GB for the application binaries and working files.
- CaptureGem binaries from the same release:
recorderd,transcoder,ffmpeg,ffprobe,curl,streamlink, andcacert.pem.
The daemon checks that transcoder has the same version as recorderd, so always keep those two binaries from the same CaptureGem release.
Prepare the Binaries
The easiest source is the CaptureGem Linux AppImage. Extract it and copy the bundled files into one directory:
mkdir -p ~/capturegem/bin
chmod +x CaptureGem-*.AppImage
./CaptureGem-*.AppImage --appimage-extract
cp squashfs-root/resources/resources/recorderd ~/capturegem/bin/
cp squashfs-root/resources/resources/transcoder ~/capturegem/bin/
cp squashfs-root/resources/resources/ffmpeg ~/capturegem/bin/
cp squashfs-root/resources/resources/ffprobe ~/capturegem/bin/
cp squashfs-root/resources/resources/curl ~/capturegem/bin/
cp squashfs-root/resources/resources/streamlink ~/capturegem/bin/
cp squashfs-root/resources/resources/cacert.pem ~/capturegem/bin/
rm -rf -- ./squashfs-root
chmod +x ~/capturegem/bin/recorderd ~/capturegem/bin/transcoder ~/capturegem/bin/ffmpeg ~/capturegem/bin/ffprobe ~/capturegem/bin/curl ~/capturegem/bin/streamlink
Your installation should contain:
~/capturegem/
bin/
recorderd
transcoder
ffmpeg
ffprobe
curl
streamlink
cacert.pem
Use an absolute -binDir path in a service. The daemon uses this directory to find the transcoder, FFmpeg tools, cURL, Streamlink, and CA certificate bundle. Streamlink is required when recording generic stream URLs and other Streamlink-backed sites.
Start recorderd
Run the daemon with its default port assignments:
~/capturegem/bin/recorderd -defaultPorts -binDir ~/capturegem/bin
Default ports are HTTP 28471, WebSocket 28472, and proxy 28474. To choose ports yourself, provide HTTP and WebSocket ports; the proxy port is optional and otherwise remains 28474:
~/capturegem/bin/recorderd -http 28471 -ws 28472 -proxy 28474 -binDir ~/capturegem/bin
The -startupDiag=false flag turns off the startup-diagnosis log. Do not use -isChild or -parentPort for a standalone installation; those flags are for the Electron desktop app.
On first start, CaptureGem creates its data directory at ~/CaptureGem_Data. By default, recordings are stored in ~/CaptureGem_Data/videos/out, temporary HLS data is in ~/CaptureGem_Data/videos/tmp-hls, metadata is in ~/CaptureGem_Data/metadata, and proxy downloads are in ~/CaptureGem_Data/proxy/downloads.
Verify the Daemon
In another terminal, request the version and initial state:
curl http://127.0.0.1:28471/getVersion
curl http://127.0.0.1:28471/getInitialState
Use 127.0.0.1 for local control. The HTTP server is not an authenticated remote-management interface.
Enable Live Monitor Thumbnails in Headless Mode
Important: headless clients must activate the Monitor tab.
The desktop app sends this signal when you open its Monitor tab, but a standalone
recorderdhas no UI to do that automatically. Without the signal, recorderd may create the first preview image for a recording, but it will not keep refreshing that thumbnail.
After every recorderd restart, send SetActiveTab with the exact lowercase value monitor before relying on /getPreviewFiles:
curl -X POST http://127.0.0.1:28471/ \
-H "Content-Type: application/json" \
-d '{
"SetActiveTab": {
"tabName": "monitor"
}
}'
The active-tab signal is kept in memory and is not persisted across daemon restarts. The Monitor feature must also be enabled in CaptureGem settings (disableMonitor must be false, which is the default).
When you no longer need continuously refreshed previews, signal another tab such as dashboard to avoid the extra thumbnail-processing work:
curl -X POST http://127.0.0.1:28471/ \
-H "Content-Type: application/json" \
-d '{
"SetActiveTab": {
"tabName": "dashboard"
}
}'
Run It as a User Service
For a long-running headless setup, use a per-user systemd service. Create ~/.config/systemd/user/capturegem-recorderd.service:
[Unit]
Description=CaptureGem recording daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/capturegem
ExecStart=%h/capturegem/bin/recorderd -defaultPorts -binDir %h/capturegem/bin
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Security: lock down the HTTP and proxy ports before starting the service.
Recorderd listens for HTTP on all network interfaces (TCP 28471 by default), and its proxy does the same on TCP 28474. These interfaces do not authenticate requests. The WebSocket listener (TCP 28472) is loopback-only. On a VPS or any machine connected to an untrusted network, block direct access to 28471 and 28474 in both the provider firewall/security group and the host firewall.
For example, if UFW is already enabled, run these commands with administrator privileges before enabling the service:
ufw deny in to any port 28471 proto tcp
ufw deny in to any port 28474 proto tcp
ufw status numbered
Adjust the rules if you selected different ports. Do not add public inbound allow rules for these ports. For remote administration, prefer a VPN or an SSH local-forward; this example makes the HTTP API available only at 127.0.0.1:28471 on your workstation:
ssh -N -L 28471:127.0.0.1:28471 your-user@your-server
If you use an authenticated TLS reverse proxy instead, keep recorderd's raw HTTP and proxy ports blocked from external access so clients cannot bypass the proxy.
Then reload, enable, and inspect the service:
systemctl --user daemon-reload
systemctl --user enable --now capturegem-recorderd
systemctl --user status capturegem-recorderd
journalctl --user -u capturegem-recorderd -f
If the daemon must keep running after you log out, enable lingering for that Linux user using your system administrator's normal account-management procedure.
HTTP API
All GET routes and JSON POST messages use the HTTP port. Every POST except /forceUnlockConfigFile accepts a JSON object with one top-level action field. JSON field names below match recorderd's API.
GET Routes
Route Purpose /getInitialState Full configuration and model state /getVersion Recorderd version /getDefaultConfig Default configuration /getPreviewFiles Current live preview files; supports ETag caching /getAllMetadata Paginated video metadata; accepts page, entries, tags, negativeTags, modelName, site, search, sort, startDate, and endDate /getQueueState Transcoding, ffmpeg, metadata, and API queue state; accepts optional filter /getSystemMonitorHistory System-monitor history /getAuditFiles?videoId=... Playlist, recording-stat, and transcoder audit files /getVideoDetails?videoId=... Video dimensions, codec, and recorded end reason /getRecordingLifecycleHistory Recording lifecycle history /getFakeOfflineModels Configured fake-offline models /getRequestConfig?site=... Saved cURL request configuration for a site /fetchPreviewImage?previewFile=...&isCacheFile=false A live preview image /fetchImageGrid?videoId=...&size=small A small or large review image grid /getProxyStreams Proxy streams active in the last 15 seconds /getProxyStreamFiles?streamId=... Files captured for a proxy stream /getProxyStats Proxy request statistics /testUrlFetchChain Diagnostic route for the renderer URL-fetch chain
Common POST Examples
POST to the HTTP root. For example, add a model, then start recording it:
curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" -d '{"AddModel":{"username":"example_user","site":"Chaturbate"}}'
curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" -d '{"StartRecording":{"username":"example_user","site":"Chaturbate"}}'
Action field Body Purpose AddModel { username, site, monitor? } Add a model, optionally in monitor mode RemoveModel { username, site } Remove a model ChangeModelSettings { username, site, modelConfig, startRecording } Change auto-record settings, notes, or rating StartRecording / StopRecording / PauseRecording / StartMonitoring { username, site } Control recording or monitoring AddFakeOfflineModel / RemoveFakeOfflineModel { username, site } Manage the fake-offline list CreateRecordingGroup { modelIds, color? } Create a recording group UpdateRecordingGroup { groupId, modelIds, color? } Update a recording group DisassociateRecordingGroup { modelIds } Remove models from a group ChangeGlobalSettings { settings... } Update directories and global recorder settings ResetToDefaultConfig {} Restore default configuration SetActiveTab { tabName } Set dashboard, review, monitor, or settings; use lowercase monitor for continuously refreshed headless previews ExportModels {} Export models to CSV ImportModels { csvPath } Import models from a CSV path on the server DeleteVideo { videoNames } Delete videos by name CutVideo { videoFile, cutTimes, keepOriginal, enableBoomerang } Trim a video; cutTimes contains startSecs/endSecs pairs ConcatVideo { videoFiles, keepOriginal } Combine videos by absolute path ExportVideos { videoFiles, destinationDir } Copy videos to a directory AddVideoBoolMetadata { fileMetadata } Set boolean metadata entries by filename and key UpdateVideoMetadata { filename, modelName, siteName, recordTime } Update video metadata StartProxyRecording / StopProxyRecording { streamId } Control a captured proxy stream ImportProxyRecording { streamId, site, username } Import a proxy recording manually EnterLicense { license } Activate a license key EnterPatreon { patreonEmail } Activate with a Patreon email RequestEmailCode { email } Request an email verification code SubmitEmailCode { email, code } Submit an email verification code ChaturbateLogin { username, password } Log in to Chaturbate EnterFilebaronAuth { email, password } Authenticate with Filebaron TestCurlRequest { username, curlRequest, testType } Test a base64-encoded cURL request UrlFetchRequest { method, url, headers, body, timeout? } Send a URL request through the renderer chain
For example, update the output directory with the correct ChangeGlobalSettings payload:
curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" -d '{"ChangeGlobalSettings":{"videoSaveDirectory":"/home/your-user/videos"}}'
To clear a stale configuration lock, use the one path-based POST route:
curl -X POST http://127.0.0.1:28471/forceUnlockConfigFile
WebSocket
The real-time WebSocket route is ws://127.0.0.1:28472/ws. It is bound to loopback and broadcasts state changes such as model status and recording progress.
Troubleshooting
- Transcoder version mismatch or not found: put the release-matched
transcodernext torecorderdand pass the correct-binDir. - cURL or certificate errors: confirm that the bundled
curlandcacert.pemare in-binDirand that cURL is executable. - Port already in use: use different
-http,-ws, and, if needed,-proxyvalues. - Configuration locked: stop duplicate daemon instances, then use
/forceUnlockConfigFileonly when you are certain no other instance is writing that data directory. - Corrupted or missing recordings: never run two recorderd instances against the same
CaptureGem_Datadirectory. One process can clean up temporary files the other still needs.


