Skip to content

Configuration

Stagemgr reads its configuration from three places, and which one a setting belongs in is not a matter of taste:

Kind of setting Where
Non-secret deployment settings -- host names, mount point, business rules, email addresses, house facts config/server.yml
Connection details and secrets the process environment (.env in development and test) or encrypted credentials
Anything the box office maintains -- theaters, venues, productions, users the database, through the admin UI

Scaffolding the files

The real config files are gitignored; the repository tracks .example templates. One task copies them:

bundle exec rake setup:config

It creates each config/*.yml from its config/*.yml.example, plus .env from .env.example, and never overwrites a file that already exists. It does not load Rails -- it cannot, since config/database.yml is one of the files it produces. bin/docker-entrypoint runs it on every container start.

Generated Read by Notes
config/database.yml Active Record Every value is environment-driven; the same file works native, in Docker and in CI
config/server.yml config/environments/{development,production}.rb The main deployment file, documented below
config/ticket_print.yml config.x.tktprint Ticket-printing service URL, per environment
config/schedule.yml resque-scheduler Already tracked, so setup:config leaves it alone
config/my_emma_credentials.yml nothing A dead legacy file. MyEmma credentials come from MY_EMMA_* or credentials
.env dotenv (development, test) and docker compose

The rest of the setup: namespace

Task Boots Rails? Does
setup:config no The scaffolding above
setup:site[slug] no Copies sites/example/ to sites/<slug>/ and inserts site_theme: <slug> under all: in config/server.yml (Theming)
setup:secret_key_base no Generates SECRET_KEY_BASE in .env if it is blank or missing; never replaces an existing value
setup:bootstrap yes Creates this environment's database, loads db/schema.rb, applies pending migrations, seeds only when there are no users
setup:admin yes Creates or updates the first administrator (ADMIN_EMAIL/ADMIN_PASSWORD skip the prompts)
setup:theater yes Creates the first theater and venue and associates the administrator
setup:payments yes Prompts for Stripe keys and writes them to .env
setup:demo_data yes One sample production with performances, ticket classes and allocations
setup:doctor yes Health check; exits 1 on problems
setup:wizard yes All of the above, interactively, in order

Every task is idempotent: re-running one reports what already exists instead of failing.

config:setup is deprecated

The old name still works and forwards to setup:config, printing a deprecation notice. Use setup:config.

Why setup:bootstrap and not db:prepare

db:prepare only reaches for db/schema.rb when it hits NoDatabaseError. The Docker stack hands Rails a database MySQL has already created (MYSQL_DATABASE), so db:prepare sees an existing-but-empty database and replays every migration back to 2009 -- slow, fragile on MySQL 8, and it rewrites the committed db/schema.rb on the way out. setup:bootstrap loads the schema instead, and migrates only what is actually pending.

.env

.env is gitignored and read by two different things: docker compose, and dotenv-rails -- which is loaded in the development and test groups only. In production the same variables must come from the process environment (Compose environment:, Passenger SetEnv, a systemd EnvironmentFile).

Delete blank lines, do not leave them

A variable set to the empty string is ignored by AppSecrets rather than shadowing a credential -- that is deliberate, and it is the fix for the outage described in Troubleshooting. But a blank line is still noise, and setup:doctor warns about every one it finds: five in a .env copied straight from the template, four once setup:secret_key_base (or the wizard) has filled in SECRET_KEY_BASE. If you are not using a key, delete the line.

Database

Variable Default Purpose
DATABASE_USER stagemgr_dev MySQL account. Must not be root -- the mysql image refuses to start with MYSQL_USER=root
DATABASE_PASSWORD password Its password
DATABASE_HOST 127.0.0.1 Compose overrides this to mysql
DATABASE_PORT 3306
DATABASE_NAME stagemgr_dev Development/production database
TEST_DATABASE_NAME stagemgr_test RSpec and Cucumber. Give every concurrent checkout its own -- the suites truncate every table between examples
DATABASE_POOL 5 Active Record connection pool
DATABASE_SOCKET -- Connect over a Unix socket instead of TCP (a native Homebrew MySQL)
MYSQL_ROOT_PASSWORD changeme-root Compose only: the MySQL container's root password. Deliberately not DATABASE_PASSWORD

In production the database name still defaults (stagemgr_production) but the user and password do not: config/database.yml.example uses ENV.fetch without a default there, so booting production against stagemgr_dev/password because a variable was unset fails loudly instead.

Redis

Variable Default Purpose
REDIS_URL redis://127.0.0.1:6379/0 Resque queues and Rack::Attack counters. Compose overrides it to redis://redis:6379/0

Rails

Variable Purpose
RAILS_ENV development, test or production
SECRET_KEY_BASE Session/cookie signing key. rake setup:secret_key_base fills it in; bundle exec rails secret generates one by hand
RAILS_MASTER_KEY Alternative to shipping config/master.key/config/credentials/<env>.key as a file. See Credentials
STAGEMGR_SUB_URI Sub-path the app is mounted at, e.g. /tickets. Compose passes it through as RAILS_RELATIVE_URL_ROOT; it must agree with server.yml's sub_uri
RAILS_SERVE_STATIC_FILES Serve public/ from Rails instead of from Nginx/Apache
RAILS_LOG_TO_STDOUT Log to stdout for a container or journald log driver
RAILS_MAX_THREADS, WEB_CONCURRENCY, PORT Puma only; the Docker image runs Passenger
RAILS_RAISE_ERRORS Re-raise controller exceptions instead of rendering the friendly error page (app/controllers/application_controller.rb)
BACKTRACE Unsilenced backtraces
SKIP_REQUIRED_SECRETS_CHECK Let the production boot check pass with secrets missing. Only for a build or maintenance process that never serves a request

Secrets

Each of these is also readable from encrypted credentials -- see Credentials & secrets for the full registry and the resolution order.

Variable Needed when
STRIPE_SECRET_KEY payment_processing.default_gateway (or default_recurring_gateway) is stripe
STRIPE_SIGNING_SECRET You receive Stripe webhooks (whsec_…)
POSTMARK_API_TOKEN email.delivery_method is postmark
RESQUE_ADMIN_PASSWORD Always, in production -- see the Resque dashboard
MY_EMMA_USERNAME, MY_EMMA_PASSWORD, MY_EMMA_ACCOUNT_ID Using MyEmma mailing lists. All three are required together; with any of them absent the integration disables itself
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY ActiveStorage on S3 (uncomment the matching block in config/storage.yml). Blank means local disk
PAYPAL_LOGIN, PAYPAL_PASSWORD, PAYPAL_SIGNATURE, PAYPAL_PEM_FILE, PAYPAL_EXPRESS_LOGIN, PAYPAL_EXPRESS_PASSWORD Legacy PayPal gateways only

Development runs MyEmma read-only

config/environments/development.rb calls MyEmma.read_only! after setting credentials: reads work, writes are logged and skipped, whatever account the credentials point at.

docker compose

Variable Default Purpose
STAGEMGR_PORT 8080 Host port for the app
STAGEMGR_BIND 127.0.0.1 Interface the published ports bind to
MYSQL_PORT 3306
REDIS_PORT 6379
HOST_UID, HOST_GID -- Linux only: renumber the container's app user so bind-mounted files come back owned by you
DB_PREPARE_ON_BOOT true in development, false in production Run setup:bootstrap on container start. Accepts true/1/yes, any case
RESQUE_QUEUES * Queues the containerized worker consumes

First-run and CI

Variable Purpose
ADMIN_EMAIL, ADMIN_PASSWORD Skip setup:admin's interactive prompts (scripted installs)
MAIL_DUMP_DIR spec/mailers/theme_render_spec.rb writes every rendered email body under this directory, for before/after diffing

Reports run from cron

db/reports/*.sh are plain shell + SQL, not Rails, and take their configuration from the cron environment:

Variable Default Purpose
REPORT_EMAIL none -- the scripts abort without it Recipient and envelope sender for mail_*.sh
REPORT_SUBJECT_TAG StageMgr Prefix in the subject line, e.g. [StageMgr] Unfulfilled Flexpasses
MY_CNF $HOME/.my.cnf MySQL defaults file the HUD count scripts (db/reports/hud/*.sh) authenticate with

config/server.yml

Copied from config/server.yml.example. Keys under all: apply to every environment; an environment block (development:, test:, production:) overrides them by deep merge.

Identity and mount point

Key Meaning
app_name Display name in page titles and as the fallback house name before a theater row exists
host Host used to build absolute URLs (email links, calendar feeds)
host_protocol http or https for those URLs
sub_uri Mount point when the app is not served from /, e.g. /tickets. Passed to ActionMailer as :script_name, so mail sent from a worker gets the prefix exactly once. Leave it blank on a standalone install
secure_root_url Absolute base for the links in the generated public calendar feed (ResqueJobs::GenerateCalendar)
static_cache_dir Where that generated calendar is written
ext_site_wrapper Layout for the public order pages. standalone for a self-contained install; the name of an externally supplied layout when a marketing site wraps the app
site_theme Directory under sites/ holding this house's editorial copy. Commented out = generic copy (Theming)

Sales rules

Key Meaning
order_expiration_in_minutes How long an unpaid order is held
restrict_sales_due_to_time_at_minutes_before Stop public sales this many minutes before curtain (inventory routes to the box office)
minutes_before_performance_close_to_third_party_sales The same cut-off for theater users
restrict_sales_due_to_capacity_at Seats remaining at or below which self-service sales stop; also drives "Call box office" on the calendar
max_ticket_dropdown Largest quantity a patron can pick in one order
resourced_default_runtime_minutes Assumed running time when productions.running_time is blank
calendar_display.warning_at, calendar_display.critical_at Calendar heatmap thresholds, as a percentage of seats remaining
allowed_order_task_suppressions Which order tasks a payment type may suppress

Reports and storage

Key Meaning
hud_export_directory Where the HUD export jobs write their text files
archive_directory Destination for data-retention archives. Blank disables the archive/prune pipeline -- the jobs refuse to run rather than delete rows nothing exported. Must be covered by your backups
max_report_date_range_days Widest date range a report may request
report_timeout_seconds Report query timeout
report_frequent_customer_at, report_frequent_customer_range_days "Frequent customer" threshold on the house management report

theater: -- facts about the house

Plain, non-editorial facts printed on public order pages and in email. Every one may be left blank: the sentence that would have used it is omitted rather than printed empty. Anything with a voice to it belongs in a site theme instead. The full key list, with fallbacks, is in Theming → the theater: block.

The house's name is not a key here

It is the name of the Default theater row in the database, so the one proper noun that appears in dozens of places has a single source the box office already maintains. Renaming that row renames the house everywhere.

my_emma:

Key Meaning
newsletter_group, coupon_group Names of the MyEmma groups a patron joins when they tick the mailing-list box
create_production_groups, create_theater_groups Whether new productions/theaters get their own Emma groups

The two group-name keys have three distinct states:

State Behaviour
Key absent from server.yml The historical names Newsletter and Flash Offers, so a config predating these keys keeps behaving as it did
Key present but blank Nobody is added to that group -- an explicit opt-out, not a lookup for ""
Key present with a name That group

Group ids are resolved by name and cached for five minutes: the API resolves a name by listing every group in the account, so a lookup per order is expensive -- but a permanent memo would go on serving the id of a group that has since been renamed. When MyEmma is configured and writable, setup:doctor checks that each configured name actually exists in the account and warns if it does not.

email:

Key Meaning
delivery_method postmark, file (writes to tmp/mails -- the development default, so a fresh install needs no mail credentials), sendmail, test
addresses.box_office Default From: for patron mail, and TheaterInfo#box_office_email
addresses.flex_pass_notifications Flex pass activity
addresses.membership_notifications Membership activity
addresses.supervisor_notifications High-priority system notices
addresses.wheelchair_conversion_notifications Seat accessibility conversions
addresses.software_address System/administrative mail; the last-resort sender
addresses.exception_notifications Crash reports from the exception notifier

exception_notifications falls back to software_address. With neither configured the exception-notification middleware is left out of the stack entirely and a warning is logged: EmailNotifier would otherwise build a mail with no recipients and raise from inside the middleware while it was handling the real exception, replacing a useful 500 page with a confusing one.

payment_processing:

Key Meaning
default_gateway stripe, paypal, bogus (the development/test default)
default_recurring_gateway Gateway for subscriptions
additional_card_types Comma-separated extra card brands to accept
test_credit_card, test_card_brand Values the non-production forms pre-fill

RequiredSecrets reads these: a stripe gateway makes STRIPE_SECRET_KEY mandatory in production, and delivery_method: postmark makes POSTMARK_API_TOKEN mandatory. See Credentials.

The Resque dashboard

config/routes.rb mounts the Resque web UI at /admin/resque with no other authentication in front of it. Its HTTP basic-auth password comes from RESQUE_ADMIN_PASSWORD or the resque_admin_password credential:

Situation Result
A password is configured Basic auth, compared with secure_compare
None, in production Denied to everyone (fail closed). An open queue dashboard exposes job arguments -- order ids, email addresses -- and offers a one-click "Clear failed jobs"
None, outside production Open to anyone who can reach it

A resque_admin_password: still sitting in config/server.yml keeps working but warns once at boot. It is the only key with that plaintext fallback left.

The test environment reads the example file

config/environments/test.rb loads the tracked config/server.yml.example, not the config/server.yml you copied it to, so every checkout and every CI run tests against the same values instead of whatever a developer's gitignored copy happens to say. Cucumber forces RAILS_ENV=test too, so it does the same.

That makes the test: block part of the test suite. Its values are deliberate sentinels -- 555-BOX-OFFICE, PICKUPWINDOW.TEST, https://WEBSITE.TEST, Test Theater Box Office, Test Director -- obviously fake, so a spec cannot pass against a developer's real config by accident. Specs and Cucumber features assert them.

Adding a key the tests need

Add it to the test: block of config/server.yml.example, not to your own config/server.yml. test.rb also forces ext_site_wrapper to standalone regardless of what the file says.

Keys that are gone

Key Why
filestore_hash (server.yml) Read by nothing. Deleted from all three blocks of the example
email.addresses.online_errors (server.yml) Added in 2011 and never read by any code; unhandled exceptions go to exception_notifications. Deleted from the example -- a value left in your own file is ignored
root_url (server.yml) Read by nothing -- only secure_root_url is. Still present in the example's environment blocks; do not rely on it
resque_admin_password (server.yml) Now a deprecated fallback that warns at boot; use RESQUE_ADMIN_PASSWORD or the credential
STRIPE_PUBLISHABLE_KEY Read by nothing; never prompted for
config.external_site_root (development.rb) Pointed at a developer's home directory and was read by nothing