Practical reference for running a TollGate router day-to-day. Every
command below is typed into the router shell over SSH (or a serial
console). The tollgate binary ships inside the tollgate-wrt
package and talks to the running service over a Unix domain socket at
/var/run/tollgate.sock.
Prerequisite: the TollGate service must be running. If it is not, commands that route through the socket report
failed to communicate with TollGate service. Start it withtollgate start(or/etc/init.d/tollgate-wrt start).
tollgate status Service status (uptime, module health)
tollgate start Start NoDogSplash + TollGate
tollgate stop Stop NoDogSplash + TollGate
tollgate restart Restart NoDogSplash + TollGate
tollgate health Component health check
tollgate version Version + build information
tollgate logs [-n N] [-f] TollGate log lines (wraps logread)
tollgate wallet balance Total wallet balance (sats)
tollgate wallet info Per-mint breakdown
tollgate wallet fund [cashu-token] Add funds from a Cashu token
tollgate wallet drain cashu Export ALL funds as Cashu tokens
tollgate network private status Private Wi-Fi: SSID, password, on/off
tollgate network private enable Turn private Wi-Fi on (2.4 + 5 GHz)
tollgate network private disable Turn private Wi-Fi off
tollgate network private rename <ssid> Change the private SSID
tollgate network private set-password [pw] Change the private password
tollgate upstream scan Scan all radios for networks
tollgate upstream connect <SSID> [pass] Connect to an upstream network
tollgate upstream list Show configured upstream STAs
tollgate upstream remove <SSID> Remove a disabled upstream STA
tollgate config get Print current config + identities
tollgate config set <key> <value> Set one value by dot-path
tollgate config schema Print the full config schema
tollgate config save <json> Replace config.json wholesale
tollgate config save-identities <json> Replace identities.json wholesale
Every command accepts the global --json (-j) flag for
machine-readable output. See JSON output below.
tollgate statusReports whether the service is running, its version and uptime, and whether the config, wallet, and upstream-network subsystems are healthy:
running: true
version: v0.5.0
uptime: 4h12m
config_ok: true
wallet_ok: true
network_ok: true
network_ok reflects the upstream connectivity probe, not the private
Wi-Fi radio state.
tollgate start # starts NoDogSplash first, then tollgate-wrt
tollgate stop # stops tollgate-wrt first, then NoDogSplash
tollgate restart # restarts both in start orderThese wrap the OpenWrt init scripts
(/etc/init.d/nodogsplash, /etc/init.d/tollgate-wrt). Stopping the
service does not kick off connected customers immediately —
NoDogSplash keeps existing authorisations until their MAC entries
expire — but no new sessions can be purchased while the service is
down.
What happens to those authorisations is not left to NoDogSplash's timer
once the module comes back: at startup the module reads NoDogSplash's
client list and closes the gate of every client NoDogSplash still
authorises that it holds no session for (a restart loses every session —
they live in the process that died). The client must buy again, and the
log says so per MAC (startup reconciliation), because an inherited
authorisation with nothing metering it is free internet. See
docs/architecture/startup-nds-reconciliation-decision.md.
tollgate healthA lighter check than status: prints status: ok, the config/wallet
flags, and uptime. If the socket is unreachable it reports
Service not reachable and exits non-zero, making it suitable for a
monitoring cron job:
# example: alert if the service is down
tollgate health >/dev/null 2>&1 || echo "TollGate is DOWN on $(date)"tollgate versionTollGate Version
version: v0.5.0
commit: a1b2c3d
build_time: 2026-07-03T10:00:00Z
go_version: go1.23.0
openwrt_version: OpenWrt 24.10.0
tollgate logs # all TollGate log lines
tollgate logs -n 50 # last 50 lines
tollgate logs -f # follow (like tail -f)
tollgate logs -n 100 -f # last 100, then followThis wraps logread -e tollgate, so it captures every line the
service writes via syslog. For a raw, unfiltered view of everything
on the router (useful when debugging NoDogSplash or DHCP alongside
TollGate), use logread directly:
logread -fThe wallet holds Cashu ecash across one or more mints. Customers pay into it; the payout routine sweeps profits to configured Lightning addresses on a per-mint schedule.
tollgate wallet balanceTotal wallet balance: 1284 sats
This is the sum across all mints that currently hold funds,
including mints no longer listed in accepted_mints.
tollgate wallet infoWallet info - Total: 1284 sats across 2 mints
mint_balances:
https://mint.coinos.io: 900
https://mint.minibits.cash: 384
mint_count: 2
total_balance: 1284
Only mints with a non-zero balance are shown. Use this when you need to see where the funds sit (e.g. before draining or when investigating a per-mint payout failure).
# with the token as an argument
tollgate wallet fund cashuA...
# or interactively (the CLI prompts and reads from stdin)
tollgate wallet fund
Paste your Cashu token: <paste token>The token is sent to the service, which decodes it, swaps it with the mint to verify the proofs are unspent, and stores the new proofs. On success:
Successfully funded wallet with 500 sats
This is the same receive path customer payments take, so it also works for topping up from an external Cashu wallet.
tollgate wallet drain cashuThis removes ALL funds from the wallet. The CLI prints a warning and asks for confirmation:
⚠️ WARNING: Draining the wallet will remove ALL funds from the wallet!
The funds will be converted to Cashu tokens that will be saved to a file.
Once drained, the tokens are OUT of the wallet and must be stored securely.
Are you sure you want to drain the wallet? (y/N):
After confirmation, every mint with a balance is drained into a
separate Cashu token. The tokens are written to a timestamped file
(wallet_drain_2026-07-05_14-30-00.txt) in the current directory and
also printed to the terminal:
Wallet Drain Results:
====================
Total drained: 1284 sats
✓ Tokens saved to: wallet_drain_2026-07-05_14-30-00.txt
Token 1:
Mint: https://mint.coinos.io
Balance: 900 sats
Token: cashuA...
Token 2:
Mint: https://mint.minibits.cash
Balance: 384 sats
Token: cashuA...
For scripts and other non-interactive callers, --yes (or -y)
skips the confirmation prompt:
tollgate wallet drain cashu --yesDraining each mint is an independent, irreversible operation. If one mint's drain fails after another's succeeded, the command reports a partial result: it prints and saves the tokens that were produced, lists the per-mint failures, and exits non-zero. Check the output carefully — a partial drain means some funds left the wallet as tokens while others stayed in it.
Every successfully produced token is also appended (before the next
mint is attempted) to /etc/tollgate/wallet-drain-journal.jsonl, an
append-only safety copy in case the terminal session or the device is
lost before the tokens are secured. Sweep and clear that file the same
way you treat the drain output.
Cancellation and failure are distinguishable from success by exit
code: 0 only when the whole drain succeeded; a declined or
unanswerable prompt (e.g. stdin at EOF) and any full or partial drain
failure exit non-zero.
Treat the output file as cash — anyone who reads a token string can spend it. Copy it somewhere safe and delete the plaintext once redeemed.
Lightning drain (
tollgate wallet drain lightning) is not yet implemented; onlycashuis supported.
The private Wi-Fi is the network you (the operator) connect to, distinct from the captive-portal guest network. Commands operate on both the 2.4 GHz and 5 GHz private interfaces simultaneously; the radios are found by band, not by section name (which radio is 2.4 GHz varies between routers). If the router has no radio for one of the bands, that band's steps log a warning and are skipped.
tollgate network private statusPrivate Network Configuration
=============================
SSID: MyTollGate
Password: correct-horse-battery-09
Status: Enabled
tollgate network private enable
tollgate network private disableDisabling shows a lockout warning and requires confirmation, because turning off the private Wi-Fi can cut off your only wireless access to the router:
⚠️ WARNING: Disabling the private network may lock you out of the router!
Make sure you have another way to access the router (e.g., via the public
network or physical access).
Are you sure you want to disable the private network? (y/N):
tollgate network private rename MyNewNetworkChanges the SSID on both radios and reloads wireless immediately. Any device currently connected to the old SSID will be disconnected.
# set a specific password (must be 8–63 characters for WPA2)
tollgate network private set-password "my-new-password"
# generate a random human-readable password instead
tollgate network private set-passwordWhen no password is given, a random one is generated in the form
Word-Word-Word-NN (e.g. Alpha-Bravo-Charlie-42) using a
cryptographic random source. The new password is printed back so you
can record it:
Private network password changed successfully
new_password: Alpha-Bravo-Charlie-42
tollgate network private set-encryption psk2+tkip+ccmpSupported modes:
| Mode | Meaning |
|---|---|
psk2+ccmp |
WPA2-PSK with AES/CCMP. The default and what the router is set up with. |
psk2+tkip+ccmp |
Also offers the legacy TKIP cipher, for old clients that cannot do AES. |
psk-mixed+ccmp |
Also accepts WPA1 clients. |
WPA3-SAE is deliberately not offered: the wpad the router ships with has no
SAE support, so selecting it would leave the private network unable to start —
a lockout, not a feature. An open (unencrypted) mode is refused for the same
class of reason: the private network is how you reach the admin board, whose
login is a root-capable login over plain HTTP on :8090.
The three private-network commands above (rename, set-password, set-encryption)
also record the new value in /etc/tollgate/config.json, which is the file the
service reconciles the router onto at every start. Both radios are written
together, so the 2.4 GHz and 5 GHz SSIDs cannot drift apart.
Upstream Wi-Fi is how the router reaches the internet — either from an ISP router, a mobile hotspot, or another TollGate in reseller mode. See docs/wireless_gateway_manager.md for the background daemon that automates this; the commands below are for manual control.
tollgate upstream scanScans all radios and lists visible networks sorted by signal. Each entry also reports the band of the radio that scanned it, so a 2.4 GHz SSID can be told apart from a 5 GHz one without assuming radio0 is 2.4 GHz:
SSID Signal Ch Encryption Radio Band
------------------------------------------------------------------------------------
HomeFibre -42 dBm 36 WPA2 radio1 5g
TollGate-Cafe -55 dBm 6 WPA2 radio0 2g
OpenGuest -67 dBm 11 none radio0 2g
The band comes from the wifi-device sections of /etc/config/wireless: the
band option (OpenWrt 21.02+), falling back to the legacy hwmode, then to
the channel number (above 14 is 5 GHz; auto and its numeric spelling 0 say
nothing). A radio the config does not identify reports unknown instead of
guessing, and a band is never bound to a radio whose frequency is unknown while
other radios are identified — those bands are skipped, exactly as the
first-boot setup skips them.
tollgate upstream connect HomeFibre "my-wifi-password"
tollgate upstream connect OpenGuest # no password needed for open networksConnection runs as a streaming operation with live progress so you can see where it stalls:
[1/7] Enabling radios...
[2/7] Scanning for 'HomeFibre'...
[3/7] Found 'HomeFibre' (-42 dBm on radio1) encryption=psk2
[4/7] Setting up wwan interface...
[5/7] Creating STA upstream_homefibre on radio1...
[6/7] Switching upstream... waiting for DHCP
[7/7] Connected to 'HomeFibre' via upstream_homefibre (-42 dBm on radio1)
The command picks the radio with the best signal for the target SSID,
creates a STA interface, and switches the upstream to it. The previous
upstream (if any) is disabled but kept in config as a fallback
candidate. After a manual connect, the background connectivity-check
daemon is paused for manual_pause_seconds (default 120 s) so it
does not immediately fight your choice.
If the SSID is not found in the scan, you get
SSID '<name>' not found in scan — scan again or move closer.
tollgate upstream listSSID STATUS RADIO ENCRYPTION
-------------------------------------------------------
HomeFibre ACTIVE radio1 psk2
OldUpstream disabled radio0 psk2
ACTIVE is the interface currently providing upstream; disabled
entries are retained as fallback candidates.
tollgate upstream remove OldUpstreamRemoves a disabled STA section from the wireless config. Active upstreams cannot be removed — disable or switch away first. This is housekeeping: removing an entry does not affect connectivity, it just stops the daemon from ever considering that SSID again.
TollGate stores its configuration in /etc/tollgate/config.json and
identities in /etc/tollgate/identities.json. See the
Configuration section of the README for
the full schema and an abridged example.
tollgate config getPrints both the current config and identities. The output mirrors what is on disk after the last successful save/reload.
tollgate config set <key> <value>Keys are dot-paths into the JSON document. The change is validated against the schema and persisted to disk immediately:
tollgate config set metric milliseconds # sell time instead of data
tollgate config set step_size 44040192 # adjust the billing unit
tollgate config set accepted_mints.0.price_per_step 2 # index into an array
tollgate config set show_setup false # booleans accepted as-isThe CLI confirms the write, then reminds you to restart:
Set metric = milliseconds (restart tollgate-wrt to apply)
Most config set changes take effect only after
tollgate restart (or /etc/init.d/tollgate-wrt restart), because
the running service reads the file at startup.
Two settings are the exception, and they are the reason config set also
reports what it applied:
tollgate config set private_ssid c08r4d0r-7F3A
tollgate config set admin_access br-privateSet private_ssid = c08r4d0r-7F3A; runtime: 1 applied, 1 not applicable here
The private network's SSID, passphrase and encryption live in UCI
(/etc/config/wireless), which hostapd reads, and admin_access is an
nftables property. Neither can be honoured by a file the service merely reads,
so config set/config save write them to the router as well, and say for
each setting whether the runtime was changed (applied), already matched
(unchanged), could not be applied on this host (skipped), or was refused
with the reason (refused — e.g. admin_access=br-mgmt on a router that has
no br-mgmt bridge yet). See the README's
Network settings for the values.
Setting private_key from the CLI never echoes the passphrase back.
tollgate config applyRe-applies every declared operator setting — the private-network credentials
and the admin-access scope — and reports per setting what happened. The same
work runs automatically after config set/config save and at service start,
so this verb is for a config.json you edited by hand:
Operator settings converged (runtime: unchanged, not applicable here)
It is a converging writer: a router whose runtime already matches the file
reports unchanged and nothing on the wire moves. That is what makes it safe
on a service restart — it never drops your private-network clients for a change
that was already in effect.
tollgate config schemaOutputs the full machine-readable schema for both config and
identities — every field, its type, default, and validation rules.
Use this to discover valid keys and values before calling
config set.
tollgate config save '{"config_version":"v0.0.7","metric":"bytes",...}'
tollgate config save-identities '{"identities":[...]}'These write the complete JSON document in one shot. The config
variant validates required fields (config_version, metric,
step_size, accepted_mints, profit_share) and checks that
profit_share factors sum correctly before writing. Prefer
config set for individual changes; use save only when provisioning
a router from a known-good template.
Both commands reload the in-memory config after writing, but a restart is still needed for the change to take full effect.
Every command accepts a global --json (or -j) flag. With it, the
CLI emits structured JSON instead of the human-readable tables, and
destructive commands (wallet drain cashu,
network private disable) skip their confirmation prompts and act
immediately — this is intentional, so the flag can drive automation:
# script-friendly balance check
tollgate --json wallet balance
# drain without a prompt (tokens go to stdout, no file is written)
tollgate --json wallet drain cashu
# health probe for a monitoring loop
tollgate --json healthWhen the service is unreachable, --json output still includes a
success: false object with an error field rather than printing
prose to stderr, so a wrapper script can parse the failure reliably —
and the process exits non-zero whenever the reported result is a
failure (full or partial), so exit-code checks and JSON parsing agree.
A wallet drain cashu response with "success": false may still
carry a "tokens" array inside data: those tokens were produced
irreversibly and belong to you — persist them before investigating the
errors entries.
TollGate has no accounts and no user identifiers: a customer is identified
by the MAC address their device uses on your Wi-Fi. Sessions, byte meters
and open gates are all keyed by it, and it is the address ndsctl is
asked to authorise. Nothing anywhere in the stack takes that identity from
a value the client sends — a ?mac= parameter or a request-body field is
accepted and ignored; the module resolves the address from the request's
source IP through the DHCP lease file and the kernel ARP table, and
refuses the request (device-unresolved) rather than guessing when it
cannot.
This is one rule, and it holds on every endpoint of the payment API on
:2121, not just the money path:
| Endpoint | What it acts on | mac you send |
|---|---|---|
GET /whoami |
the calling client | ignored |
POST / (a token) |
the calling client's session | ignored |
POST /ln-invoice |
the calling client's quote | ignored (query and JSON body) |
GET /ln-invoice?quote=… |
the quote of the calling client | ignored |
GET /session-state |
the calling client's session state | ignored |
GET /balance |
the calling client's session | ignored |
GET /usage |
the calling client's byte/time usage | ignored |
The parameter is accepted because the shipped portal's Lightning lane sends
back the address it read from /whoami; it never decides which device a
read or a grant applies to. Nothing about this is silent, because silence is
how a test rig ends up believing the wrong thing:
- every client-scoped response carries
X-TollGate-Client-MAC: <address>— the client the module actually answered for, in canonical (lowercase) form; - when you assert a different address, the response also carries
X-TollGate-Mac-Claim-Ignored: <address>— the claim that was not honoured. A matching claim (what the portal sends) is not reported.
Both headers are exposed through CORS, so a page or harness running on the
portal origin can read them. The session event of a purchase (kind: 1022)
names the same thing independently, in its device-identifier tag.
What this means for a test rig or a script. To pay for the session of a
device, send the request from that device's own socket — bind to its
interface/IP, or run the probe on the device. Posting a token to
http://<router>:2121/?mac=<some-other-device> does not buy that device
anything: the grant goes to the socket that sent it, and the MAC you named
is reported back as ignored. A harness that posts "for" a MAC it is not
itself using and then looks for a session on that MAC is measuring the wrong
device — read X-TollGate-Client-MAC (or the device-identifier tag of the
1022) and compare it with your own socket address instead.
It is not an account, not stable, and not something this router can
change. A MAC address is the source address of every frame the device
transmits: it is chosen by the device's own operating system and network
card, it is visible to anyone in range, and it can be typed into a query
string by anyone. An access point can only filter addresses
(macfilter/maclist) or force a re-association — and forcing one does
not change the private address a device has saved for that network.
So "TollGate rotates your MAC automatically" is false, and no release has ever implemented it. If you have repeated it, correct it: the honest claim is that TollGate does not require address stability, and never ties anything durable to it.
On many platforms the device changes or randomises the address by itself, without the customer asking you:
| Platform | Default | How a customer changes it |
|---|---|---|
| iOS / iPadOS 18+ | "Fixed" on a network with WPA2 or stronger, "Rotating" on a weak or open one — which is the usual captive-portal SSID, where it moves to a different private address about every two weeks with no user action | Settings → Wi-Fi → (i) next to the network → "Private Wi-Fi Address" → Off / Fixed / Rotating |
| Android 10+ | One randomised address per network, stable while that network is saved | Settings → Network & internet → Internet → (gear) → "Privacy" |
| Windows 10/11 | Randomisation off unless enabled per network | Settings → Network & internet → Wi-Fi → the network → "Random hardware addresses" |
| Linux (NetworkManager) | preserve — the hardware address |
nmcli con mod <id> wifi.cloned-mac-address random |
A WPA2 password therefore has a side effect worth knowing: it keeps iOS on a fixed address. An open SSID (the classic captive-portal posture) is where rotation happens by itself.
The session belongs to the address it was bought on, so:
- The device returns as a new client and the portal offers the buy flow again.
- The abandoned address is not reconciled a minute later: the address
itself keeps the access NoDogSplash already granted it for about an hour.
NoDogSplash only stops listing an authenticated client when its idle
timeout fires, and TollGate ships
authidletimeout='3600', so a departed client stays listed — and NDS-authorised — for roughly that hour. From the first sweep after the listing goes away the module asks NoDogSplash whether that client is still there, and after two consecutive "gone" answers — within ~30–90 s — it deauthorises the address, drops its session record, and clears its metering baseline. The honest end-to-end bound is therefore about an hour (NDS listing lifetime) plus a minute (module reconciliation), not a minute. That is what stops an address nobody holds from staying authorised indefinitely, which anyone who later holds it (a hardware-address fallback, a spoof, a collision) would otherwise inherit for free. - The leftover time or data the customer paid for does not travel to the new address yet: entitlement still belongs to the address. Carrying it across needs a session ticket and a portal change, and is scheduled as its own change.
Tell customers that plainly, rather than promising seamless roaming: changing your device's Wi-Fi address ends your current session.
WARNING: NoDogSplash no longer lists <mac> (1/2 passes) — its binding stays authorised for now
Reconciled the stale binding of <mac>: its client is gone, the gate is deauthorised and the session is retired (12 MB of covered usage; ...)
Only a confirmed close retires anything. If ndsctl deauth fails you
will see the escalation instead — ERROR: could not close the gate of the stale binding of <mac> … (unconfirmed gate closes=N) — and the module
keeps the record and keeps retrying, because a gate that is still open must
stay owned by something. The same rule applies to a customer who is merely
idle: a client NoDogSplash still lists is never touched.
That last rule is also the limit of this pass, and worth knowing: if NoDogSplash keeps listing an address whose device has left (rather than dropping the entry), the module cannot tell that address from a customer who is simply idle, and it leaves it alone on purpose — cutting off a paying customer is the worse failure. Closing that residue is the job of the session-ticket work, which re-binds entitlement explicitly instead of inferring it from an address.
Because any client can present any address (and on an open SSID many devices change theirs by themselves), a MAC allow-list is not an access control — it hands internet to whoever names an allowed address. Keep the per-MAC controls you do have (session state, byte meters) as accounting, not as authorisation.
The Unix socket /var/run/tollgate.sock is not accepting
connections. The service is either stopped or crashed.
tollgate start # or:
/etc/init.d/tollgate-wrt restart
tollgate health # verify it came backIf it keeps crashing, check the logs:
tollgate logs -n 100
logread -e tollgate -fThe captive-portal QR scanner needs a secure context (HTTPS or
localhost) to access the camera via getUserMedia(). Captive
portals are served over plain HTTP, so browsers such as Brave and
Firefox on Linux block camera access entirely.
Workaround: skip the scanner and paste the Cashu token manually
into the portal input field, or fund the wallet from the router shell
with tollgate wallet fund <token>. On Chrome/Edge the scanner
usually works; on the routers where SSL/HTTPS is configured for the
captive portal domain, camera access may also succeed.
Payouts run on a per-mint timer and only fire when the balance exceeds
min_payout_amount. If payouts seem stuck:
tollgate wallet info # is there a balance to pay out?
tollgate config get # check payout thresholds per mint
logread -e tollgate | grep -i payout # look for payout errors
logread -e tollgate | grep -i "Could not find" # missing identity / Lightning addrCommon causes: the mint is unreachable (network issue), the profit
share references an identity not in identities.json, or the balance
is below min_payout_amount / min_balance.
tollgate network private status # confirm it says Enabled
wifi reload # force a wireless reload
logread -e hostapd # check the AP daemonIf the 5 GHz interface is in client (STA) mode for an upstream, it cannot also serve as a private AP — this is a hardware limitation of single-radio setups, not a bug.
The STA interface was created and switched to, but the upstream router
did not hand out a lease within the DHCP timeout
(dhcp_timeout_seconds, default 180 s).
tollgate upstream scan # is the SSID still visible?
logread -e odhcp # DHCP client logsTry moving closer to the access point, verifying the password, or checking that the upstream router is not out of DHCP leases.
When the mint does not answer a payment within its 30-second deadline the module does not claim the payment failed. It says the outcome is unknown and shows the customer a reference: 16 hex characters, the salted fingerprint of the note they sent. (The note itself is never written to a log — anyone holding it can spend it — so the reference is the only handle that ties the customer to the attempt.)
Search the log for it:
logread -e tollgate | grep '<reference the customer showed you>'The reference appears on the deadline line and again on the line that answers the question you actually have — what the mint did with the note:
late Receive COMPLETED … amount=N — the mint took the note and no session was granted; credit or refund it— the customer's value is in the operator wallet and they received nothing. Nothing credits or refunds this automatically today, so settle it by hand, explicitly (grant the device access, or return the value to an address the customer controls) and note what you did.late Receive FAILED (outcome still ambiguous) …: <error> — the mint may have taken the note; check the wallet balance for the mint before resubmitting anything, no session was granted— the late answer was not the mint saying "no". This is what a timeout or another unreachable-class error looks like, and it is the common case rather than the odd one: the module's deadline and the wallet's own HTTP client both run on 30 seconds, so the first answer to arrive late is normally a client-side one that says nothing about what the mint did with the note. Do not tell the customer to send it again. Check the mint's balance for the amount first; if the note was credited, settle it by hand as in theCOMPLETEDcase above; invite a resubmission only once you have established the mint did not take it.late Receive FAILED …: <mint error> — the mint did not take the note, no session was granted— the mint itself refused the note, and a refusal is the one answer only the mint can give: the proofs were already spent elsewhere, they sit on a retired keyset, they cannot cover the swap fee, or the request was rejected outright. The note was never spent. The customer can safely submit it again.
If you see only the Receive outcome unknown line, the money-moving
request had not finished when you looked — or the process was restarted
while it was in flight, in which case no outcome line will ever be
written. Re-check the log before telling the customer anything. The
durable journal that would settle a late outcome automatically is not
implemented; the reference plus these lines are the whole procedure.
The TOLLGATE_TEST_CONFIG_DIR environment variable exists for the test
harness: it redirects the config directory, the drain journal
(/etc/tollgate/wallet-drain-journal.jsonl — bearer tokens) and the
CLI socket to a temp directory. It is meant to be set only by go test.
If it appears in a service drop-in, wrapper script or shell profile on a
router, state silently splits: the drain journal lands elsewhere (0600,
but wherever the variable points) while anything not sharing the
environment still uses the stock paths. Both the service and the CLI now
print a WARNING: TOLLGATE_TEST_CONFIG_DIR is set line whenever they
honor it — if you see that line in logread on a production router,
remove the variable from the environment and move the journal back.