Three tabs listed the same extensions three different ways: Asterisk Admin
as devices with category/status/transport, Extensions as a row of messaging
checkboxes, PSTN Trunk as permission tiers with a duplicate Messaging column
that wrote the same flag. Changing one extension meant knowing which of the
three owned the setting you wanted.
There is now one Extensions tab with one extensions table, merged from
pjsip.conf (via /api/pstn-permissions, which always works) and /api/ea-devices
where the Easy Asterisk container is reachable, keyed by extension so a row
known to only one source still shows. Capabilities add columns rather than
nav buttons: Category/Status/Transport are .ea-only, Tier/Approved-numbers
are .pstn-only, and both classes start on <body> so nothing flashes before
/api/ea-status and /api/pstn-status answer. Categories, Rooms, Groups,
Concurrent-call caps and Personal numbers are cards under the same tab, gated
the same way.
Per-row Save picks its write path: tier + approved numbers + messaging via
/api/pstn-permissions with a trunk installed, messaging alone via
/api/pstn-messaging without one — which is what that endpoint has always
been for. No backend changes; the standalone messaging-chips card and the
duplicate Messaging column are both gone.
Verified in Chromium against a fixture Asterisk config: full layout renders
nine columns and six cards, the bare layout collapses to Ext/Name/Messaging
with two cards, and both save paths write pstn-permissions.conf correctly
(messaging-only leaves tier and allowed_numbers untouched).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh
services/asterisk-digital-ocean.sh was a near-verbatim copy of
services/asterisk.sh — same vendor refresh, compose template, messaging
dialplan, presence alerts, UFW rules and dashboard/trunk chaining, with the
helper functions renamed _asterisk_do_*. Two copies meant every fix had to
land twice, and several never did.
There is now one `asterisk` service. It reads the DigitalOcean metadata
service and asks either way (so a droplet with metadata blocked, or another
provider's public VM, can still opt in), then gates the genuinely
droplet-specific behaviour on that one answer: swapfile for low-RAM plans,
public-FQDN-only setup with no LAN/VLAN prompts, a Caddy site block pinned
to that FQDN, the remote-Authelia option, and the doctl Cloud Firewall.
Two things that were droplet-only for no real reason now apply everywhere:
the entrypoint patch that writes security-level events to logs/full, and
the logrotate config for that file. Without them the Security Dashboard's
Security Log tab and CrowdSec's Asterisk acquisition were silently empty on
every home/LAN install; crowdsec.sh now detects either install directory.
Existing droplets are left alone: an install at ~/docker/asterisk-digital-ocean
keeps its directory and easy-asterisk-do container names, since its Caddyfile
block, UFW rules, Cloud Firewall, CrowdSec acquisition and PSTN trunk all
name those exactly. New installs use ~/docker/asterisk / easy-asterisk.
`sudo ./setup.sh asterisk-digital-ocean` still works via a new SERVICE_ALIAS
map in setup.sh, without a second menu entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh
The step 4 heading said "once per DID" and the intro said steps 3-7 repeat
per DID, both of which overstate the work. The body already noted the trunk
could be reused, but the heading is what gets read first - and it actively
misleads toward creating one trunk object per number.
One inbound SIP Trunk serves the whole account: the $[E164]$ placeholder is
substituted with each DID's own number at call time, and the Asterisk side
matches whatever arrives via _X. and resolves it through
pstn-personal-dids.conf. Same for the outbound Service Trunk in step 3,
which the body already flagged as skippable for DIDs 2+.
Rewrites the heading, adds an explicit up-front note on why one suffices,
and corrects the intro to name which steps actually repeat per number.
A group-owned personal DID is now working end to end against a real
inbound call: it rings every authorized group member simultaneously, first
to answer wins, and members whose own tier/allowed_numbers don't authorize
the caller are correctly skipped.
Updates the code comments that were written before that confirmation
existed (the Busy(15) block's "NOT yet confirmed live", and the group-ring
script's unverified-SHELL() note), and documents all four fixes plus the
"Commit Changes" caveat in the Anveo setup guide's bugs-fixed section so
the next person hitting one-ring-then-busy has the trail.
Also clears the guide's "Still open" section: the 2-number account cap no
longer applies, and the interactive walkthrough it described as unbuilt
was built (_pstn_anveo_walkthrough).
Documents the actual end-to-end sequence that got a real DID working
(account setup, DID ordering, both trunk objects, droplet configuration,
permission setup, testing), including the real confirmed values (the
$0.00388/min rate, the port-5060 SIP URI forwarding format, the two
separate trunk-object types) and a summary of every real bug hit and
fixed along the way. Linked from the existing planning-notes doc.
_1NXXNXXXXX / _NXXNXXXXX were one digit short (10/9 characters instead of
the correct 11/10 for a full NANP number: 1 + area code(3) + exchange(3) +
subscriber(4)). Asterisk's exact-length pattern matching never matched a
real dialed number as a result — confirmed live via "extension not found
in context 'intercom'" on every outbound test call, traced by dumping
`dialplan show intercom` and counting characters against the actual
pattern. This was the real root cause underneath everything else fixed
along the way (stale settings file, the #include not reaching the live
config) — those were real bugs too, but this one was still there
underneath all of them.
Confirmed against a live install's pjsip.conf/extensions.conf that every
endpoint falls back to context=intercom for messaging (message_context
blank), and that [intercom] owns one exact-match per-device dial pattern
regenerated on every dialplan rebuild. Rather than risk racing that, every
endpoint now gets message_context=sip-messaging (patched into both of Easy
Asterisk's device-creation code paths, plus a one-time migration for
existing devices), routing messages to their own [sip-messaging] context
gated on the existing pstn-permissions.conf messaging flag.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
get_all_permissions()/write_permission() now handle messaging alongside
tier/allowed_numbers as one save action, and the Security Dashboard's PSTN
Trunk table gets a Messaging checkbox column - no more needing to re-run
pstn-trunk.sh's CLI just to change who can use internal SIP texting,
matching how tier/allowed_numbers/personal_did already worked.
Tested that messaging correctly survives tier changes and personal-DID
assignment/removal on the same extension (independent axes, as intended).
Still explicitly not done, and said so in both READMEs rather than
implying otherwise now that there's a nice UI for it: the actual SIP
MESSAGE dialplan wiring that would make Asterisk enforce this flag. That
gap hasn't changed - only the permission storage/UI layer around it has.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
Prompts now start with a 1/2/3 choice (Anveo Direct / VoIP.ms / manual)
that pre-fills already-known values - Anveo Direct's sbc.anveo.com
hostname and its 4 published signaling IPs, so they don't need retyping
from memory - plus provider-specific account-setup reminders. Every
value stays editable at each subsequent prompt; this only changes
defaults, not behavior, so existing manual-entry users see no change.
Flagged rather than silently trusted: Anveo Direct's own Outbound Trunks
page documents dialing as [PREFIX]PHONENUMBER@sbc.anveo.com, which
contradicts an earlier no-prefix-needed finding from their FAQ. The
dialplan still dials the bare number (matching the older finding) - the
quick-pick prints a loud warning to verify the Prefix field can be left
blank before relying on this, rather than guessing and hardcoding prefix
handling without live confirmation either way.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
Multiple DIDs can now share one trunk/account, each assigned to a specific
extension - additive to the existing shared trunk DID/ring-group, which
keeps working unchanged for everyone regardless of what's assigned here.
- New pstn-personal-dids.conf (DID -> owner extension), read live by the
dialplan for inbound routing: a call to a personal DID routes straight
to its owner, checked against the owner's own tier/approved-numbers, no
ring-group fallback since it's that extension's own line.
- New personal_did= field per extension in pstn-permissions.conf: the
outbound Caller-ID override, used by pstn_check_busy (the shared exit
point for both domestic and international dialing) instead of the
shared trunk DID when the calling extension has one assigned.
- Both files kept in sync automatically by one write path - CLI prompt at
install/update, or a new "Personal numbers" card in the Security
Dashboard's PSTN Trunk tab - rather than requiring hand-editing both
consistently. Reassigning a DID or giving an extension a new one cleanly
drops the stale side of the old mapping.
- Assigning a DID to an internal-tier extension is accepted but warned
about (won't ring anyone until also granted full/restricted tier),
matching this repo's permissive-with-warnings style rather than
blocking the action outright.
Also fixed a real bug found while building this: security-dashboard.sh's
write_permission() did cp.remove_section(ext) whenever tier was set to
"internal", silently discarding any messaging=yes or personal_did=
already set on that extension. Now removes only the tier/allowed_numbers
keys, dropping the section only once nothing else remains in it.
Separately, de-anchored pstn-trunk.sh's install prompts from VoIP.ms as
the implicit default (provider name default, server-hostname example
wording, rate-prompt wording) now that Anveo Direct is an equally
confirmed, tested provider option.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
- CLI country menu now asks for a per-minute rate when a country is added,
stored alongside its code/name in pstn-intl-allowed.conf. The usage-alert
script buckets each month's outbound calls into domestic vs. per-country
international (longest-code-prefix match on the dialed digits) and prices
each bucket at its own rate instead of one blended flat rate, which badly
under/over-estimated international cost.
- While the kill-switch is tripped, the periodic script now also force-hangs
up any PSTN call already in progress (docker exec + Asterisk's "channel
request hangup" against the trunk's active channels), not just new calls
- closing the gap where an in-progress call just kept running until it
ended naturally. Runs every check while tripped, not only the run that
trips it, to close a start-of-call race.
- Fixed a bug caught by testing: the hangup sweep was nested inside the
"does the call log exist" guard, so it silently never fired if the log
was missing/rotated while tripped. Moved it outside that guard.
- Documented an honest "how bulletproof is this?" breakdown in the
generated README: the provider's own real-time $0-balance block is the
actual hard ceiling (confirmed for Anveo Direct), the kill-switch here is
an estimate-based second layer, not a replacement for funding the prepaid
account at your actual risk tolerance.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
Support reply (MFonk, 7/22/2026) confirms both open questions: all calls
(incoming and outgoing) are blocked in real time at $0 balance, not just
via the recurring-fee grace-period mechanism, and the 4 signaling IPs
already hardcoded into this doc's research notes are still current.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
- Replace the hourly cron.d spend/volume check with a per-minute systemd
timer (cron.d fallback), since it's now also the kill-switch's
enforcement point and the interval is the exposure window.
- Add a hard monthly spend-cap kill-switch: trips pstn-trunk-killswitch.conf
(read live by the dialplan, both call directions, internal calling
unaffected) once estimated spend reaches an admin-set cap, plus a loud
ntfy warning at 80% of the cap before it trips. Does not auto-reset;
clearing it is a CLI-only action (update mode) so a compromised/careless
web session can't quietly re-enable spend after a trip.
- Add a CLI-only (never web-dashboard) international-calling allow-list:
continent/country menu, always asked every run with the resulting
allow-list printed exactly once right after, optional auto-expiry with
day-of and at-expiry ntfy notices, and active re-blocking once expired.
Dialplan gates the US "011" prefix pattern on it for full-tier extensions.
- Add an independent messaging_enabled flag per extension in
pstn-permissions.conf for Asterisk's native SIP MESSAGE texting, since
the risk profile (no cost) differs from PSTN calling permissions. The
dialplan wiring to enforce it is flagged as a known gap pending live
verification against Easy Asterisk's message routing, rather than
shipping a guessed pattern.
- security-dashboard.sh: detect whether pstn-trunk is actually installed
(not just base Asterisk) and show a clear "not installed" state instead
of misleading default caps/extensions; add clickable column-header
sorting to the CrowdSec active-bans table.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
Anveo Direct's "Call Security" rate-cap feature (found while evaluating
their account portal) prompted a closer look at our own NANP dialplan
pattern, which turned out to have a real gap: NANP covers Caribbean/
Atlantic nations and several US territories, not just the US - these dial
exactly like a normal 10-digit US number but bill at international/
premium rates, a well-known toll-fraud/"one-ring scam" vector precisely
because the format looks domestic. The existing _1NXXNXXXXX/_NXXNXXXXX
patterns matched all of them equally, meaning any extension (including
"full" tier) could reach premium Caribbean/territory numbers despite the
whole point of the design being "US only, no premium-rate destinations."
Verified the area code list against multiple sources rather than relying
on training-data recall alone, then added an explicit block on all 27
(Bahamas, Barbados, Anguilla, Antigua & Barbuda, British Virgin Islands,
US Virgin Islands, Cayman Islands, Bermuda, Grenada, Turks & Caicos,
Jamaica, Montserrat, Northern Mariana Islands, Guam, American Samoa, Sint
Maarten, Saint Lucia, Dominica, Saint Vincent, Puerto Rico, Dominican
Republic, Trinidad & Tobago, Saint Kitts & Nevis) - checked via REGEX()
against the extracted area code, before permission-tier checks, so it
applies regardless of tier. Tested the extraction/matching logic against
both blocked and legitimate US area codes.
Updated docs/pstn-calling-voipms-plan.md's toll-fraud reasoning, which had
previously (incorrectly) claimed NANP-only restriction alone fully closed
the cost-per-minute risk - noted as a real, live gap rather than glossing
over it. Also noted Anveo Direct's provider-side Call Security setting
(rate cap + max call duration) as a complementary backstop worth
configuring on their account portal directly, independent of this repo's
code.
VoIP.ms's one-POP-one-hostname model meant a single resolved IP was enough
for the trunk's identify match=, but Anveo Direct (researched as an
alternative after finding VoIP.ms's ToS selectively requires government ID
at signup) sends inbound signaling from a fixed set of published IPs
instead. Generalizes the pjsip include to accept multiple match= lines in
one identify object (PJSIP allows repeating it to build one match set - no
separate config objects needed per IP), with a new install prompt for any
additional known source IPs beyond the auto-resolved one.
No dial-prefix support was added despite earlier research suggesting Anveo
Direct needed one - verified against their current official FAQ (fetched
directly rather than trusting a stale search-result claim) that plain
number dialing works with no prefix.
Fixed a real bug caught while testing the update-mode round trip:
.pstn-trunk.env was written with unquoted values, which breaks `source`
entirely once any value contains a space - a multi-IP list or a
multi-word provider name (e.g. "Anveo Direct") would make bash try to run
the second word as a command. Predates this change (a multi-extension
ring group has the same shape); only surfaced by actually re-sourcing the
file and reapplying, not by inspecting generator output. Fixed by quoting
every value.
Updated docs/pstn-calling-voipms-plan.md with the Anveo Direct/DIDLogic/
SIP.US research findings and what's now implemented vs. still open
(real-time balance enforcement at Anveo specifically is unconfirmed).
The user found ToS language stating the account "may run on a negative
balance" and describing a 30-day-notice-then-suspend / 30-consecutive-
days-then-close process - worth reading against the wiki's "balance must
be over $0 to call" claim the whole toll-fraud design leans on.
Reconciled: these describe two different things, not a contradiction.
New call attempts should still be blocked in real time at $0 (the wiki's
claim, and the core assumption this design needs). The ToS's negative-
balance language most plausibly covers recurring fees (DID/E911) landing
when balance is already near zero, and in-progress-call settlement edge
cases - not a window where fraud keeps dialing while negative. Still not
verified against a live account either way.
Adds an inbound concurrent-call cap mirroring the existing outbound one -
outbound alone didn't protect against an inbound call-flood, which also
costs money per-minute on VoIP.ms. Both defaults bumped from 3 to 10.
Moves the cap numbers themselves out of static dialplan text and into a new
pstn-limits.conf, read live via AST_CONFIG() the same way permission tiers
already are - changing either cap takes effect on the next call, no
Asterisk restart, no reinstall. "update in place" never touches this file,
matching the existing protection for pstn-permissions.conf/.env/firewall/
Caddy config.
Adds a concurrency-caps card to security-dashboard.sh's "PSTN Trunk" tab,
above the existing permissions table, so both caps are visible and editable
from the same web page. Tested against a real running instance of the
Python app: default fallback when the file doesn't exist yet, save/persist,
invalid-input rejection, and a bash-to-Python round trip on the generated
file format.
Inbound dialplan ordering mirrors outbound's existing pattern: permission
check (is any ring-group member authorized for this caller) before the
concurrency check, consistent with outbound's tier-check-then-busy-check
order.
Reworks the outbound permission model from a flat allow-list into three
per-extension tiers (internal / restricted / full), addressing the ask for
extensions that can only reach pre-approved numbers plus extensions with
full US calling, while internal extension-to-extension dialing and ring
groups stay ungated for everyone regardless of tier.
Permissions now live in pstn-permissions.conf, read by the dialplan via
Asterisk's AST_CONFIG() on every call instead of being baked into static
dialplan text - editing that file takes effect on the next call, no
Asterisk restart and no re-running the installer. "update in place" mode
never touches this file (same protection this repo's update-mode
convention already gives .env/firewall/Caddy config); only a "fresh"
reinstall (with confirmation) or the web UI change it.
Adds a "PSTN Trunk" tab to services/security-dashboard.sh: lists every
extension (parsed from pjsip.conf) with its live tier and approved numbers,
editable with no restart - this is what makes the tier model actually
manageable day to day. Extracted the dashboard's systemd-unit writing into
its own function so "update" mode refreshes it too (previously only fresh
installs did), and generalized both the dashboard and the trunk service to
detect either asterisk-digital-ocean or the home/LAN asterisk install.
Inbound ring-group membership now checks each member's tier live per call
via an unrolled per-member dialplan block (full always rings, restricted
only if the caller's number is approved, internal never rings) rather than
a single static Dial() string.
Caught and fixed two real bugs during testing against a sandboxed vendor
copy and a live instance of the (stdlib-only) Python dashboard app:
- Asterisk Goto/GotoIf argument parsing: ring<ext>/skip<ext> are named
priorities within the same extension (declared via "same => n(label),..."),
not separate exten => entries, so jumping to them needs the single-argument
Goto(label) form - the two-argument Goto(label,1) form used initially
addresses a different, nonexistent extension named "label" instead.
- A security-relevant REGEX() direction issue: the inbound Caller-ID check
initially interpolated attacker-influenced call data into the PATTERN side
of a REGEX() match rather than the tested-string side, which would let a
crafted Caller-ID forge a match against an unrelated approved-numbers
entry. Fixed by keeping the admin-controlled approved-list as the pattern
and the live call data as the string being tested, consistently on both
the outbound and inbound checks.
Verified end-to-end: dialplan/pjsip generation and vendor-file patching
(idempotent, syntax-checked) as before, plus the new permission-file
round-trip between bash and Python, and the dashboard's new API endpoints
exercised against a real running Python server (extension parsing, tier
changes, number normalization, invalid-input rejection, atomic file writes).
Generalizes services/pstn-trunk.sh (renamed from voipms-trunk.sh in the
prior commit) away from VoIP.ms specifics - any IP-authenticated SIP
provider works, VoIP.ms is just the suggested default. Adds:
- Role-based outbound permission: a configurable allow-list of extensions
that may dial PSTN numbers (regex-gated on CHANNEL(peername)), separate
from internal extension-to-extension dialing which stays open to everyone
regardless. Blank list preserves the original "everyone can dial out"
behavior.
- Inbound ring-group: rings a configurable list of extensions instead of a
single hardcoded one.
- ntfy alerts: immediate on denied (unauthorized extension) or rejected
(concurrency cap hit) calls, plus an hourly cron-driven check that alerts
once per month when estimated spend crosses a threshold and every hour
call volume looks like a burst. Uses a self-contained pipe-delimited call
log rather than Asterisk's CDR, to avoid depending on CDR module
availability and CSV comma-quoting.
- Settings persisted to .pstn-trunk.env so "update in place" reapplies
everything from that file instead of fragile re-parsing out of generated
Asterisk config (which had a real bug: update mode was extracting the
wrong Dial(PJSIP/...) line).
Tested end-to-end against a sandboxed copy of the real vendor files:
permission-gate regex, ring-group dial-string construction, ntfy line
injection/removal, and the usage-alert script's threshold/burst/monthly-
dedup logic all verified with synthetic data. Caught and fixed a sed `&`
escaping bug in the ring-group substitution before it shipped (RING_DIAL
contains literal `&` join characters, which sed's replacement syntax
otherwise treats as "insert the match").
Adds a VoIP.ms SIP trunk on top of asterisk-digital-ocean: IP-authenticated
trunk (no password stored), NANP-only outbound dialplan, a global 3-call
concurrent cap via GROUP()/GROUP_COUNT(), and inbound routing to one
extension. Config lives in its own include files rather than being
appended directly to pjsip.conf/extensions.conf, since Easy Asterisk fully
regenerates both from its own internal state — the includes are patched
into the vendor's generator functions so they survive that regeneration.
Wires the new service into setup.sh's is_installed() and README's services
table, and updates docs/pstn-calling-voipms-plan.md to reflect what's now
implemented vs. still open (spend/volume alerting, live-account
verification).
Inbound (DID) is now decided as wanted, not outbound-only. Adds a cost
estimate table for 100 min/month each direction, and clarifies that
NANP-only restriction bounds cost-per-minute but not burn speed, so the
concurrent-call cap and spend alert are required before funding a live
trunk, not optional hardening.
Captures the toll-fraud / prepaid-cap research and decisions from a design
discussion (provider: VoIP.ms, US-only calling for now) so implementation
can be picked up in a future session without re-deriving the background.
Nothing implemented yet.
Both scripts now detect and install msitools if missing, supporting
apt-get (Debian/Ubuntu), dnf (Fedora), and pacman (Arch). Falls back
to a clear error if the package manager is not recognised.
Remove msitools from manual prerequisites in the doc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Both scripts now download and install GE-Proton10-34 if not already present:
- setup-swbf2-linux.sh: installs to ~/.steam/root/compatibilitytools.d/
- setup-swbf2-wolf.sh: installs into the WolfSteam container via docker cp + tar
Both pause after install and prompt the user to set GE-Proton in Steam
(Compatibility → Force) before continuing, since that step requires the GUI.
Update docs to reflect GE-Proton is now handled automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Remove editorial content (EA server speculation, 2005 alternative, opinions)
- Add native Linux Steam section covering all six setup steps
- Add separate key paths tables for Wolf and native
- Add native Linux troubleshooting commands alongside Wolf equivalents
- Rename title to reflect both platforms
- Fresh install table rewritten as factual survival matrix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Long-form document tracing the actual discovery process: the wrong turns,
the aha moments, and the reasoning behind each step. Covers:
- Why EA is involved in a Steam game at all (link2ea:// architecture)
- JunoConfigureRegistry failure and why DISABLEROLLBACK was a dead end
- msiextract bypass as the actual solution
- Why direct system.reg edits don't survive reboots (wineserver ownership)
- The launch wrapper approach for registry persistence
- RPC_S_SERVER_UNAVAILABLE and EALocalHostSvc
- Wolf-specific bwrap capabilities mismatch
- localconfig.vdf atomic rename problem and chmod 555 fix
Written for sharing — no identifying information, explains the "why"
for each fix so readers can adapt if EA changes their MSI structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Documents the EA account requirement, first-time login flow, credential
caching for machine transfers, community server options if EA shuts down,
and the SWBF2 2005 alternative for a launcher-free experience.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Revised approach: skip link2ea:// and EA App account requirement entirely.
Launch starwarsbattlefrontii.exe directly via the wrapper, spoof Origin
registry entries so the game's built-in launcher check passes. Documents
what doesn't work and why (msiexec/bwrap/EA account issues).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Documents the EA App installation workaround for Star Wars Battlefront II
(2017) running under Wolf/WolfSteam with GE-Proton10-34. Covers the bwrap
capability problem, Steam launch wrapper trick, msiextract bypass for the
JunoConfigureRegistry Wine incompatibility, and correct EA Desktop file layout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Swaps image from filebrowser/filebrowser to gtstef/filebrowser:stable
- Uses ./data:/home/filebrowser/data volume layout (Quantum convention)
- Generates data/config.yaml instead of settings.json + database.db
- Mounts primary path as /files with defaultEnabled: true
- Deploys fbq-add-source.sh for adding extra directories post-install
- Updates README with Quantum-specific instructions
docs: save standalone bootstrap as docs/standalone-template.sh
Preserves the full standalone pattern from filebrowser.sh as the
reference template for adding standalone support to other services.
https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt