LibrePortal/containers/mastodon/scripts/mastodon_install_hooks.sh
librelad 65167463f9 fix(chat apps): tag every service so its IP actually substitutes
Installing rocketchat failed with

    invalid IPv4 address: ParseAddr("IP_DATA_2"): unable to parse IP

ipUpdateComposeTags allocates one IP per SERVICE_TAG_N annotation and fills
IP_TAG_i only where SERVICE_TAG_i exists. The four new apps tagged only their
primary service, so every sidecar — matrix's postgres, mattermost's postgres,
rocketchat's mongo, and fifteen of stoat's sixteen — kept a literal IP_DATA_n
in the deployed compose and docker refused to create the container.

Tag every service that carries an ipv4_address, index-aligned with its IP_TAG.
For stoat that also meant moving caddy from SERVICE_TAG_1 to _6 so the indices
line up with the IPs rather than the reading order.

mastodon had the same latent break (IP_TAG_2 and _3 untagged) and is fixed the
same way — it would have failed on first install for the same reason.

SERVICE_TAG carries the compose *key*, not container_name: 'libreportal app
restart <app> <service>' passes it to 'docker compose restart', which only
understands keys.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:48:09 +01:00

72 lines
3.5 KiB
Bash

#!/bin/bash
# Mastodon install hooks.
# Web Push identity.
# ---------------------------------------------------------------------------
# The two VAPID values are not independent secrets — they are the two halves of
# one P-256 keypair, and the browser verifies that the push request is signed by
# the private key matching the public key it subscribed with. The framework's
# RANDOMIZED* generators mint each placeholder on its own, so they can produce
# two well-formed strings but never a *pair*: what shipped before was two
# unrelated random values, and web push could not work with them. Stoat hits the
# same wall and solves it the same way, in its own install hook.
#
# Mastodon's webpush gem expects unpadded URL-safe base64 of the raw key
# material: the 32-byte private scalar (43 chars) and the 65-byte uncompressed
# public point (87 chars). Both are sliced out of the SEC1 DER, whose layout for
# prime256v1 is a fixed 121 bytes — a 2-byte SEQUENCE header, INTEGER 1, then
# `04 20` introducing the private scalar at offset 7, and the public point as
# the trailing field. Both slices were cross-checked against `openssl ec -text`.
#
# Runs at install_post_setup: the deployed config exists by then (so there is
# something to write to) and the compose has not been templated yet (so the pair
# reaches the compose on this same install).
#
# Generated once, then left alone. Rotating the pair invalidates every push
# subscription clients are holding, so an existing well-formed pair is never
# replaced — including across reinstalls.
mastodon_install_post_setup()
{
local app_name="${1:-mastodon}"
local config_file="${containers_dir}${app_name}/${app_name}.config"
[[ -f "$config_file" ]] || return 0
# Keep a pair that already has the right shape. The length test is also what
# retires the old values: two independent RANDOMIZEDVAPID strings are the
# wrong length for the public half, so an install carrying them regenerates
# once and is correct from then on.
if [[ "$CFG_MASTODON_VAPID_PRIVATE_KEY_1" =~ ^[A-Za-z0-9_-]{43}$ \
&& "$CFG_MASTODON_VAPID_PUBLIC_KEY_1" =~ ^[A-Za-z0-9_-]{87}$ ]]; then
return 0
fi
local pem der
pem=$(mktemp) || return 0
der=$(mktemp) || { rm -f "$pem"; return 0; }
if ! openssl ecparam -name prime256v1 -genkey -noout -out "$pem" 2>/dev/null \
|| ! openssl ec -in "$pem" -outform DER -out "$der" 2>/dev/null; then
rm -f "$pem" "$der"
isError "Could not generate a VAPID keypair for $app_name — web push will not work."
return 0
fi
local vapid_private vapid_public
vapid_private=$(head -c 39 "$der" | tail -c 32 | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
vapid_public=$(tail -c 65 "$der" | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
rm -f "$pem" "$der"
# Refuse to write a malformed pair over a working one — a short read or an
# openssl build with a different DER layout would otherwise quietly break
# push instead of leaving it as it was.
if [[ ! "$vapid_private" =~ ^[A-Za-z0-9_-]{43}$ || ! "$vapid_public" =~ ^[A-Za-z0-9_-]{87}$ ]]; then
isError "Generated VAPID keypair had an unexpected shape — leaving $app_name's push keys alone."
return 0
fi
updateConfigOption "CFG_MASTODON_VAPID_PRIVATE_KEY_1" "$vapid_private" "$config_file"
updateConfigOption "CFG_MASTODON_VAPID_PUBLIC_KEY_1" "$vapid_public" "$config_file"
isSuccessful "Generated a VAPID keypair for $app_name web push."
}