Commit Graph
22 Commits
Author SHA1 Message Date
Claude 779a49b02a Merge main into ionos-script-integration-x32ofw, resolve coturn conflicts
Both branches independently solved the same Mattermost/Asterisk coturn
relay-port collision problem. Kept main's find_free_coturn_range-based
approach (documented in CLAUDE.md as the canonical pattern, and shared
across every coturn-owning service) over this branch's earlier
Mattermost-only port-slot scheme, and cleaned up the now-unused
_MM_COTURN_PORT/_MM_COTURN_MIN/_MM_COTURN_MAX/EMBEDDED_COTURN_SLOT
references that had auto-merged without conflict markers.
2026-08-14 01:26:28 +00:00
Claude 624ae3d2f3 Stop Mattermost's WEB_PORT/CALLS_UDP_PORT rescanning on every update
Found while adding a live scanner to the coturn-slot code: WEB_PORT and
CALLS_UDP_PORT were scanned unconditionally, before the reinstall-mode
prompt even ran and before anything stopped the currently-running
container. On an "Update" run that meant find_free_port would see this
instance's OWN already-published port as occupied and silently shift it
to the next free one — every plain update could have moved the service's
port out from under already-configured Caddy routes, bookmarks, and the
Calls plugin's client config, without the operator asking for that.

services/asterisk.sh already gets this right for WEB_ADMIN_PORT: update
reads the existing port back from .env (no rescan), fresh scans from the
plain default only after stopping the old container. Brought Mattermost
in line with the same shape — the port resolution moved from before the
reinstall-mode block to after it, so MODE is known and, for a fresh
install/"Full reinstall", the old containers are already stopped by the
time it scans.

WEB_PORT/CALLS_UDP_PORT are now also written to .env directly (they
weren't before), with a fallback to parse them from the existing
MM_SERVICESETTINGS_LISTENADDRESS / docker-compose.yml port mapping for
installs made before this change — so an update on an already-running
instance doesn't regress just because its .env predates the new
variables. Verified the explicit-var, fallback-parse, and priority-order
(explicit wins over fallback) cases against a mock before shipping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-13 15:12:05 +00:00
Claude cce8147059 Live-verify a newly assigned Mattermost coturn slot isn't already bound
Requested check: the slot-allocation scheme added in the previous commit
only checked against OTHER mattermost*/.env files on the box, not
against what's actually listening. A slot whose numbers happen to be
free by that bookkeeping could still be squatted by something this
script doesn't track (a manually-run process, an unrelated service) —
this box already learned that lesson once, from Asterisk and
Mattermost's embedded coturn ranges overlapping without either side
knowing.

Only a NEWLY assigned slot gets the live check — an already-cached slot
(read back from this instance's own .env) is trusted as-is, since a live
conflict on an already-configured, already-running instance's own port
is a real problem to report, not something to silently route around by
moving that instance's TURN port out from under it.

Can't scan the full 200-port relay range port-by-port (large ranges use
the offset scheme instead of scanning per CLAUDE.md's port-collision
section) — checks the control port plus both relay-range boundaries as
the practical middle ground. Verified against a mock: a candidate slot
whose control port is already bound gets skipped in favor of the next
free one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-13 15:04:51 +00:00
Claude 4a09d3d1de Give each Mattermost instance's embedded coturn its own port slot
Follow-up to the Asterisk/Mattermost relay-range overlap fix: that fix
only handled the two-service collision, and left a documented gap for
what happens when a second (or third...) Mattermost instance also falls
back to embedded coturn — they'd have collided with each other on the
same fixed 3479/49253-49452 numbers, same bug, different pair.

find_free_port-style scanning doesn't work for the relay range itself —
it's a scan for a single free port, not a free contiguous 200-port
block — so this follows the same fixed-offset-per-instance approach
CLAUDE.md documents for traccar.sh's large port range instead. Each
instance gets an integer slot (control port = 3479 + slot, relay range
= 49253 + slot*200 through +199) computed once as the smallest slot
number not already claimed by another mattermost*/.env on the box, then
cached in that instance's own .env as EMBEDDED_COTURN_SLOT so it reads
back the same value on every later update or full reinstall instead of
potentially landing on a different slot (which would silently move an
already-configured instance's TURN port out from under it — the same
"never touch what's already the box's answer" rule everything else in
update mode already follows).

Verified the allocation logic against a mock: first instance gets slot
0, a second gets slot 1 without stepping on the first, both instances
keep their own slot across a simulated re-run, and a third new instance
correctly lands on the next free slot (2) rather than reusing either.

Threaded the computed port/range through every place that used to
hardcode 3479/49253/49452: the coturn compose block, the UFW rule
(now also labeled with the instance suffix, matching this file's other
UFW comments), and the Calls-plugin TURN config text in the generated
README/System-Console instructions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-13 03:45:28 +00:00
Claude 158df0d545 Fix embedded-coturn relay-port overlap between Asterisk and Mattermost
Confirmed live on a box that retired the shared coturn service in favor
of each service running its own dedicated/embedded coturn permanently:
Mattermost's embedded-coturn fallback used relay range 49153-49352,
which overlaps Asterisk's embedded coturn range (49152-49252) by ~100
UDP ports. Both run network_mode: host, so with shared coturn out of
the picture this is the exact same collision CLAUDE.md documents as the
original, already-fixed-once bug that the shared coturn service was
built to solve in the first place — reintroduced here because
Mattermost's embedded-coturn fallback path apparently never got checked
against Asterisk's numbers when it was written.

Moved Mattermost's embedded relay range to 49253-49452 (same 200-port
width, now contiguous with and non-overlapping Asterisk's 49152-49252).
Updated the docker-compose command flags, the matching UFW rule, and
added a comment explaining the offset so it doesn't drift back into
collision — and noting the known residual gap this doesn't cover: two
Mattermost instances *both* falling back to embedded coturn at once
would still collide with each other on these same fixed numbers. Not
fixed here since it requires more than one Mattermost instance to be
running without shared coturn at the same time, which isn't this box's
situation; flagged in-code for whoever hits it.

Also made asterisk.sh's generated README port table stop unconditionally
claiming a TURN relay range it isn't actually publishing when the shared
coturn service (not this install's own container) is fronting TURN
instead — it now branches on USE_EMBEDDED_COTURN, which the function
already receives as a parameter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-13 03:40:32 +00:00
Claude ca239a3886 Retire the shared coturn service — every WebRTC/SIP service now runs its own
Shared coturn (services/coturn.sh, ensure_coturn_user in lib/common.sh) is no
longer an installable or usable option anywhere in this repo. It's moved to
attic/coturn.sh (with tools/coturn-test-check.sh alongside it), which is
outside setup.sh's services/*.sh glob, so it never registers, never appears
in the menu, and `sudo ./setup.sh coturn` now fails with "unknown service".

Asterisk and Mattermost each already had an opt-out to run their own
dedicated coturn instead of the shared one; that opt-out is now the only
behavior — the shared-coturn preference, the opt-out prompt, and every
ensure_coturn_user() call site are gone. find_free_coturn_range()
(lib/common.sh) is what makes unconditional dedicated coturn safe: it scans
every coturn-owning service's own .env on the box for already-claimed relay
ranges and picks one that can't collide, so Asterisk + any number of
Mattermost instances can each run their own coturn on one box without the
relay-port collisions this repo's coturn history warns about.

Existing installs still pointed at a shared coturn container are left
running as-is on `update` (no silent migration attempt against a service
that no longer exists to heal against) — a full/fresh reinstall is the
migration path, which generates a new dedicated coturn with fresh
credentials and says so.

Also updates CLAUDE.md's coturn guidance for future service authors,
attic/README.md with the retirement rationale, and stale
services/coturn.sh path references in services/asterisk.sh,
tools/pstn-test-check.sh, README.md, and docs/vps-sizing-recommendations.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crt4ymNEHEbWqscB1qvZgC
2026-08-13 02:12:32 +00:00
Claude d835e0d734 mattermost, asterisk: dynamic, collision-safe dedicated coturn ranges
Mattermost's own embedded-coturn fallback hardcoded the same relay range
(49153-49352) for every instance, with no per-instance offset -- running
two Mattermost instances without the shared coturn service (or one
alongside Asterisk's own dedicated coturn, now possible via the prior
commit) would silently reproduce the exact pre-merge collision bug this
repo's coturn history warns about, just among Mattermost instances
instead of Asterisk/Mattermost.

Adds find_free_coturn_range() (lib/common.sh, standalone-mode-stubbed in
both services matching the existing port_in_use/find_free_port
convention): a coturn relay range can't be collision-checked with live
socket scanning the way a single port can -- coturn only opens ports
inside its configured range on demand, so an idle range looks the same
as an unclaimed one to ss/netstat. The only reliable check is reading
what every other coturn-owning service's .env on the box actually
claims (COTURN_MAX_PORT for the shared instance, TURN_MAX_PORT for each
dedicated one) and picking a range starting safely past the highest.

Also adds Mattermost's own opt-out prompt for the shared coturn
preference, matching the one just added to Asterisk (fresh-install-only,
never re-asked on update, same as every other coturn-shape decision in
that file). An update now explicitly preserves its existing dedicated
range from .env rather than silently recomputing a new one.

Verified end-to-end: a shared instance + Asterisk's dedicated coturn +
two independent Mattermost instances, each discovering and avoiding
every range already claimed by the others, land on entirely
non-overlapping port blocks.
2026-08-13 00:55:31 +00:00
Claude aa65b5ef5b Make "Full reinstall" a real teardown for asterisk, mattermost, and coturn
Extends the security-dashboard prototype to the shared-coturn trio, since
these three are exactly the case that pattern was built for — a fresh
reinstall of any of them today just overwrote files in place without
stopping old containers first, and coturn's own fresh path never made an
informed choice about the consumer credentials/database it happens to
leave alone (safe today, but by omission rather than design).

- asterisk.sh / mattermost.sh: "Full reinstall" now stops the existing
  containers (`docker compose down`) before falling through to the normal
  install flow, and asks a single explicit question — delete stored data
  (PBX config/spool/voicemail for Asterisk; Postgres db/uploads/config/
  plugins for Mattermost) — defaulting to preserve. Their shared-coturn
  TURN credential is deliberately left alone either way (reused from
  cache via ensure_coturn_user(), same as update) — it's not this
  service's own data, and coturn already handles that continuity.
  Mattermost's existing "_db_has_data" check already reads the
  filesystem to decide whether to reuse or regenerate DB_PASS, so the
  wipe/preserve choice composes with that for free — no separate flag
  needed. Asterisk's warns to re-run pstn-trunk afterward if data is
  wiped, since that's what actually goes stale (its dialplan patch),
  not the fabricated "AMI secret" framing an earlier draft of this
  warning used before I checked the actual code.

- coturn.sh: "Full reinstall" now lists which consumers are currently
  registered (from users/*.env) and asks explicitly whether to also
  wipe TURN credentials and the user database, instead of silently
  preserving them as an unexamined side effect of never deleting the
  directory. Defaults to preserve. If the operator does choose to wipe,
  the running container is restarted afterward — it holds the old,
  now-deleted turndb file open, so new turnadmin writes to the fresh
  file would otherwise go unseen until a restart anyway. Every affected
  consumer already self-heals a missing credential on its own next
  Update run via ensure_coturn_user()'s existing cache-miss path — no
  changes needed there, just confirmed it covers this case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-12 13:07:38 +00:00
Claude ad011b2db8 Add check_container_health helper, wired into mattermost.sh as reference
A service's "Started" message after docker compose up -d doesn't mean
the app is actually working — it can still crash-loop (bad DB
password, missing required env var, etc.) with no visible sign until
someone separately runs docker ps -a much later, exactly what happened
repeatedly this session (mattermost, koha-db, homebox, vaultwarden,
filebrowser all showed a clean "Started" message while crash-looping).

check_container_health (lib/common.sh) waits briefly, checks the
container's actual status and restart count via docker inspect, and
prints recent logs automatically if it's not running or has already
restarted — instead of a misleading one-line success message.

Wired into mattermost.sh's own start step as the reference
implementation, guarded by declare -F so standalone runs (no
lib/common.sh sourced) degrade gracefully. Not retrofitted across
every other service in one pass — this establishes the shared helper
so other services can adopt it incrementally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 03:24:50 +00:00
Claude 74628f3736 Fix vaultwarden SMTP false-positive and mattermost DB password mismatch
vaultwarden: SMTP_PORT defaulted to "587" and SMTP_SECURITY was a
hardcoded "starttls" literal in the .env template, written
unconditionally regardless of whether SMTP_HOST was ever provided.
Confirmed live: skipping SMTP entirely (blank SMTP_HOST) still wrote
real values for those two, and Vaultwarden reads that as "some SMTP
config is present," refusing to start ("Both SMTP_HOST and SMTP_FROM
need to be set") even with host/from genuinely blank. Both now stay
empty unless SMTP_HOST is actually set.

mattermost: DB_PASS/MM_SECRET were only reused from the existing .env
when MODE=update — a "fresh" reinstall always generated a new
POSTGRES_PASSWORD. Confirmed live: choosing fresh after removing only
the mattermost app container (not the whole directory) regenerates the
password in .env while db/'s existing Postgres data still enforces the
OLD one from its first init (the entrypoint skips re-init on existing
data), causing "password authentication failed for user mattermost" on
every start. Whether db/ already has real data is what actually
determines whether the old password is still live, not which reinstall
mode was chosen — reuse the existing secrets whenever db/ is non-empty,
regardless of MODE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 03:22:53 +00:00
Claude 39e7b2ae6e Fix Mattermost crash-looping with permission denied on config.json
The official mattermost/mattermost-team-edition image runs as a fixed
UID/GID 2000 baked into the image — it does not read PUID/PGID env vars,
that's a LinuxServer.io s6-overlay convention this image doesn't use.
This file set them anyway (computed from ACTUAL_USER's uid/gid), which
did nothing, while the actual host directories (./data, ./logs,
./config, ./plugins) got chowned to ACTUAL_USER instead of 2000:2000.

Confirmed live: the container fails on its very first start with
"could not create config file: open /mattermost/config/config.json:
permission denied" and crash-loops — which then presents as a 502 from
Caddy, an easy trail to follow to the wrong place since Caddy itself
was fine.

Removed the dead PUID/PGID mechanism and chown the app's own volumes to
2000:2000 after the existing ACTUAL_USER chown. db (postgres:15-alpine)
isn't affected — its entrypoint fixes its own volume ownership on
startup. Runs on both fresh installs and "update" reruns, so re-running
the installer on an already-broken instance self-heals it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-10 23:41:41 +00:00
Claude 9e06ed4b83 Bake cross-service port collision avoidance into every service script
With 70+ services sharing a handful of common default ports (emby and
jellyfin both default to 8096, changedetection and frigate both default
to 5000, arm and nextcloud both default to 8080...), nothing previously
checked whether a service's default port was actually free on the host.
Whichever service installed second would silently write a compose file
claiming an already-held port, only failing at `docker compose up` time.

Adds two shared helpers to lib/common.sh:
- port_in_use PORT [PROTO] — true if something's already listening
- find_free_port VARNAME START [PROTO] — scans upward, writes back the
  first free port

Every service that publishes a fixed host port now scans before writing
docker-compose.yml, on every install (not just when adding an explicit
additional instance). On a normal single-install host this is a silent
no-op; it only changes behavior when something else already holds the
port.

- The 19 services already given multi-instance support this session had
  their port scan moved out of the "add instance" branch to run
  unconditionally, since the same collision risk exists on a plain first
  install.
- 20 more services with previously-hardcoded ports gained scanning for
  the first time: archivebox, arm, calibre-web, changedetection,
  drum-rhythm-game, gatus, n8n, nextcloud, onlyoffice, stirling-pdf,
  uptimekuma, portainer, iopaint (both GPU/CPU compose branches), koha
  (paired), syncthing (paired), wg-easy (paired, plus WG_PORT env so
  generated peer configs keep the right Endpoint), homeassistant
  (bridge-mode only — host mode can only warn), frigate and
  frigate-audio (multi-port stacks, moved together).
- caddy.sh is the deliberate exception: 80/443 stay fixed and only warn
  on collision, since silently moving Caddy itself would leave nothing
  listening where any client actually looks.
- authelia.sh needs no change — it has no published host port at all.
- Every service's standalone bootstrap fallback (sudo bash services/x.sh
  with no sibling files) got the same two helpers duplicated into its
  stub block, matching how every other shared helper is already handled
  there.

Documents the full pattern in CLAUDE.md's new "Port collision avoidance"
section, including the quoted-heredoc/backtick-escaping gotcha and the
network_mode:host limitation (can only scan ports the app takes as a
configurable env var).

Verified via bash -n on every changed file, plus functional runs seeding
occupied ports for each collision shape used here (single, paired,
multi-port stacks) and confirming the scan/shift and generated
compose/README output are correct — including the emby/jellyfin,
nextcloud/arm, and frigate/changedetection collision scenarios that
originally motivated this.
2026-08-09 23:55:49 +00:00
Claude 5f36b14f93 mattermost: add PikaPods migration helper (DB dump + files import)
New opt-in prompt on fresh/new installs (skipped on "update" reruns,
where an existing instance is already in real use and importing over
it would be destructive): "Migrating from an existing Mattermost
instance (e.g. PikaPods)?" -- if yes, generates
migrate-from-pikapods.sh in the instance's own directory, same
generated-helper pattern as Immich's import-photos.sh.

Checked PikaPods' own docs before writing this rather than guessing
at their export mechanics: they expose per-pod SFTP (file access) and
a Database-access toggle that hands you an Adminer link for a full
SQL dump -- their own documented backup/migration flow is stop the
pod, SFTP the files, export the DB via Adminer. The generated script
assumes that shape (plain-text SQL dump + a files directory) and says
so in its header, including that PikaPods' exact SFTP layout wasn't
verified against a live pod so the files-argument path needs the
user's own confirmation.

What the script does: stops the mattermost container (leaves the DB
container running), drops and recreates the database owned by the
same existing role -- so .env's credentials are never touched or
regenerated, avoiding the "restored data, mismatched password" bug
class fixed elsewhere in this repo -- imports the dump via psql,
rsyncs the files directory into ./data, restarts. Requires typing
"YES" to proceed since it's destructive to whatever's currently in
the fresh instance's database.

Correctly parameterized per-instance: pulled from install_mattermost's
own MM_CONTAINER/DB_CONTAINER variables, so it's already correct for
either the first instance or an additional named one.

Verified end-to-end: prompt fires correctly at the right point in the
flow, generated script is syntactically valid, and the container
names/paths it's parameterized with match the actual instance being
installed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBtExJcqxnokyZZKmphdug
2026-08-09 21:45:26 +00:00
Claude cf3d4bf6de Extract coturn into a shared service; add Mattermost multi-instance support
Asterisk and Mattermost each used to embed their own dedicated coturn
container (network_mode: host), and their default relay port ranges
overlapped by ~100 UDP ports — running both on one box meant a
coin-flip over which service's active call lost its media relay.

- services/coturn.sh: new shared TURN/STUN relay, one instance for
  every consumer instead of one each. Runs --lt-cred-mech with a
  SQLite user database (not --use-auth-secret — coturn doesn't
  support both auth mechanisms on one instance at once, confirmed via
  coturn's own upstream docs/issues) so each consumer gets its own
  dedicated username/password without stepping on any other's.

- lib/common.sh: ensure_coturn_user() — chain-installs coturn.sh on
  first need (same declare -F guard pattern as the existing
  asterisk -> security-dashboard chaining) and registers/reuses a
  per-consumer credential, mirroring configure_caddy_for_service's
  out-param convention.

- services/asterisk.sh: _asterisk_write_compose gains a
  USE_EMBEDDED_COTURN flag. New installs use the shared service;
  existing installs keep their dedicated coturn exactly as-is on
  every "update" (detected from the existing compose file before
  regenerating it, so a rebuild can never silently drop the container
  its own .env TURN_PASSWORD still points at) and only switch on an
  explicit "fresh" reinstall, with a warning first.

- services/mattermost.sh: same embedded/shared coturn handling, plus
  genuine multi-instance support (separate dir/containers/DB/ports per
  instance, auto-scanned free ports for extras) for real isolation
  between groups, as opposed to Team Edition's built-in Teams feature.
  Calls plugin TURN config switched from the HMAC "TURN Static Auth
  Secret" field to the verified "ICE Servers Configurations" JSON
  field, which accepts the same fixed username/credential shared
  coturn issues. Also fixes a latent bug found while adding proper
  update-mode detection: DB_PASS/MM_SECRET were regenerated on every
  single rerun with no existing-install check at all, silently
  breaking Postgres auth on any reinstall.

- CLAUDE.md: documents the ensure_coturn_user pattern (including the
  auth-mechanism constraint and the embedded-coturn migration-safety
  rule) for any future service that needs TURN.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQkdAn3iG5A4WoqU9FHMaN
2026-08-04 16:32:08 +00:00
Claude 883f0d2557 Document restricting DMs to teammates in Mattermost's README
Answers a real gap: Teams alone don't limit who can Direct Message
whom — that's a separate System Console setting
(TeamSettings.RestrictDirectMessage), free in Team Edition. Documented
it in the Teams section along with the caveat that it only filters the
DM picker UI, not a hard boundary (existing DMs unaffected, multi-team
users can still DM across all their teams).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQkdAn3iG5A4WoqU9FHMaN
2026-08-04 13:57:51 +00:00
Claude 2fc49fa43e Document Teams setup in Mattermost's generated README
Team Edition includes multiple Teams natively (no Enterprise license
needed), but nothing in the generated README said so or explained how
to create one. Added a Teams section covering creation, adding
members, and multi-team membership, plus a one-line pointer in the
install summary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQkdAn3iG5A4WoqU9FHMaN
2026-08-04 13:48:49 +00:00
Claude 1fc0a6edfe Apply the local/remote Caddy mode resolution to every service, not just traccar
traccar.sh's caddy_net wiring was fixed to mirror configure_caddy_for_service's
own mode resolution (CADDY_MODE from site config, then a local ~/docker/caddy,
then the legacy CADDY_REMOTE_HOST var) instead of only checking for the local
directory. That same bare directory check was copy-pasted into the caddy_net
wiring of every other Docker service in the repo, so a site with Caddy on a
different box would silently fail to join any of their containers to caddy_net
during setup (or, for homeassistant/koha, only get half the wiring right).

Applied the same fix mechanically across all 37 services using the standard
_CADDY_NET_BLOCK/_CADDY_NET_SECTION pattern (verified identical text via
scripted diff before touching any of them), plus by hand for:
- homeassistant.sh and koha.sh, which use their own differently-shaped
  variables (HA_CADDY_NET_LINES / _CADDY_NET_ENTRY) for the same decision
- paintplus.sh and ai-stack.sh, which do a live `docker network connect`
  instead of a compose network block
- watchyourlan.sh, whose Caddy note was worded for local-only setups

sms-inbound.sh got more than a mode swap: its Caddy wiring was hand-rolled
(not routed through configure_caddy_for_service) and had no remote-Caddy
path at all — a remote Caddy box would get a misleading "Caddy isn't
installed here" message instead of a snippet. Added
_sms_write_caddy_snippet(), mirroring the snippet-file pattern
configure_caddy_for_service uses everywhere else, and pointed the firewall
gate at the same three-way mode instead of a two-way dir check.

Verified: bash -n across all of services/*.sh, a scripted check that every
touched file has exactly one _CADDY_MODE resolution and no leftover bare
`[ -d "$DOCKER_DIR/caddy" ]` feeding a caddy_net decision, and spot-checked
docker compose config renders (traccar, mattermost) confirming the ${VAR}
interpolation and multi-service usage sites still resolve correctly.
2026-08-03 18:54:02 +00:00
Claude fa906186c3 fix(services): conditional caddy_net across all Docker service scripts
Only include the caddy_net network block in generated docker-compose.yml
files when ~/docker/caddy exists. Prevents "network caddy_net declared
as external, but could not be found" on standalone installs without Caddy.

Affected: actualbudget, archivebox, arm, audiobookshelf, calibre-web,
changedetection, drum-rhythm-game, emby, fmd, frigate, frigate-audio,
frigate-notify, gatus, homeassistant, immich, iopaint, jellyfin, joplin,
koha, lyrion, magicmirror, mail-archiver, mattermost, mealie, meshcentral,
n8n, nextcloud, ntfy, onlyoffice, portainer, stirling-pdf, syncthing,
traccar, unifi, uptimekuma, vaultwarden, wg-easy

Skipped: authelia (creates caddy_net itself), caddy, crowdsec, wolf,
wolf-pair, and services using network_mode: host with no caddy_net.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
2026-06-20 04:16:41 +00:00
Claude 7c3f101fe0 Add asterisk, nextcloud, onlyoffice, mattermost services + vendor/easy-asterisk
asterisk.sh (homelab):
- Easy Asterisk PBX with self-hosted coturn TURN server
- Vendored from outis1one/easy-asterisk v0.10.0 for offline install
- LAN-only or FQDN mode (TLS + TURN relay for remote access)
- Auto-answer SIP headers for intercom use case
- Authelia SSO for web admin; WEB_ADMIN_AUTH_DISABLED=true when chosen
- UFW rules: 5060-5061, 8080, 8088-8089, 3478, 10000-20000/udp, 49152-49252/udp
- Builds custom Docker image from vendor/easy-asterisk/

nextcloud.sh (utilities):
- Custom Dockerfile: nextcloud:apache + smbclient (SMB external storage)
- MariaDB 10.11 sidecar with matching env vars
- OVERWRITEPROTOCOL/OVERWRITECLIURL/TRUSTED_PROXIES set for Caddy
- Enables files_external app after first-run init (waits up to 90s)

onlyoffice.sh (utilities):
- JWT generated once, preserved across re-runs
- _ensure_yq: auto-installs yq v4 for FileBrowser config patching
- _wire_nextcloud: idempotent occ wiring (DocumentServerUrl, jwt_secret)
- _wire_filebrowser: patches config.yaml + restarts container
- Caddy block overrides X-Frame-Options to allow iframe embedding

mattermost.sh (utilities):
- PostgreSQL 15-alpine + Mattermost Team Edition + coturn (port 3479)
- 8443/udp for Calls plugin RTC server
- coturn uses --use-auth-secret HMAC mode (required by Calls plugin)
- SITE_URL computed from SITE_DOMAIN, promptable
- UFW: 8443/udp, 3479, 49153-49352/udp

vendor/easy-asterisk/:
- All upstream source files vendored for offline/self-contained installs
- Dockerfile, docker/entrypoint.sh, docker/coturn-entrypoint.sh
- easy-asterisk-v0.10.0.sh (6929-line management script)
- scripts/vpn-diagnostics.sh, scripts/dns-whitelist.sh
- .env.example

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-09 00:28:38 +00:00
Claude e9f05cc127 Use stored SITE_DOMAIN as default in Caddy domain prompt
lib/common.sh: configure_caddy_for_service now pre-fills the domain prompt
with $DEFAULT_SUBDOMAIN.$SITE_DOMAIN when a site domain has been configured
(setup.sh configure / ~/docker/.config). No more typing the full domain for
every service — just press Enter to accept the default.

services/mattermost.sh: remove redundant custom Caddy/domain block added in
the previous commit. MATTERMOST_SITE_URL is already computed from SITE_DOMAIN
before configure_caddy_for_service is called, so the simple call is sufficient.

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-08 18:41:46 +00:00
Claude ab778868df Fix Mattermost Calls, add Authelia to Asterisk web admin
Mattermost Calls:
- Add 8443/udp to compose ports for the Calls plugin RTC server (WebRTC
  direct path; coturn relay is only the fallback, not the sole path)
- Add 8443/udp to UFW rules and router port-forward table
- Warn that WebRTC requires HTTPS — calls silently fail over HTTP
- Prompt for Caddy domain and update MATTERMOST_SITE_URL in .env to match
  the HTTPS URL before Caddy is wired (previously SITEURL was written before
  the domain was known, leaving it as http://localhost:8065)
- Update README with RTC server address field and corrected port table

Asterisk web admin:
- No built-in auth: add Authelia SSO check matching CLAUDE.md pattern
- Set WEB_ADMIN_AUTH_DISABLED=true in .env when Authelia handles auth
  (prevents double-login prompts)

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-08 18:41:46 +00:00
Claude d948e86341 Add Nextcloud, OnlyOffice, and Mattermost services
Nextcloud: custom Dockerfile (nextcloud:apache + smbclient) for SMB external
storage support without AIO. All data uses bind mounts so Kopia/Borg coverage
is automatic. Enables files_external app after first boot.

OnlyOffice: JWT-secured document server wired to Nextcloud via occ commands
and FileBrowser Quantum config.yaml if both are installed. Port 8082.

Mattermost: Team Edition + PostgreSQL + dedicated coturn on port 3479 (does
not conflict with Easy Asterisk's coturn on 3478). Bind mounts throughout.
UFW rules and router port-forward instructions printed at install time.

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-08 18:41:46 +00:00