← Paracosm·Docs/Handbook
Contents · Configuration

Configuration: the .env and the overlay

Paracosm's configuration has two layers:

  1. .env in the source root: every secret and every per-install value. It is git-ignored and never shipped; .env.example is the template. The Python tools load it through tools/paracosm_env.py.
  2. The templates in config/: Paracosm's changes to OpenSimulator's own example files, with ${NAME} placeholders filled from .env. Nothing secret lives in them.

tools/make_config.py joins the two into the server's bin/ (the build runs it; see quickstart.md section 5):

python tools/make_config.py <bin> [--env <env file>] [--force]
writes (in <bin>) from
OpenSim.ini OpenSim.ini.example + config/paracosm.ini
config-include/StandaloneCommon.ini config-include/StandaloneCommon.ini.example + config/StandaloneCommon-overlay.ini
config-include/FlotsamCache.ini, config-include/osslEnable.ini their .example files, unchanged (without them every boot logs a warning about a missing include)
Regions/Regions.ini config/Regions.ini.template, with a region UUID made once
OpenSim.exe.config config/OpenSim.exe.config, copied as it is: OpenSim's log4net config with OpenSim.log rolling at 20 MB, five old files kept (operations.md, Logs)

How .env is read

KEY=VALUE per line; blank lines and lines starting with # are skipped; quotes around a value are stripped; nothing is expanded. A variable already set in the process environment wins over the file. The tools find the file at PARACOSM_ENV if set, else .env in the source root.

Every key in .env.example

"Change" says whether a new install must change the template's value.

Paths

key template value read by controls change
PARACOSM_ROOT commented out (C:/path/to/paracosm) tools/paths.py, so every tool the source root. Unset (or not an existing folder, with a warning), the repo the tools sit in no
PARACOSM_BIN commented out (C:/path/to/paracosm/opensim-src/bin) tools/paths.py: run_server.py, backup.py, wb.py, region_restart.py the server's bin/ folder: where OpenSim runs, its databases and logs. Unset, <source root>/opensim-src/bin yes: <build output>/bin

The grid and the region (written into OpenSim's config)

key template value becomes controls change
PARACOSM_LOGIN_URI http://127.0.0.1:9000 (not config) the login URI run_viewer.py and the test bot use; run_viewer.py accepts only a loopback one only with a different port
PARACOSM_GRID_NAME Paracosm [GridInfoService] gridname the grid's name, as viewers show it optional
PARACOSM_GRID_NICK paracosm [GridInfoService] gridnick the grid's short name optional
PARACOSM_EXTERNAL_HOST 127.0.0.1 [Const] BaseHostname; Regions.ini ExternalHostName the address in the login and service URLs, and where viewers send region traffic. 127.0.0.1 = this machine only for LAN or remote use (quickstart section 12)
PARACOSM_PORT 9000 [Const] PublicPort; [Network] http_listener_port; Regions.ini InternalPort the simulator's TCP port (login, capabilities) and the region's UDP port only to run a second server on one machine
PARACOSM_REGION_NAME Paracosm the Regions.ini section name the region's name optional
PARACOSM_REGION_LOCATION 1000,1000 Regions.ini Location the region's grid coordinates, in 256 m cells optional
PARACOSM_REGION_SIZE 256 Regions.ini SizeX, SizeY metres per side: 256, 512 (2x2 var region) or 768 (3x3); a multiple of 256, square optional; before the first start
PARACOSM_ESTATE_NAME Paracosm Estate [Estates] DefaultEstateName the estate the first start creates optional
PARACOSM_ESTATE_OWNER_FIRST, PARACOSM_ESTATE_OWNER_LAST Resident, Spirit [Estates] DefaultEstateOwnerName the estate owner, created on the first start. Each part must be one word (OpenSim splits the name at the space). Also read by backup.py (the owner's IAR) yes
PARACOSM_ESTATE_OWNER_PASSWORD change-me [Estates] DefaultEstateOwnerPassword the estate owner's password, used only when the first start creates the account. Also read by backup.py (save iar) yes; never empty
PARACOSM_ESTATE_EMAIL [email protected] [Estates] DefaultEstateOwnerEMail the estate owner's email optional

The [Estates] values matter only on the first start. Later changes do not rename the owner or change the password; use the console (reset user password <first> <last> <password>) and then update .env.

Accounts the tools log in with

key template value read by controls change
PARACOSM_RESIDENT_FIRST, _LAST, _PASSWORD Resident, Spirit, change-me run_viewer.py (the default, --as resident), backup.py (IAR) the AI resident's own avatar. The template makes it the estate owner; then its password must equal PARACOSM_ESTATE_OWNER_PASSWORD yes
PARACOSM_OWNER_FIRST, _LAST, _PASSWORD empty run_viewer.py --as owner a person's own avatar, launched through the same viewer build without the helper bridge optional
PARACOSM_BOT_FIRST, _LAST, _PASSWORD Test, Bot, change-me bot/Program.cs the LibreMetaverse test bot's account (create it first). The bot reads its process environment, not the .env file only if you use the bot

OpenSim's REST console

key template value becomes controls change
PARACOSM_CONSOLE_USER empty [Network] ConsoleUser the REST console's user name. Empty: the REST console refuses every session set it (with a password) only when you need a console on a headless server
PARACOSM_CONSOLE_PASS empty [Network] ConsolePass its password. OpenSim checks only the user name for "off": with a user name set and this empty, an empty password logs in set a strong one whenever the user is set
PARACOSM_CONSOLE_PORT 9001 [Network] console_port the REST console's port (plain HTTP, all interfaces) optional; firewall it

tools/run_server.py always starts OpenSim with -console=rest (a detached process has no terminal, and a local console dies at startup). Connect with dotnet OpenSim.ConsoleClient.dll -host 127.0.0.1 -port <port> -user <user> -pass <password> from bin/.

WorldBridge

key template value becomes / read by controls change
WORLDBRIDGE_URL http://127.0.0.1:9002 wb.py where the tools reach WorldBridge only with WORLDBRIDGE_PORT
WORLDBRIDGE_PORT 9002 [WorldBridge] Port WorldBridge's port (always on 127.0.0.1) optional; keep it equal to the URL's port
WORLDBRIDGE_ALLOW_CONSOLE false [WorldBridge] AllowConsole whether the console op may run server console commands (see Security) true only if the resident needs it, or for a backup with the OAR, IARs and terrain (without it backup.py copies only the databases and state)
WORLDBRIDGE_FILE_ROOTS worldbridge-files [WorldBridge] FileRoots the folders a request may name files in (see Security) optional
WORLDBRIDGE_OBSERVER_TOKEN, WORLDBRIDGE_BUILDER_TOKEN, WORLDBRIDGE_GAIA_TOKEN commented out (change-me-...) [WorldBridge] TokenObserver, TokenBuilder, TokenGaia extra bearer tokens with a role (the table under the overlay). Each line is written only when its key is set; 16 characters or more (python -c "import secrets; print(secrets.token_hex(24))"). A client uses one as its WORLDBRIDGE_TOKEN optional; never the template's value (make_config.py refuses it)
WORLDBRIDGE_TOKEN_FILE commented out (C:/path/to/paracosm/opensim-src/bin/WorldBridge.token) wb.py (so also run_server.py stop and backup.py) the token file WorldBridge writes on its first start. Unset or empty, wb.py uses <PARACOSM_BIN>/WorldBridge.token, which is where WorldBridge writes it only if the file is elsewhere; the MCP server needs it in its own env

The viewer's helper bridge

key template value read by controls change
PARACOSM_VIEWER_TOKEN commented out run_viewer.py, tools/leap_bridge.py, the MCP server the bearer token of the LEAP bridge on 127.0.0.1:9004. Unset, run_viewer.py makes a fresh random one per launch and the bridge writes it to a per-user token file the tools and the MCP read (tools/PORTABILITY.md) no; set it only to pin one token on purpose

Optional environment variables (not in the template)

variable read by default does
PARACOSM_ENV tools/paracosm_env.py (every tool), tools/make_config.py (when no --env) <source root>/.env the env file the tools load (the systemd unit sets it)
PARACOSM_BACKUP_DIR backup.py <source root>/backups (git-ignored) where backups go
WORLDBRIDGE_TOKEN wb.py, the MCP server none a token given directly; wins over the token file
WORLDBRIDGE_REGION wb.py, the MCP server none: the simulator's first region on a simulator with several regions, the region (name or uuid) a call goes to when it names none itself; sent as ?region= (worldbridge.md, "Several regions")
VIEWER_URL, VIEWER_TOKEN the MCP server http://127.0.0.1:9004; VIEWER_TOKEN is a legacy name, read only when neither PARACOSM_VIEWER_TOKEN nor the bridge's token file gives one the viewer bridge the viewer_* tools call
PARACOSM_VIEWER_EXE, PARACOSM_VIEWER_DATA run_viewer.py a development build under C:/fs/phoenix-firestorm the viewer exe, and the folder it runs in (a packaged viewer: its own folder)
PARACOSM_VIEWER_PORT run_viewer.py, leap_bridge.py 9004 the bridge's port
PARACOSM_VIEWER_MUTE run_viewer.py unset 1 starts the viewer muted
PARACOSM_ASSET_INSTR OpenSim (the asset-instr patch) unset 1 in OpenSim's environment logs every GetAssets request, miss and serve (GETASSET-INSTR in OpenSim.log)
FS_ROOT, VCVARS, PY_SCRIPTS viewer-build/*.bat, package.py (FS_ROOT) C:\fs, VS 2022 Build Tools' vcvars64.bat, %APPDATA%\Python\Python312\Scripts the viewer build (packaging.md)

The overlay, block by block

config/paracosm.ini (merged into OpenSim.ini)

block key = value upstream default what it does
[Const] BaseHostname = "${PARACOSM_EXTERNAL_HOST}" "127.0.0.1" the host in BaseURL, which builds the login URI ([GridInfoService] login), the home and service URLs, and the map tile URL
PublicPort = "${PARACOSM_PORT}" "9000" the port in those URLs
[Permissions] region_manager_is_god = true false estate managers may use god powers (they still ask for them in the viewer each session)
[Estates] DefaultEstateName, DefaultEstateOwnerName, DefaultEstateOwnerUUID = 00000000-0000-0000-0000-000000000000 (a random UUID), DefaultEstateOwnerEMail, DefaultEstateOwnerPassword commented out (the first start prompts for them) the first start creates the estate and its owner with no prompt
[Network] http_listener_port = ${PARACOSM_PORT} 9000 the simulator's HTTP port. OpenSim does not derive it from PublicPort; left at 9000, a second server on one machine cannot start
ConsoleUser, ConsolePass, console_port commented out the REST console (above)
[Groups] Enabled = true, LevelGroupCreate = 0, Module = "Groups Module V2", StorageProvider = OpenSim.Data.SQLite.dll, ConnectionString = "URI=file:groups.db,version=3", ServicesConnectorModule = "Groups Local Service Connector", LocalService = local, MessagingEnabled = true, MessagingModule = "Groups Messaging Module V2", MessageOnlineUsersOnly = true, NoticesEnabled = true groups off Groups V2 stored in bin/groups.db. Needs the sqlite-groups patch (the build applies it). WorldBridge's group op and the shared-group editing model use it (sharing-and-inventory.md)
[Messaging] OfflineMessageModule = "Offline Message Module V2", StorageProvider = OpenSim.Data.SQLite.dll, ConnectionString = "URI=file:offlineim.db,version=3" no offline messages offline IM on (below). Needs the sqlite-offlineim patch: with an unpatched OpenSim.Data.SQLite.dll OpenSim stops at startup
[UserProfiles] ProfileServiceURL = "${PARACOSM_PROFILE_SERVICE_URL?}" (written only when set) unset: basic profiles only OFF by default: full profiles need PARACOSM_PROFILE_SERVICE_URL and PARACOSM_FULL_PROFILES=true in the .env, and then the profile service answers on the grid port without credentials (below)
[WorldBridge] Enabled = true (module default true) loads the control channel
BindAddress = "127.0.0.1" 127.0.0.1 anything but 127.0.0.1 or localhost is logged as a warning and forced back to 127.0.0.1
Port = ${WORLDBRIDGE_PORT} 9002 the channel's port
MaxBodyBytes = 65536 65536 the largest request body; a bigger one gets 413
TokenFile = "WorldBridge.token" WorldBridge.token where the generated gaia token is kept (relative to OpenSim's working directory, bin/)
AllowConsole = ${WORLDBRIDGE_ALLOW_CONSOLE} false the console op
FileRoots = "${WORLDBRIDGE_FILE_ROOTS}" worldbridge-files the file roots
TokenObserver = "${WORLDBRIDGE_OBSERVER_TOKEN?}", TokenBuilder = "${WORLDBRIDGE_BUILDER_TOKEN?}", TokenGaia = "${WORLDBRIDGE_GAIA_TOKEN?}" none role tokens (below); each line only when its .env key is set

WorldBridge also reads these keys; the overlay leaves them at their module defaults (commented out in config/paracosm.ini), so add one to bin/OpenSim.ini (or uncomment it in the overlay) to change it. The request queue is described in worldbridge.md ("The request queue"); the rate limit in worldbridge/HARDENING.md.

key module default what it does
MaxConcurrent 1 (1..64) worker threads for the commands that change the world (the work lane): how many run at once. 1 keeps them one at a time, as they always ran; only spawn, spawn_mesh and list were load-tested side by side (at 4), so raise it only after testing your own workload
QueueLimit 64 work-lane requests that may WAIT for a worker; one more is answered 503 with Retry-After at once, and nothing runs
ReadWorkers 2 (1..64) worker threads for the read commands (state, list, heightmap, avatars, chat, inspect, worn, contents, anims, raycast, roam {status:true}), which never wait behind the work lane
ReadQueueLimit 256 read-lane requests that may wait; beyond it, 503
QueueWaitSec 20 (at least 1) a request that has waited longer than this is answered 503 instead of run (its caller has probably given up)
RateLimitPerSec, RateLimitBurst 50, 100 the per-token rate limit (429); RateLimitPerSec = 0 turns it off
DeleteSupersededScripts true set_script deletes the script asset it replaces when that asset is provably unused

WorldBridge's token keys. The role tokens come from .env; AuthToken has no .env key and the overlay does not set it (the token file is the intended way), so it would have to be added to bin/OpenSim.ini by hand, and again after every make_config.py --force:

key .env key role rule
AuthToken none gaia used instead of the token file when set. Refused (with a warning) if shorter than 16 characters or equal to the retired demo token paracosm-worldbridge-token
TokenObserver WORLDBRIDGE_OBSERVER_TOKEN observer read only: state, list, heightmap, avatars, chat, inspect, worn, contents
TokenBuilder WORLDBRIDGE_BUILDER_TOKEN builder observer + spawn, recolor, rez_item, put_notecard
TokenGaia WORLDBRIDGE_GAIA_TOKEN gaia every command

Each token must be 16 characters or more and not the retired demo value, or it is refused. A token listed twice gets its best role. A request with a token whose role is too low gets 403 naming the role needed.

The gaia token: from AuthToken if valid; else read from TokenFile; else (missing, too short, or the demo value) a new random 48-character token is generated and written to TokenFile. To rotate it, stop the server, delete the file, start the server, and restart every MCP client (they read the token once).

config/StandaloneCommon-overlay.ini (merged into config-include/StandaloneCommon.ini)

block key from
[GridInfoService] gridname = "${PARACOSM_GRID_NAME}", gridnick = "${PARACOSM_GRID_NICK}" .env
[UserProfilesService] Enabled = ${PARACOSM_FULL_PROFILES?} (written only when set; upstream: false), ConnectionString = "URI=file:profiles.db,version=3" (upstream: commented out, so OpenSim.db) the overlay: the local profiles service and its own database file, off unless the .env turns it on

Offline messages and profiles

Both run inside the simulator and store to their own SQLite file in bin/ (tools/backup.py copies every *.db).

Offline IM ([Messaging], OpenSim's OpenSim/Addons/OfflineIM). An instant message, inventory offer, group notice or group invitation to someone who is not logged in is stored in offlineim.db and delivered at their next login; the sender of an IM sees "User is not logged in. Message saved." Up to 25 are kept per recipient (MaxOfflineIMs; past it the sender sees "Message not saved"), and messages older than 14 days are dropped each time OpenSim starts. Group notices and invitations are kept too (ForwardOfflineGroupMessages, default true). OpenSim 0.9.3.0 has this storage for MySQL and PostgreSQL only; the sqlite-offlineim patch adds it for SQLite. To turn offline IM off, remove the three [Messaging] lines from config/paracosm.ini and run make_config.py --force.

Profiles ([UserProfiles] in OpenSim.ini, [UserProfilesService] in StandaloneCommon.ini) are OFF by default: set PARACOSM_PROFILE_SERVICE_URL (e.g. http://127.0.0.1:9000) and PARACOSM_FULL_PROFILES=true in the .env and run make_config.py to turn them on, only where the grid port is trusted (see the credentials note). With a ProfileServiceURL the viewer's profile window gets the full set: About and First Life text and pictures, web URL, interests, picks, classifieds, notes on other avatars, and the privacy preferences (IM to email, online status). Without one it gets the basic profile only. The storage is profiles.db; the sqlite-profiles patch fixes four upstream SQLite bugs (the preferences were never stored, a classified could not be deleted). ProfileServiceURL is loopback on purpose: the region is the only caller for local avatars, and a loopback URL does not depend on PARACOSM_EXTERNAL_HOST resolving or on the router reaching its own public address. OpenSim resolves the URL at startup and stops if it cannot. [UserProfiles] AllowUserProfileWebURLs (default true) can stop profiles sending people to web pages. To turn full profiles off, empty ProfileServiceURL and set [UserProfilesService] Enabled false.

The profile service answers JSON-RPC on the simulator's HTTP port with no credentials of any kind (upstream's design; a grid's ROBUST server exposes the same methods). Anyone who can reach that port can read any local avatar's profile, including the email address that the preferences call returns, and can change it. On a world only you reach this changes nothing; on one other machines reach, see Security below and tls.md.

Tested offline (2026-09-27, opensim-patches/sqlite-offlineim/README.md and sqlite-profiles/README.md): the config as make_config.py writes it, loaded by OpenSim's own ConfigurationLoader, starts the offline IM module and the profiles service; messages store, cap and deliver once; profile, notes and preferences calls work over HTTP through OpenSim's own JSON-RPC client. Not tested with a viewer on a running region.

config/Regions.ini.template (written to Regions/Regions.ini)

key value notes
section name [${PARACOSM_REGION_NAME}] the region's name
RegionUUID made once by make_config.py kept on every later run; a fixed UUID would collide between installs
Location ${PARACOSM_REGION_LOCATION} grid cells of 256 m
SizeX, SizeY ${PARACOSM_REGION_SIZE} var regions: 512 or 768
InternalAddress 0.0.0.0 the region's UDP socket listens on every interface
InternalPort ${PARACOSM_PORT} the UDP port
AllowAlternatePorts False
ExternalHostName ${PARACOSM_EXTERNAL_HOST} the address viewers are told to send UDP to

The template makes one region. For more, add a section per region to bin/Regions/Regions.ini by hand, AFTER the existing one: its own name, a fresh RegionUUID, a free Location (a 512 region at 1001,1000 covers cells 1001-1002 x 1000-1001), and its own InternalPort. A new region joins the estate named by DefaultEstateName (the overlay's [Estates] block). One WorldBridge channel serves them all; the first region loaded is the default one and keeps the unsuffixed state files (worldbridge.md, "Several regions").

Security-relevant settings

setting default risk advice
[WorldBridge] AllowConsole (WORLDBRIDGE_ALLOW_CONSOLE) false the console op runs ANY OpenSim console command: shutdown, create user, reset user password, load oar, terrain load of any path. Console commands are not confined by FileRoots leave it off unless the resident needs it. backup.py needs it on for the OAR, IARs and terrain (it copies the databases and state without it); run_server.py stop uses it for a clean shutdown and otherwise terminates the process
[WorldBridge] FileRoots (WORLDBRIDGE_FILE_ROOTS) worldbridge-files (beside OpenSim) the file-taking ops read (or, for bundle, write) the server's disk a file outside every root is refused: spawn_mesh, upload_texture, upload_sound and store_anim answer 403 file outside FileRoots for their file; a spawn_mesh lods file outside fails the request (500); a physics file outside falls back to box physics; bundle skips writing its file. Roots are ;-separated; a relative root is resolved against bin/; each is created if missing
the gaia token (bin/WorldBridge.token) generated on the first start full control of the world keep the file private; give an AI a lower role token (WORLDBRIDGE_OBSERVER_TOKEN, _BUILDER_TOKEN) when it does not need gaia
WorldBridge's bind address 127.0.0.1 none by default; any local process or user with the token can connect never tunnel, proxy or port-forward 9002 (no TLS; the rate limit and the request queue bound load, not access; tls.md has the only shape to consider if an AI must run elsewhere)
the REST console (PARACOSM_CONSOLE_USER/PASS, port 9001) off (empty user) plain HTTP on all interfaces; the credentials travel in clear leave it off unless you need it; if on, set a strong password and firewall 9001. It is off only while the USER is empty
bin/OpenSim.ini written by make_config.py holds the estate owner's password and the console password in clear treat it like .env: never share, commit or ship it
[Permissions] region_manager_is_god true in the overlay any estate manager can god-edit anything on the region make estate managers only of people you trust (grant_estate_manager)
PARACOSM_VIEWER_TOKEN unset: a fresh random token per viewer launch, in a per-user file the LEAP bridge (loopback) drives the viewer; /snapshot writes only inside its snapshot folder keep the token file private; pin a token only on purpose
PARACOSM_EXTERNAL_HOST 127.0.0.1 changing it is what makes the world reachable from other machines expose only port 9000 (TCP and UDP); tls.md
the profile service ([UserProfilesService] Enabled) on its JSON-RPC methods on the simulator port (9000, all interfaces) take no credentials: anyone who reaches the port can read and change any local avatar's profile and read the email in their preferences on a world other machines reach, decide whether that is acceptable; turning full profiles off is above. The simulator port listens on every interface even while PARACOSM_EXTERNAL_HOST is 127.0.0.1, so a machine on the same network can reach it unless the firewall stops it