Configuration¶
astar-server reads one TOML file, conventionally node.toml. Start from the
template in the repository:
This file is secret-free by design
No passwords, no tokens, ever. [secrets] declares where the credential
comes from; the credential itself lives in an environment variable or is
POSTed to the loopback control port at runtime. See
Secrets below.
Two sections are required — [listener] and [control] — plus [secrets] if
you want the node to authenticate anywhere. Everything else is optional.
A minimal config¶
bind = "0.0.0.0:4569" is an internet-reachable socket once UDP 4569 is
forwarded, on a daemon that can bridge a local radio and key a transmitter.
Start closed:
[listener]
bind = "0.0.0.0:4569"
answer = "auto"
max_calls = 2
auth = "required" # (1)!
allowed_nodes = ["<node id allowed to call you>"] # (2)!
[control]
bind = "127.0.0.1:8730"
[secrets]
source = "env"
- Every inbound caller is MD5-challenged against the secret you provisioned
for that node id — see Secrets (
ALLSTAR_PEER_<NODE>). An unknown username is rejected. - A second, independent gate: a caller whose node id is not on the list is rejected at call setup, before answer. Fill it in — a placeholder left in place admits nobody, which is the safe way to fail.
auth = "off" with no allowlist is an open node
That combination — which is what an omitted allowed_nodes plus
auth = "off" gives you — admits every caller on the internet, subject
only to max_calls. It is a deliberate opt-in for a closed lab network or a
loopback bind, never a starting point:
If you want an open node on a public bind, that is your call.
[listener] — the inbound socket¶
| Key | Values | Meaning |
|---|---|---|
bind |
"0.0.0.0:4569" |
Where the IAX2 socket listens. UDP 4569 must be forwarded from the internet for inbound calls to land. |
answer |
"auto" | "manual" |
Answer inbound calls automatically, or wait for POST /answer. |
max_calls |
integer | Cap on simultaneous inbound calls. |
auth |
"required" | "optional" | "off" |
Authentication policy for inbound callers. |
allowed_nodes |
list of node ids | Optional allowlist. Omitted or empty means every caller is admitted, subject to auth and max_calls. A caller not on a non-empty list is rejected at call setup, before answer. Set it unless you mean to run an open node. |
[register] — register with a registrar¶
Optional. Omit the whole section if the node should not register.
peer is the registrar's address; take the host from your existing rpt.conf
register => <node>:<pw>@<HOST> line. node_id is your node number and is also
the register username. The password is not here — see Secrets.
[control] — the control channel¶
Keep it on loopback. This is where commands and secrets are POSTed, and it can key the transmitter. See Control API.
[bridge] — audio topology¶
Absent means mode = "bridge".
| Key | Default | Meaning |
|---|---|---|
mode |
"bridge" |
"handset" (1:1 with the local radio), "bridge" (pure conference, local radio off), or "conference" (the same mix-minus engine). |
mix_minus |
true |
Each member hears everyone but itself. false gives a full mix, so members hear themselves — useful for loopback testing. |
include_local_radio |
false |
Add the local microphone as a conference source and feed the local speaker the sum of all members. |
The topology can be re-wired live with POST /bridge.
[audio] — device selection¶
Optional; omit for system defaults.
[audio]
input = "USB" # substring match on the device name
output = "USB"
backend = "cpal" # or "none" for a headless host with no audio devices
backend = "none" selects a hardware-free null backend, which is what a
container or a headless VPS wants.
codec_policy — codec negotiation¶
A top-level key, not a section, and "prefer_slin16" is the default — omit
it entirely and the node negotiates wideband:
| Value | Offers, best first |
|---|---|
"ulaw_only" |
µ-law, A-law — the pre-slin wire behaviour |
"allow_slin" |
µ-law, A-law, slin (slin only if the peer asks for it) |
"prefer_slin" |
slin, µ-law, A-law |
"prefer_slin16" |
slin16, slin, µ-law, A-law — the default |
prefer_slin negotiates 8 kHz 16-bit linear audio (~128 kbps) with peers that
permit it (ASL3: allow = slin) and falls back to µ-law. prefer_slin16 adds
16 kHz wideband linear (~256 kbps) above that, and switches the station's own
audio pipeline — capture, playback and mixing — to 16 kHz.
A µ-law-only node is still answered in µ-law. Negotiation honours whatever
FORMAT a caller asks for whenever the node can carry it, so a ClearNode or an
ASL3 peer that only speaks µ-law gets µ-law on a prefer_slin16 node, and it
can share a conference with a wideband client — each leg resamples at its own
codec edge. A caller that asks for µ-law gets µ-law even if it lists slin16 in
its CAPABILITY — the peer's stated FORMAT wins when it is in the common set,
because listing a codec says the peer can transcode it, not that it wants it
on this link. What the default changes is the caller that actually asks for
slin16 (the astar clients always do): it is answered in slin16 instead of being
quietly downgraded.
Set codec_policy = "ulaw_only" to pin the old behaviour — worth doing on a
bandwidth-constrained link, since each wideband leg costs roughly twice a slin
leg and eight times a µ-law one.
A peer with no codec in common with the policy is rejected at call setup with
CAUSE Unable to negotiate codec and CAUSECODE 65, the same as Asterisk. astar
implements µ-law, A-law, slin and slin16 and no others, so a
disallow=all / allow=gsm peer lands here. The same applies in the other
direction: if a node this server dials answers with a format the policy never
offered, the link is hung up with that cause rather than transmitting outside
the operator's cap.
[announce] — voice and CW announcements¶
Optional. Covers a periodic station ID and per-event announcements.
[announce]
enabled = true
id_mode = "cw" # "cw" (Morse), "tts", or "off"
id_interval_secs = 600 # 0 or omitted disables the periodic ID
cw_wpm = 20
cw_tone_hz = 800.0
cw_keys_when_idle = true
[announce.tts]
enabled = true
binary = "piper"
voice = "/path/to/voice.onnx"
timeout_ms = 4000
gain_db = -6.0 # negative attenuates a voice that renders hot
[announce.events.incoming_call]
enabled = true
destination = "to_air" # "to_air" or "to_monitor"
Event names are incoming_call, hangup, registered, register_failed and
answered.
cw_keys_when_idle transmits
A periodic station ID that keys the transmitter is still a transmission. Configure it as the licensed operator responsible for the station, and know what your node is connected to.
[dtmf] — DTMF command execution¶
Off by default, deliberately.
Enabling this lets any connected member command your links
DTMF * sequences map to link commands (*1/*2/*3). Turn it on only if
you are comfortable with everyone on the conference being able to connect
and disconnect links.
[links] — per-target dial profiles¶
"wt-guest" is the AllStarLink web-transceiver guest shape, needed for app
nodes whose guest context only exposes the WT extension. It requires a
[portal] section.
[portal] — AllStarLink portal account¶
Only needed for wt-guest link dials, which require a freshly minted
web-transceiver token.
[portal]
user = "<your portal callsign>"
node = "<a node the account owns>"
credential_env = "ALLSTAR_PORTAL_PASS"
credential_env is the name of an environment variable, not the password.
The value is resolved once at startup and never logged.
[wireguard] — link transport¶
Optional. Absent means plain UDP. When present, the link runs over a userspace WireGuard tunnel.
[parrot] — parrot tuning¶
Only consulted when the bridge is in parrot mode; harmless otherwise.
Secrets¶
The [secrets] section declares the source, never the material.
The daemon reads two environment variables at startup:
Nothing is read at startup; you POST the credential to the loopback control port after the daemon is up:
The reply is a bare ok — the secret has no path back out.
An inline secret key, for a deployment where the operator mounts a
private node.toml (for example /etc/iaxnode/node.toml) with restricted
permissions. Repo-tracked templates ship this empty, and it should stay
that way. If you can avoid it, avoid it.
Never commit, paste or screenshot a node secret
The daemon goes to some length to keep credentials out of Debug output,
logs, snapshots, events and error messages. That effort is wasted the moment
a password lands in a config file you push, or in a bug report.