Skip to content

Set up the camera

This guide uses a Linux installer layout. The camera renderer is a separate Node.js process. It needs the emulator, a current Volt build containing camera.html, and compatible renderer assets.

Get the source version that matches your emulator release. From a PlusEMU source checkout, run:

Terminal window
cd CameraRenderer
npm ci
npx playwright install chromium

On a Linux host missing Chromium system libraries, install them with npx playwright install-deps chromium. Install the browser as the account that will run the renderer.

Use the deployed Volt directory as CAMERA_PAGE_ROOT. For the Linux installer, confirm this file exists:

Terminal window
ls /var/www/hotel/client/camera.html

If it is missing, deploy a compatible Volt build before continuing.

The renderer’s local asset layout differs from the installer’s public hotel-files layout. Do not point CAMERA_ASSET_ROOT at that directory without preparing the expected files.

For the example below, place compatible assets under /srv/plusemu-camera-assets with this structure:

/srv/plusemu-camera-assets/
furniture/nitro/*.nitro
furniture/json/ProductData.json
clothes/nitro/*.nitro
clothes/json/FigureData.json
clothes/json/FigureMap.json
effects/nitro/*.nitro
effects/json/EffectMap.json
gamedata/json/HabboAvatarActions.json
pets/*.nitro

Use the asset set that matches your client. The current renderer requests .nitro filenames. A hotel asset pack containing only .hab files is not a drop-in local asset source for these defaults.

The example reads furniture data from the emulator through CAMERA_FURNIDATA_URL. Without that variable, the renderer also needs furniture/json/FurnitureData.json under the asset root.

Point CAMERA_IMAGES_ROOT at the directory containing Habbo-Stories/ for camera effects. In an installer layout, the image directory is /var/www/hotel/hotel-files/c_images.

An existing compatible asset service can replace local game assets through CAMERA_ASSET_ORIGIN. This must be an HTTPS origin, such as https://assets.example.com, with no path. It must serve /assets/... and /gamedata/... using the paths requested by the renderer. The renderer still reads camera effect images locally.

Generate a private secret of at least 32 characters. For example:

Terminal window
openssl rand -hex 32

Set the following keys inside the existing Camera section of the emulator’s Config/config.json. Keep its existing Effects list.

{
"RendererUrl": "http://127.0.0.1:3921/render",
"Bearer": "replace-with-your-private-secret",
"OutputDirectory": "/var/www/hotel/emulator/camera",
"TimeoutSeconds": 15,
"MaxConcurrency": 2
}

Keep the renderer private. RendererUrl must use HTTP, a private address, and the /render path. PlusEMU rejects redirects and public renderer addresses.

Give the emulator account write access to the output directory. The renderer and web server need read access. Keep the bearer out of client configuration and public files.

From CameraRenderer, enter the same secret without saving it in shell history:

Terminal window
read -rsp 'Camera secret: ' CAMERA_RENDERER_SECRET
export CAMERA_RENDERER_SECRET

Start the renderer with your prepared paths:

Terminal window
CAMERA_PAGE_ROOT=/var/www/hotel/client \
CAMERA_ASSET_ROOT=/srv/plusemu-camera-assets \
CAMERA_IMAGES_ROOT=/var/www/hotel/hotel-files/c_images \
CAMERA_MEDIA_ROOT=/var/www/hotel/emulator/camera \
CAMERA_FURNIDATA_URL=http://127.0.0.1:8080/api/gamedata/furnidata \
CAMERA_RENDERER_CONFIG=/var/www/hotel/client/configuration/renderer-config.json \
CAMERA_UI_CONFIG=/var/www/hotel/client/configuration/ui-config.json \
node server.mjs

Look for both Trusted camera renderer listening on 127.0.0.1:3921 and Trusted camera page ready. A listening process can still have asset or browser startup failures.

After the foreground check succeeds, run the same process under your service manager with these environment variables and a protected secret file. Restart it after each Volt deployment.

Add this location inside the hotel’s nginx server block. Adjust the alias if you chose a different output directory.

location /camera/ {
alias /var/www/hotel/emulator/camera/;
autoindex off;
limit_except GET HEAD { deny all; }
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-cache" always;
}

Check and reload nginx:

Terminal window
sudo nginx -t
sudo systemctl reload nginx

This location serves image files, not the private renderer. The refresh policy also lets room thumbnails change without retaining an old image.

The installer already sets thumbnails.url in Volt’s ui-config.json to /camera/thumbnail/%thumbnail%.png. Check that it still has this value.

Restart PlusEMU to load the camera connection settings:

Terminal window
sudo systemctl restart plusemu

Current fresh databases already contain camera tables. Updates apply database migrations at startup. Do not replay old SQL update files on a current installation.

  1. Sign in with an account that has camera.use.
  2. Enter a room and take a photo.
  3. Apply an available effect and confirm that the preview changes.
  4. Purchase a wall photograph and place it.
  5. Capture a room thumbnail with an account that has room management rights.
  6. Reload the navigator and check the thumbnail.

For a generated photo, request its actual /camera/<guid>.png URL in the browser. A successful renderer preview alone does not confirm public photo delivery.

Symptom Check
Renderer rejects captures The emulator bearer and CAMERA_RENDERER_SECRET match, with at least 32 characters
Renderer listens but never becomes ready camera.html, Chromium libraries, renderer file permissions, and the first asset error in its log
Missing asset error The exact requested filename exists in the expected local tree or configured asset origin
Camera unavailable for one account camera.enabled, camera.use, capture cooldowns, and daily quotas
Preview works but placed photos fail The /camera/ web route, output directory permissions, and CAMERA_MEDIA_ROOT
Photo purchase fails The account balance, point currency type 0, and a valid camera.item_id
Old photo draft fails Take a new photo in the current room and session
Thumbnail stays unchanged The thumbnail URL and cache rules on nginx and Cloudflare

See camera settings for prices and quotas.