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. CaptureGem's Linux release is built on Ubuntu 22.04; older distributions may not work.
  • 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, and cacert.pem.

The daemon checks that transcoder has the same version as recorderd, so always keep those two binaries from the same CaptureGem release.

Sites That Need the Desktop App

Most sites record the same way with or without the desktop app. These don't work in a standalone recorderd:

  • LiveJasmin: recorderd hands LiveJasmin recordings to the desktop app (on 127.0.0.1:28473), so they can't run without it.
  • XCams, ManyVids and Superlive: these streams are captured by the desktop app's built-in browser.
  • CamSoda and AmateurTV: status checks go through the desktop app's browser. As an advanced option, recorderd can send CamSoda's status checks to a browserless Chrome service instead (set the BROWSERLESS_URL environment variable to its address, for example http://127.0.0.1:3000). CamSoda's video servers sometimes refuse downloads that don't come from a browser, so CamSoda recordings may still fail this way. BROWSERLESS_URL doesn't help AmateurTV.

Chaturbate works headless with one limit. CaptureGem first checks rooms with its built-in Chaturbate client. If Chaturbate challenges that client, CaptureGem checks again with your saved Chaturbate Custom Headers or, without them, through the desktop app's browser, which a standalone recorderd doesn't have. To use Custom Headers headless, copy the request as described in How to Add Chaturbate Custom Headers to CaptureGem, save the copied curl command in a file such as chaturbate-curl.txt, and send it base64-encoded:

CB_REQUEST=$(base64 -w0 chaturbate-curl.txt)
printf '{"ChangeGlobalSettings":{"camSiteRequests":{"chaturbate":"%s"}}}' "$CB_REQUEST" \
  | curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" --data-binary @-

The request contains your Chaturbate session cookies, so delete the file afterwards and don't share it. When the session expires, send a fresh request the same way. To remove the saved request, send {"ChangeGlobalSettings":{"camSiteRequests":{"chaturbate":""}}}.

Stripchat and StripchatVR work headless with the default Use cURL for Stripchat Requests setting. If you turned it off, turn it back on with {"ChangeGlobalSettings":{"useCurlForStripchat":true}}.

Prepare the Binaries

Download CaptureGem for Linux from capturegem.com, or an older release from Past Versions. The download is a zip file, CaptureGem-<version>-Linux.zip, that contains the CaptureGem AppImage, launcher.sh, and README, license and troubleshooting files. Unzip it, then extract the AppImage and copy the bundled files into one directory:

mkdir -p ~/capturegem/bin
unzip CaptureGem-*-Linux.zip -d capturegem-download
cd capturegem-download
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, temporary files kept after conversion are in ~/CaptureGem_Data/videos/retained-temp, metadata is in ~/CaptureGem_Data/metadata, and proxy downloads are in ~/CaptureGem_Data/proxy/downloads.

To use a different data directory, set the CAPTUREGEM_DATA_DIR environment variable to its path before starting recorderd:

CAPTUREGEM_DATA_DIR=/srv/capturegem-data ~/capturegem/bin/recorderd -defaultPorts -binDir ~/capturegem/bin

Only one recorderd may use a data directory. The CaptureGem desktop app starts its own recorderd, so if the desktop app runs on the same computer under the same Linux user, give the standalone recorderd its own CAPTUREGEM_DATA_DIR and its own ports.

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.

Activate Premium

Without a license, recorderd runs in the free mode, like the desktop app without Premium:

  • each recording stops after 5 minutes;
  • CaptureGem doesn't start recordings on its own: every model's auto-record setting is None, and new models are added with None;
  • you can add up to 10 models.

To activate Premium, request a code for the email address of your CaptureGem or CamTrove Premium account, then submit the code from that email:

curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" -d '{"RequestEmailCode":{"email":"[email protected]"}}'
curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" -d '{"SubmitEmailCode":{"email":"[email protected]","code":"123456"}}'

You can also skip the first command: sign in to your CaptureGem account page, click Generate Code under App License Code, and submit that code with SubmitEmailCode. A successful activation answers with "success":true. A recording length of 5 minutes changes to 60 minutes.

Models you added in the free mode keep auto-record None after activation. Change each one with ChangeModelSettings. For example, AutoContinueWhileOnline is the Consecutive option in the desktop app:

curl -X POST http://127.0.0.1:28471/ -H "Content-Type: application/json" -d '{"ChangeModelSettings":{"username":"example_user","site":"Chaturbate","modelConfig":{"autoRecordSettings":{"autoRecordType":"AutoContinueWhileOnline","expiryUnixSecs":0}},"startRecording":false}}'

The other values are AutoRecordAlways (Record Always), AutoMonitorAlways (Monitor Always), AutoRecordExpiry (Record With Expiry), AutoMonitorExpiry (Monitor With Expiry), and AutoRecordSingle (None).

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 recorderd has 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

To use a separate data directory, add a line such as Environment=CAPTUREGEM_DATA_DIR=%h/capturegem-data under [Service].

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 server's HTTP API available at 127.0.0.1:38471 on your workstation. It uses a different local port because a CaptureGem desktop app running on the workstation uses 28471:

ssh -N -L 38471:127.0.0.1:28471 your-user@your-server

recorderd allows requests from any web page (it sends Access-Control-Allow-Origin: *), so while the tunnel is open, a website in your workstation's browser could send commands through it. Open the tunnel only while you need it.

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

RoutePurpose
/getInitialStateFull configuration and model state
/getVersionRecorderd version
/getDefaultConfigDefault configuration
/getPreviewFilesCurrent live preview files; supports ETag caching
/getAllMetadataPaginated video metadata; accepts page, entries (default 50), tags and negativeTags (comma-separated), modelName, sort, and sortDirection
/getMetadataInitializationStatusWhether the first metadata scan is still running
/getQueueStateTranscoding, ffmpeg, metadata, and API queue state; accepts optional filter
/getUnfinishedRecordingsRecordings that haven't become videos yet, with their state, errors, and a manual conversion command
/getActivityHistoryConversion and deletion history, newest first; accepts offset, filter, action, and reason
/getCombineProgress?id=...Progress of a running ConcatVideo started with progressId; 404 once it has finished
/getBandwidthCurrent download rates of active recordings
/getRetainedTempFiles?videoId=...Whether a video's temporary files were kept, where, and when they will be deleted
/tempFolderMoveStatusProgress of moving the temporary files folder after a settings change
/getSystemMonitorHistorySystem-monitor history
/getAuditFiles?videoId=...Playlist, recording-stat, and transcoder audit files
/getVideoDetails?videoId=...Video dimensions, codec, and recorded end reason
/getRecordingLifecycleHistoryRecording lifecycle history
/getFakeOfflineModelsConfigured fake-offline models
/getRequestConfig?site=...Saved cURL request configuration for a site
/fetchPreviewImage?previewFile=...&isCacheFile=falseA live preview image
/fetchImageGrid?videoId=...&size=smallA small or large review image grid
/getProxyStreamsProxy streams active in the last 15 seconds
/getProxyStreamFiles?streamId=...Files captured for a proxy stream
/getProxyStatsProxy request statistics
/resolveSuperliveProfile?livestreamId=...The Superlive profile ID for a livestream ID
/getFlirt4FreeCandidatesUsernames of Flirt4Free models currently in a public room
/testUrlFetchChainDiagnostic route for the desktop app's URL-fetch chain; needs the desktop app

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 fieldBodyPurpose
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; groupId is the group's number as a string, such as "3"
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, allowReencode?, progressId? }Combine videos by absolute path. Videos that can't be joined as they are get a 409 reply listing the differences; send allowReencode: true to re-encode them
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
ManageUnfinishedRecording{ folderName, action }Retry the conversion of an unfinished recording (action "retry") or delete its source files (action "delete")
DeleteTranscodingQueueItems{ paths }Delete waiting recordings and their source files from the transcoding queue; paths come from /getQueueState
RetranscodeRetained{ videoId }Convert a video again from its kept temporary files
SetRetainedTempDeletionPaused{ videoId, paused }Pause or resume the automatic deletion of a video's kept temporary files
DeleteAllTempFiles{}Not available over HTTP; only inside the desktop app
StartProxyRecording / StopProxyRecording{ streamId }Control a captured proxy stream
ImportProxyRecording{ streamId, site, username }Import a proxy recording manually
RequestEmailCode{ email }Email a license code to the address of a CaptureGem or CamTrove account
SubmitEmailCode{ email, code }Activate Premium with an emailed code or an account-page code
EnterLicense{ license }Not used: the request never gets a reply. Activate with RequestEmailCode and SubmitEmailCode
EnterPatreon{ patreonEmail }Activate with a Patreon email
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 desktop app's browser; needs the desktop app

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 transcoder next to recorderd and pass the correct -binDir.
  • cURL or certificate errors: confirm that the bundled curl and cacert.pem are in -binDir and that cURL is executable.
  • Port already in use: use different -http, -ws, and, if needed, -proxy values. The CaptureGem desktop app's own recorderd uses 28471 when it is free.
  • Configuration locked: stop duplicate daemon instances, then use /forceUnlockConfigFile only when you are certain no other instance is writing that data directory.
  • Corrupted or missing recordings: never run two recorderd instances against the same data directory. The desktop app counts as one, because it starts its own recorderd. One process can clean up temporary files the other still needs. Use CAPTUREGEM_DATA_DIR to give each instance its own directory.
  • A site shows Errored and the log mentions the URL fetch chain or 127.0.0.1:28473: that site needs the desktop app. See Sites That Need the Desktop App above.
← Return to Guides