Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions scripts/seed-communities.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Upsert local dev community hosts for row-zero host binding.
--
-- :hosts is a newline-separated list of authorities derived by seed-hosts.py
-- (e.g. "localhost:3000", "localhost", "127.0.0.1", "127.0.0.1:3000"). Each
-- distinct authority becomes a communities row; existing rows are left
-- untouched (ON CONFLICT against the unique lower(host) index).
INSERT INTO communities (host)
SELECT btrim(h)
FROM unnest(string_to_array(:'hosts', E'\n')) AS h
WHERE btrim(h) <> ''
ON CONFLICT (lower(host)) DO NOTHING;
66 changes: 66 additions & 0 deletions scripts/seed-hosts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Derive the loopback community hosts to seed for local dev.

The relay uses row-zero host binding: it resolves a request's community from the
Host header and fails closed (404) when that authority is not a `communities`
row. Local dev tooling hits the relay under several loopback authorities
(``localhost``/``127.0.0.1``, with and without the port), and under host binding
those are *distinct* keys -- so we seed each one to avoid a fail-closed 404 when
one client uses an alternate authority.

Reads RELAY_URL from the environment (default ``ws://localhost:3000``) and prints
the authorities to seed, one per line, normalized the same way the relay
normalizes a community host. seed-local-community.sh feeds these to
seed-communities.sql.
"""

import os
from urllib.parse import urlparse


def derive_hosts(relay_url):
"""Return the ordered, de-duplicated list of authorities to seed."""
parsed = urlparse(relay_url)
host = (parsed.hostname or "").rstrip(".").lower()
port = parsed.port
scheme = parsed.scheme.lower()

def authority(h):
if not h:
return ""
display_host = f"[{h}]" if ":" in h and not h.startswith("[") else h
default_port = (scheme == "ws" and port == 80) or (scheme == "wss" and port == 443)
if port and not default_port:
return f"{display_host}:{port}"
return display_host

hosts = []
primary = authority(host)
if primary:
hosts.append(primary)

# Non-loopback deployments seed only RELAY_URL's authority; loopback dev
# additionally seeds both localhost and 127.0.0.1, with and without port.
if host in {"localhost", "127.0.0.1"}:
hosts.extend(["localhost", "127.0.0.1"])
if port:
hosts.extend([f"localhost:{port}", f"127.0.0.1:{port}"])

seen = []
for h in hosts:
if h and h not in seen:
seen.append(h)
return seen


def main():
relay_url = os.environ.get("RELAY_URL", "ws://localhost:3000")
hosts = derive_hosts(relay_url)
if not hosts:
raise SystemExit("could not derive a host from RELAY_URL")
for h in hosts:
print(h)


if __name__ == "__main__":
main()
75 changes: 18 additions & 57 deletions scripts/seed-local-community.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
# The relay intentionally fails closed when the request Host header is not in
# `communities`. Local dev uses loopback hosts, so bootstrap must create those
# rows after migrations before desktop/Tauri HTTP bridge calls can succeed.
#
# Host derivation lives in seed-hosts.py and the upsert in seed-communities.sql,
# kept out of this shell script on purpose: inlining the Python as a heredoc
# inside `$(...)` made bash 3.2 (stock macOS /bin/bash) fail to parse the script
# at all. The shell now only wires the two together and never parses their
# source, so it is portable across bash versions.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand All @@ -24,71 +30,26 @@ export PGPASSWORD="${PGPASSWORD:-buzz_dev}"
export PGDATABASE="${PGDATABASE:-buzz}"
export RELAY_URL="${RELAY_URL:-ws://localhost:3000}"

hosts_sql=$(python3 - <<'PY'
import os
from urllib.parse import urlparse

relay_url = os.environ.get("RELAY_URL", "ws://localhost:3000")
parsed = urlparse(relay_url)

host = (parsed.hostname or "").rstrip(".").lower()
port = parsed.port
scheme = parsed.scheme.lower()

def authority(host, port, scheme):
if not host:
return ""
display_host = f"[{host}]" if ":" in host and not host.startswith("[") else host
default_port = (scheme == "ws" and port == 80) or (scheme == "wss" and port == 443)
if port and not default_port:
return f"{display_host}:{port}"
return display_host

primary = authority(host, port, scheme)
hosts = []
if primary:
hosts.append(primary)

# Local desktop/dev tooling has historically used both localhost and 127.0.0.1,
# and some HTTP clients can omit the default/non-default port in Host handling.
# Under row-zero host binding these are distinct hosts, so seed loopback aliases
# for local dev to avoid a fail-closed 404 when one side uses an alternate
# authority. Non-loopback deployments seed only RELAY_URL's authority.
if host in {"localhost", "127.0.0.1"}:
hosts.extend(["localhost", "127.0.0.1"])
if port:
hosts.extend([f"localhost:{port}", f"127.0.0.1:{port}"])

seen = []
for h in hosts:
if h and h not in seen:
seen.append(h)

if not seen:
raise SystemExit("could not derive a host from RELAY_URL")

print(",\n".join(f" ('{h.replace("'", "''")}')" for h in seen))
PY
)
hosts="$(python3 "${SCRIPT_DIR}/seed-hosts.py")"
if [[ -z "${hosts}" ]]; then
echo "error: could not derive any community host from RELAY_URL=${RELAY_URL}" >&2
exit 1
fi

sql="
INSERT INTO communities (host)
SELECT host
FROM (VALUES
${hosts_sql}
) AS v(host)
ON CONFLICT (lower(host)) DO NOTHING;
"
sql_file="${SCRIPT_DIR}/seed-communities.sql"

if command -v psql >/dev/null 2>&1; then
PGPASSWORD="${PGPASSWORD}" psql -h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" -d "${PGDATABASE}" -v ON_ERROR_STOP=1 -c "${sql}"
PGPASSWORD="${PGPASSWORD}" psql -h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" -d "${PGDATABASE}" \
-v ON_ERROR_STOP=1 -v hosts="${hosts}" -f "${sql_file}"
elif docker exec buzz-postgres psql --version >/dev/null 2>&1; then
docker exec -i -e PGPASSWORD="${PGPASSWORD}" buzz-postgres \
psql -U "${PGUSER}" -d "${PGDATABASE}" -v ON_ERROR_STOP=1 -c "${sql}"
psql -U "${PGUSER}" -d "${PGDATABASE}" -v ON_ERROR_STOP=1 -v hosts="${hosts}" < "${sql_file}"
else
echo "error: neither psql nor buzz-postgres docker psql is available" >&2
exit 1
fi

echo "Seeded local dev community host(s):"
echo "${hosts_sql}" | sed -E "s/^ +\('(.+)'\),?$/ - \1/"
while IFS= read -r host; do
[[ -n "${host}" ]] && echo " - ${host}"
done <<< "${hosts}"
Loading