AudioProxy.Config (audio_proxy v0.4.0)

Copy Markdown View Source

Runtime configuration, read from AP_-prefixed environment variables.

The variable surface is defined by docs/audio-proxy-api-v1.md §6. Values are parsed and validated exactly once, during AudioProxy.Application.start/2, and stored in :persistent_term — reads on the request path are then a cheap global lookup with no GenServer in the way.

Malformed values raise AudioProxy.Config.Error at boot, so a misconfigured container fails immediately instead of serving traffic with surprising defaults. Every error message names the offending variable.

Variables

VariableTypeDefault
AP_KEYhex-encoded binary, ≥ 32 bytes decodedunset (nil)
AP_SALThex-encoded binaryunset (nil)
AP_ALLOW_INSECUREbooleanfalse
AP_SOURCE_ALLOWLISTcomma-separated list[]
AP_LOCAL_ROOTexisting directory, not /unset (nil) — local sources disabled
AP_VARIANT_STOREscheme-tagged URL (file:///path, s3://bucket)unset (nil) — no variant cache
AP_MAX_CONCURRENCYpositive integerSystem.schedulers_online/0
AP_MAX_PROBE_CONCURRENCYpositive integer4 × AP_MAX_CONCURRENCY
AP_QUEUE_SIZEnon-negative integer32
AP_READY_QUEUE_THRESHOLDnon-negative integer, ≤ AP_QUEUE_SIZEhalf the queue, floored, min 1 (0 disables)
AP_MAX_SRC_BYTESpositive integer2_000_000_000
AP_MAX_VARIANT_BYTESpositive integerthe effective AP_MAX_SRC_BYTES
AP_RENDER_TIMEOUTpositive integer (seconds)300
AP_PROBE_TIMEOUTpositive integer (seconds)10
AP_SERVE_MODEredirect | proxy:redirect
AP_PRESIGN_TTLpositive integer (seconds)300
AP_LOG_LEVELdebug | info | warning | error:info
AP_METRICS_BINDIP address literal127.0.0.1
AP_METRICS_PORTpositive integer9568
AP_S3_ENDPOINThttp(s)://host[:port]unset (nil) — AWS proper

The listener port is read from AP_PORT, falling back to PORT (which the worktree workflow sets to the branch's hashed port), then to 4000.

The AWS variables

S3 credentials are the one thing here not AP_-prefixed: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN and AWS_REGION (or AWS_DEFAULT_REGION). Every tool that produces credentials already writes those names, and renaming them would mean an operator translating a working environment into this proxy's dialect for no gain.

They are read here rather than left to ex_aws's own resolution, which would otherwise reach into application env and, on EC2, into IMDS. Keeping them in this map means the whole configuration surface is still one thing, validated once, at boot — and that what the proxy will use is what the operator set, not whatever a metadata endpoint happened to answer. AudioProxy.S3 hands them to ex_aws per request.

Validated all-or-nothing: a half-configured client fails at its first request instead of at boot, which is the failure this module exists to prevent.

Summary

Types

The S3 settings, as one group.

t()

Functions

Returns the whole stored config.

Builds a validated config map from an environment map, without storing it.

Returns one stored config value.

Parses and validates the environment, then stores the result for all/0.

The levels AP_LOG_LEVEL accepts.

Replaces the stored config wholesale.

The addressing styles AP_S3_ADDRESSING accepts.

The serve modes AP_SERVE_MODE accepts.

Types

s3()

@type s3() :: %{
  region: String.t() | nil,
  access_key_id: String.t() | nil,
  secret_access_key: String.t() | nil,
  session_token: String.t() | nil,
  endpoint: URI.t() | nil,
  addressing: :virtual | :path,
  ca_bundle: String.t() | nil
}

The S3 settings, as one group.

All of region, access_key_id and secret_access_key are nil together when S3 is not configured — the partial set is refused at boot, so anything that finds one of them finds all three.

addressing and ca_bundle are independent of the credentials: both have a usable value whether or not S3 is configured at all.

t()

@type t() :: %{
  port: pos_integer(),
  key: binary() | nil,
  salt: binary() | nil,
  allow_insecure: boolean(),
  source_allowlist: [String.t()],
  local_root: String.t() | nil,
  variant_store: AudioProxy.VariantStore.config() | nil,
  max_concurrency: pos_integer(),
  max_probe_concurrency: pos_integer(),
  queue_size: non_neg_integer(),
  ready_queue_threshold: non_neg_integer(),
  max_src_bytes: pos_integer(),
  max_variant_bytes: pos_integer(),
  render_timeout: pos_integer(),
  probe_timeout: pos_integer(),
  serve_mode: :redirect | :proxy,
  presign_ttl: pos_integer(),
  log_level: :debug | :info | :warning | :error,
  metrics_bind: :inet.ip_address(),
  metrics_port: pos_integer(),
  s3: s3()
}

Functions

all()

@spec all() :: t()

Returns the whole stored config.

build!(env)

@spec build!(map()) :: t()

Builds a validated config map from an environment map, without storing it.

This is the function tests exercise for parsing and validation. It writes nothing to :persistent_term; it does touch the filesystem where a value is a filesystem claim — AP_LOCAL_ROOT must be an existing directory, and a file:// variant store is probed for writability.

It does not touch the network. An s3:// variant store is parsed and shape-checked here and probed by load!/1, after the config is stored — because the probe is an S3 request, and AudioProxy.S3 reads its credentials, endpoint and addressing from the very map being built. The asymmetry with the file:// probe is that one: a path is provable from itself, a bucket is not.

get(key)

@spec get(atom()) :: term()

Returns one stored config value.

load!(env \\ System.get_env())

@spec load!(map()) :: t()

Parses and validates the environment, then stores the result for all/0.

Called from AudioProxy.Application.start/2. Raises Error on the first unusable value.

log_levels()

@spec log_levels() :: [atom()]

The levels AP_LOG_LEVEL accepts.

put_all(config)

@spec put_all(t()) :: t()

Replaces the stored config wholesale.

Used by load!/1 at boot and by AudioProxy.ConfigHelper in tests. Nothing on the request path should call this.

s3_addressing_styles()

@spec s3_addressing_styles() :: [atom()]

The addressing styles AP_S3_ADDRESSING accepts.

serve_modes()

@spec serve_modes() :: [atom()]

The serve modes AP_SERVE_MODE accepts.