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, andcacert.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_URLenvironment variable to its address, for examplehttp://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_URLdoesn'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
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
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
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 (default 50), tags and negativeTags (comma-separated), modelName, sort, and sortDirection /getMetadataInitializationStatus Whether the first metadata scan is still running /getQueueState Transcoding, ffmpeg, metadata, and API queue state; accepts optional filter /getUnfinishedRecordings Recordings that haven't become videos yet, with their state, errors, and a manual conversion command /getActivityHistory Conversion 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 /getBandwidth Current download rates of active recordings /getRetainedTempFiles?videoId=... Whether a video's temporary files were kept, where, and when they will be deleted /tempFolderMoveStatus Progress of moving the temporary files folder after a settings change /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 /resolveSuperliveProfile?livestreamId=... The Superlive profile ID for a livestream ID /getFlirt4FreeCandidates Usernames of Flirt4Free models currently in a public room /testUrlFetchChain Diagnostic 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 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; 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
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. The CaptureGem desktop app's own recorderd uses 28471 when it is free. - 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 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_DIRto 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.