eleven / messenger Public

repo: scrub luan.chat (a personal domain) from the codebase (#316)

* repo: scrub luan.chat (a personal domain) from the codebase

luan.chat is a personal family instance, not product infrastructure, so
it shouldn't be baked into examples, tests, or shipped config. Replaced
throughout (CLAUDE.local.md, the operator runbook, deliberately keeps
its real references):

- Doc / test / placeholder examples → example.com (subdomain prefixes
  preserved, e.g. test.luan.chat → test.example.com; test assertions
  changed input + expected together so they stay valid).
- Functional refs → the product domain elevenmessenger.com:
  - ios/LChat/LChat.entitlements associated-domains applinks:*.luan.chat
    → applinks:*.elevenmessenger.com  (NB: functional — needs an app
    rebuild + AASA served on that host + Apple validation before
    universal links activate there).
  - push.go VAPID Subscriber admin@luan.chat → admin@elevenmessenger.com.
  - unfurl.go link-preview User-Agent +https://luan.chat → …elevenmessenger.com.
- test/knowledge.test.mjs: dropped luan.chat from the "bot must never
  leak a real hostname" guard (run.garden / elevenmessenger.com / paulca
  still guarded).
- Dead Miren deploy scripts: MIREN_CLUSTER default luanchat → mycluster.

go build/vet/test, npm test all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: drop luan.chat from the bot hostname-leak guard (merge follow-up)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 4f3ee794450974f6298e539400adbb2990c8c6a4 parent 5ce6b8b
28 files changed, +67 −67
  • .miren/agent.app.toml +1 −1
  • .miren/app.toml +1 −1
  • README.md +6 −6
  • agent/README.md +3 −3
  • agent/config.mjs +2 −2
  • broker/broker_test.go +4 −4
  • broker/db.go +2 −2
  • broker/main.go +1 −1
  • control_test.go +1 −1
  • deploy/go-router.sh +1 −1
  • deploy/miren.sh +2 −2
  • deploy/miren/agent.sh +2 −2
  • deploy/miren/home.sh +1 −1
  • deploy/miren/push.sh +1 −1
  • docs/connected-services.md +1 −1
  • docs/instance-router.md +4 −4
  • docs/onboarding-and-home-server.md +1 −1
  • home/test/crypto.test.mjs +2 −2
  • home/web/static/enroll.js +1 −1
  • internal/bot/eleven_test.go +6 −6
  • ios/LChat/LChat.entitlements +1 −1
  • ios/LChatCore/Tests/LChatCoreTests/InviteLinkTests.swift +10 −10
  • push.go +1 −1
  • push/db.go +1 −1
  • push/push_test.go +5 −5
  • test/knowledge.test.mjs +1 −1
  • unfurl.go +1 −1
  • versions_test.go +4 −4
diff --git a/.miren/agent.app.toml b/.miren/agent.app.toml
index c36877a..28d27bf 100644
--- a/.miren/agent.app.toml
+++ b/.miren/agent.app.toml
@@ -38,7 +38,7 @@ key = "LCHAT_ELEVEN_SESSION"
value = "/data/eleven-session.json"
# Set at deploy time via `-e` (see deploy/miren/agent.sh), NOT declared here:
-# LCHAT_BASE the instance to connect to, e.g. https://test-bots.luan.chat
+# LCHAT_BASE the instance to connect to, e.g. https://test-bots.example.com
# ANTHROPIC_API_KEY billing credential — lives only in this app's env
# LCHAT_INVITE one-time invite #fragment; used only on first boot
# LCHAT_ANNOUNCER_INVITE same, for the "Announcer" identity
diff --git a/.miren/app.toml b/.miren/app.toml
index 563318a..58b2363 100644
--- a/.miren/app.toml
+++ b/.miren/app.toml
@@ -41,4 +41,4 @@ value = "/data/lchat.db"
# 2. Use `-e` (plain), not `-s` (sensitive) — Miren stores sensitive vars but
# does not inject them into the container.
# It is NOT a decryption key (messages stay end-to-end encrypted); it only gates
-# the invite backend. Admin URL: https://group.luan.chat/admin#token=<token>
+# the invite backend. Admin URL: https://group.example.com/admin#token=<token>
diff --git a/README.md b/README.md
index a2c0b72..ff1c508 100644
--- a/README.md
+++ b/README.md
@@ -235,11 +235,11 @@ TOKEN=$(openssl rand -base64 32 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
# 3. Deploy (pass the token as a plain -e env var) and attach the domain:
miren deploy -e LCHAT_DB=/data/lchat.db -e "LCHAT_ADMIN_TOKEN=$TOKEN"
-miren route set group.luan.chat lchat
-# Your admin URL is then: https://group.luan.chat/admin#token=$TOKEN
+miren route set group.example.com lchat
+# Your admin URL is then: https://group.example.com/admin#token=$TOKEN
```
-Then point DNS for `group.luan.chat` at the cluster (a CNAME to the
+Then point DNS for `group.example.com` at the cluster (a CNAME to the
`*.miren.systems` hostname Miren prints, or an A record to the server IP). Once
DNS resolves, Miren provisions TLS automatically.
@@ -248,14 +248,14 @@ DNS resolves, Miren provisions TLS automatically.
keys, group name) all survive. **Do not `miren app delete` to update** — that
deletes the disk and wipes everything; use it only to intentionally reset.
-**More instances from one repo:** `./deploy/miren.sh <name> <name>.luan.chat`
+**More instances from one repo:** `./deploy/miren.sh <name> <name>.example.com`
gives each its own app + disk + token + route (re-run it to update an instance;
it preserves that instance's data too).
Your admin URL is then:
```
-https://group.luan.chat/admin#token=<ADMIN_TOKEN>
+https://group.example.com/admin#token=<ADMIN_TOKEN>
```
> WebSockets: the live feed uses a standard HTTP upgrade on `/ws`. Miren's
@@ -303,7 +303,7 @@ hands the server only ciphertext — exactly like the browser does.
```sh
# text as an argument
-tools/lchat-post.mjs --host https://group.luan.chat \
+tools/lchat-post.mjs --host https://group.example.com \
--token "$TOKEN" --priv "$PRIV" --thread "$TID" "deploy finished ✅"
# or pipe it in, with a custom screen name (config via env)
diff --git a/agent/README.md b/agent/README.md
index 0ee31b9..713bab0 100644
--- a/agent/README.md
+++ b/agent/README.md
@@ -26,7 +26,7 @@ console, its `#fragment` passed once as the matching `*_INVITE` env var.
| | |
|---|---|
| `ANTHROPIC_API_KEY` | The only recurring secret + cost. Every DM and every group @mention is one Claude API call billed to your account (bots in the "name" listen mode also spend a fraction-of-a-cent Haiku call per bare-name mention to decide whether to answer — `LCHAT_CLASSIFIER_MODEL` overrides). |
-| `LCHAT_BASE` | Your instance URL, e.g. `https://test.luan.chat`. |
+| `LCHAT_BASE` | Your instance URL, e.g. `https://test.example.com`. |
| `LCHAT_INVITE` | **One-time.** The `#fragment` of a `/join/…` link minted in the admin console (tick "This is an agent"). Spent after provisioning. |
Optional: `LCHAT_AGENT_NAME` (default `Claude`), `LCHAT_MODEL` (default
@@ -42,12 +42,12 @@ cd agent
npm install
# 1. Provision once (creates session.json — the bot's identity; back it up)
-LCHAT_BASE=https://test.luan.chat LCHAT_INVITE='<#fragment from the invite link>' \
+LCHAT_BASE=https://test.example.com LCHAT_INVITE='<#fragment from the invite link>' \
npm run provision
# 2. Run
export ANTHROPIC_API_KEY=sk-ant-...
-LCHAT_BASE=https://test.luan.chat npm start
+LCHAT_BASE=https://test.example.com npm start
```
Then, in the app, add the agent to a group or start a DM with it. It replies to
diff --git a/agent/config.mjs b/agent/config.mjs
index 5a4c2aa..61ec2cf 100644
--- a/agent/config.mjs
+++ b/agent/config.mjs
@@ -6,7 +6,7 @@
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
-// The instance base URL, e.g. https://test.luan.chat. WS base is derived
+// The instance base URL, e.g. https://test.example.com. WS base is derived
// (https -> wss, http -> ws).
export const BASE = (process.env.LCHAT_BASE || "").replace(/\/$/, "");
export const WS_BASE = BASE.replace(/^http/, "ws");
@@ -33,7 +33,7 @@ export const SESSION_PATH =
export function requireBase() {
if (!BASE) {
- console.error("Set LCHAT_BASE to your instance URL, e.g. https://test.luan.chat");
+ console.error("Set LCHAT_BASE to your instance URL, e.g. https://test.example.com");
process.exit(1);
}
}
diff --git a/broker/broker_test.go b/broker/broker_test.go
index eca8009..5384e3f 100644
--- a/broker/broker_test.go
+++ b/broker/broker_test.go
@@ -61,7 +61,7 @@ func fakeDNSimple(t *testing.T) *httptest.Server {
func TestExchange(t *testing.T) {
dns := fakeDNSimple(t)
ts := newTestBroker(t, dns.URL)
- _, tok, err := testDB.mintToken("https://test.luan.chat")
+ _, tok, err := testDB.mintToken("https://test.example.com")
if err != nil {
t.Fatalf("mint: %v", err)
}
@@ -115,12 +115,12 @@ func TestConfig(t *testing.T) {
// and never to an unregistered one (no open redirect).
func TestCallbackRelay(t *testing.T) {
ts := newTestBroker(t, "http://unused")
- if _, _, err := testDB.mintToken("https://test.luan.chat"); err != nil {
+ if _, _, err := testDB.mintToken("https://test.example.com"); err != nil {
t.Fatalf("mint: %v", err)
}
noRedirect := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
- good := base64.RawURLEncoding.EncodeToString([]byte("https://test.luan.chat")) + ".nonce123"
+ good := base64.RawURLEncoding.EncodeToString([]byte("https://test.example.com")) + ".nonce123"
resp, err := noRedirect.Get(ts.URL + "/oauth/dnsimple/callback?code=XYZ&state=" + good)
if err != nil {
t.Fatalf("callback: %v", err)
@@ -130,7 +130,7 @@ func TestCallbackRelay(t *testing.T) {
t.Fatalf("callback: got %d want 302", resp.StatusCode)
}
loc := resp.Header.Get("Location")
- if !strings.HasPrefix(loc, "https://test.luan.chat/oauth/dnsimple/callback?") || !strings.Contains(loc, "code=XYZ") || !strings.Contains(loc, "state=") {
+ if !strings.HasPrefix(loc, "https://test.example.com/oauth/dnsimple/callback?") || !strings.Contains(loc, "code=XYZ") || !strings.Contains(loc, "state=") {
t.Fatalf("relay Location = %q", loc)
}
diff --git a/broker/db.go b/broker/db.go
index 9f78883..986e977 100644
--- a/broker/db.go
+++ b/broker/db.go
@@ -12,7 +12,7 @@ import (
)
// DB holds the broker's only state: the per-instance bearer tokens the admin has
-// minted. Each token's label IS the instance's origin (e.g. "https://test.luan.chat")
+// minted. Each token's label IS the instance's origin (e.g. "https://test.example.com")
// — used both for logging and as the redirect-relay allowlist, so the relay only
// ever bounces a code back to an origin an admin has explicitly registered. It
// stores NO OAuth traffic — no codes, no access tokens ever touch disk. Tokens
@@ -45,7 +45,7 @@ func (db *DB) migrate() error {
_, err := db.sql.Exec(`
CREATE TABLE IF NOT EXISTS instance_tokens (
id TEXT PRIMARY KEY, -- random public handle, used to revoke
- label TEXT NOT NULL, -- the instance ORIGIN (scheme+host), e.g. https://test.luan.chat
+ label TEXT NOT NULL, -- the instance ORIGIN (scheme+host), e.g. https://test.example.com
token_hash TEXT NOT NULL UNIQUE, -- sha256(bearer token); the secret itself is never stored
created_at INTEGER NOT NULL,
last_used_at INTEGER
diff --git a/broker/main.go b/broker/main.go
index 01179cb..746ba5e 100644
--- a/broker/main.go
+++ b/broker/main.go
@@ -276,7 +276,7 @@ func (s *Server) admin(next http.HandlerFunc) http.HandlerFunc {
func (s *Server) mintHandler(w http.ResponseWriter, r *http.Request) {
var body struct {
- Label string `json:"label"` // the instance ORIGIN, e.g. https://test.luan.chat
+ Label string `json:"label"` // the instance ORIGIN, e.g. https://test.example.com
}
if json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<10)).Decode(&body) != nil || body.Label == "" {
http.Error(w, "label (instance origin) required", http.StatusBadRequest)
diff --git a/control_test.go b/control_test.go
index 503ac0b..8f5537b 100644
--- a/control_test.go
+++ b/control_test.go
@@ -40,7 +40,7 @@ func TestReorderDoesNotPullFlagAsValue(t *testing.T) {
}
func TestValidHost(t *testing.T) {
- ok := []string{"chat.example.com", "rtr-test.luan.chat", "a.b", "x1.y2.example.com"}
+ ok := []string{"chat.example.com", "rtr-test.example.com", "a.b", "x1.y2.example.com"}
bad := []string{"", "nodots", "-bad.example.com", "bad-.example.com", "has space.com", "Chat.Example.com", "a/b.com", "a..b"}
for _, h := range ok {
if !validHost(h) {
diff --git a/deploy/go-router.sh b/deploy/go-router.sh
index de2ecd5..f03e8ef 100755
--- a/deploy/go-router.sh
+++ b/deploy/go-router.sh
@@ -10,7 +10,7 @@
#
# Usage: ./deploy/go-router.sh [host]
# ./deploy/go-router.sh # host defaults to team.elevenmessenger.com
-# ./deploy/go-router.sh test.luan.chat
+# ./deploy/go-router.sh test.example.com
# SKIP_BOT=1 ./deploy/go-router.sh # skip the bot-ensure step
# ANTHROPIC_API_KEY=sk-ant-... ./deploy/go-router.sh # AI-enabled instead
#
diff --git a/deploy/miren.sh b/deploy/miren.sh
index 91070cf..cfb1060 100755
--- a/deploy/miren.sh
+++ b/deploy/miren.sh
@@ -4,7 +4,7 @@
# no per-instance config files.
#
# Usage: ./deploy/miren.sh <instance> <hostname> [admin-token]
-# ./deploy/miren.sh paja paja.luan.chat
+# ./deploy/miren.sh paja paja.example.com
#
# Miren's app.toml has no variable interpolation, so we template a throwaway copy
# in a temp dir (unique app name + unique disk name) and deploy from there.
@@ -13,7 +13,7 @@ set -euo pipefail
INSTANCE="${1:?usage: deploy/miren.sh <instance> <hostname> [admin-token]}"
HOST="${2:?usage: deploy/miren.sh <instance> <hostname> [admin-token]}"
TOKEN="${3:-$(openssl rand -base64 32 | tr -d '\n' | tr '+/' '-_' | tr -d '=')}"
-CLUSTER="${MIREN_CLUSTER:-luanchat}"
+CLUSTER="${MIREN_CLUSTER:-mycluster}"
REPO="$(cd "$(dirname "$0")/.." && pwd)"
TMP="$(mktemp -d)"
diff --git a/deploy/miren/agent.sh b/deploy/miren/agent.sh
index a51eb44..c00812b 100755
--- a/deploy/miren/agent.sh
+++ b/deploy/miren/agent.sh
@@ -19,8 +19,8 @@
set -euo pipefail
INSTANCE="${1:?usage: deploy/miren/agent.sh <instance>}"
-: "${LCHAT_BASE:?set LCHAT_BASE (e.g. https://test-bots.luan.chat)}"
-CLUSTER="${MIREN_CLUSTER:-luanchat}"
+: "${LCHAT_BASE:?set LCHAT_BASE (e.g. https://test-bots.example.com)}"
+CLUSTER="${MIREN_CLUSTER:-mycluster}"
APP="${INSTANCE}-bot"
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
diff --git a/deploy/miren/home.sh b/deploy/miren/home.sh
index 787a514..749c9b6 100755
--- a/deploy/miren/home.sh
+++ b/deploy/miren/home.sh
@@ -15,7 +15,7 @@ set -euo pipefail
INSTANCE="${1:?usage: deploy/miren/home.sh <instance> <hostname>}"
HOST="${2:?usage: deploy/miren/home.sh <instance> <hostname>}"
-CLUSTER="${MIREN_CLUSTER:-luanchat}"
+CLUSTER="${MIREN_CLUSTER:-mycluster}"
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
TMP="$(mktemp -d)"
diff --git a/deploy/miren/push.sh b/deploy/miren/push.sh
index cc44833..0958a15 100755
--- a/deploy/miren/push.sh
+++ b/deploy/miren/push.sh
@@ -20,7 +20,7 @@ set -euo pipefail
INSTANCE="${1:?usage: deploy/miren/push.sh <instance> <hostname> [admin-token]}"
HOST="${2:?usage: deploy/miren/push.sh <instance> <hostname> [admin-token]}"
TOKEN="${3:-$(openssl rand -base64 32 | tr -d '\n' | tr '+/' '-_' | tr -d '=')}"
-CLUSTER="${MIREN_CLUSTER:-luanchat}"
+CLUSTER="${MIREN_CLUSTER:-mycluster}"
for v in LCHAT_APNS_KEY LCHAT_APNS_KEY_ID LCHAT_APNS_TEAM_ID LCHAT_APNS_TOPIC; do
[ -n "${!v:-}" ] || { echo "error: $v must be set (the .p8 signing config lives only on the relay)" >&2; exit 1; }
diff --git a/docs/connected-services.md b/docs/connected-services.md
index ac3f96e..a09d585 100644
--- a/docs/connected-services.md
+++ b/docs/connected-services.md
@@ -1,7 +1,7 @@
# 🤖 Connecting third-party services (`/register`, and friends)
Status: **prototype shipping.** A connect broker is live at
-`connect.elevenmessenger.com` and `test-bots.luan.chat` is wired to it (branch
+`connect.elevenmessenger.com` and `test-bots.example.com` is wired to it (branch
`connect-oauth`). DNSimple is the worked example; the shape generalises to any
OAuth API. **The plan evolved during implementation** — DNSimple turned out to be
confidential-only, so the public/PKCE sections further down are **superseded** by
diff --git a/docs/instance-router.md b/docs/instance-router.md
index 689ae54..1f2e1d0 100644
--- a/docs/instance-router.md
+++ b/docs/instance-router.md
@@ -354,8 +354,8 @@ independent of every other's:
version (`lchat instances` shows it).
```
- /opt/lchat/live/test.luan.chat ─▶ versions/<canary>/lchat (one instance moved)
- /opt/lchat/live/family.luan.chat ─▶ versions/<stable>/lchat
+ /opt/lchat/live/test.example.com ─▶ versions/<canary>/lchat (one instance moved)
+ /opt/lchat/live/family.example.com ─▶ versions/<stable>/lchat
/opt/lchat/live/paulca… ─▶ versions/<stable>/lchat
```
@@ -365,10 +365,10 @@ independent of every other's:
./deploy/build-linux.sh # build linux/amd64, stamped with the git sha
scp lchat-linux-amd64 root@box:/opt/lchat/incoming
ssh box lchat stage --file /opt/lchat/incoming # register it → prints <ver>
-ssh box lchat deploy test.luan.chat --version <ver> # canary ONE instance
+ssh box lchat deploy test.example.com --version <ver> # canary ONE instance
# …verify test…
ssh box lchat deploy --all --version <ver> # promote the rest
-ssh box lchat rollback test.luan.chat # or step back to its previous version
+ssh box lchat rollback test.example.com # or step back to its previous version
```
`stage` copies a binary into the store (reading its own build id via `lchat
diff --git a/docs/onboarding-and-home-server.md b/docs/onboarding-and-home-server.md
index d080a0c..a7c624e 100644
--- a/docs/onboarding-and-home-server.md
+++ b/docs/onboarding-and-home-server.md
@@ -224,7 +224,7 @@ Two mechanisms, deliberately ranked:
serves `/.well-known/apple-app-site-association`. Invite links on associated
domains open straight into the app — full URL, fragment included — and fall
back to the web welcome page when the app's absent. Limitation: the
- associated-domains entitlement is a static list (`*.luan.chat` + named
+ associated-domains entitlement is a static list (`*.example.com` + named
custom domains); arbitrary custom domains can't join it. (WebAuthn Related
Origin Requests could stretch things later; parked.)
- **Custom scheme `elevenmessenger://` (explicit fallback).** One-line
diff --git a/home/test/crypto.test.mjs b/home/test/crypto.test.mjs
index f8aec1a..31b165c 100644
--- a/home/test/crypto.test.mjs
+++ b/home/test/crypto.test.mjs
@@ -65,7 +65,7 @@ await test("sealBlob → openBlob round-trips via a PRF-derived KEK", async () =
const prf = fakePRF();
const kek = await kekFromPRF(prf);
const dek = await generateDEK();
- const obj = { v: 1, servers: [{ url: "https://family.luan.chat", name: "Family" }] };
+ const obj = { v: 1, servers: [{ url: "https://family.example.com", name: "Family" }] };
const sealed = await sealBlob(obj, dek, { "cred-1": await wrapDEK(dek, kek) });
const { keys } = unpackBlob(sealed);
@@ -86,7 +86,7 @@ await test("envelope wraps the DEK per credential: each of 2 KEKs can open", asy
const prfA = fakePRF();
const prfB = fakePRF();
const dek = await generateDEK();
- const obj = { v: 1, servers: [{ url: "https://test.luan.chat" }] };
+ const obj = { v: 1, servers: [{ url: "https://test.example.com" }] };
const sealed = await sealBlob(obj, dek, {
"cred-a": await wrapDEK(dek, await kekFromPRF(prfA)),
"cred-b": await wrapDEK(dek, await kekFromPRF(prfB)),
diff --git a/home/web/static/enroll.js b/home/web/static/enroll.js
index 0e7d3d5..c5ff81b 100644
--- a/home/web/static/enroll.js
+++ b/home/web/static/enroll.js
@@ -1,4 +1,4 @@
-// The enrolment page. An instance (e.g. family.luan.chat) navigates here (same
+// The enrolment page. An instance (e.g. family.example.com) navigates here (same
// tab) to add itself to the user's passkey-protected backup. Passkeys are bound
// to THIS origin, so the instance can't run the ceremony — it hands us the
// server entry (its URL + the user's session) in our URL FRAGMENT, which never
diff --git a/internal/bot/eleven_test.go b/internal/bot/eleven_test.go
index 4e628df..f4e5213 100644
--- a/internal/bot/eleven_test.go
+++ b/internal/bot/eleven_test.go
@@ -45,12 +45,12 @@ func TestElevenKnowledge(t *testing.T) {
}
}
forbidden := map[string]string{
- `\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b`: "an IP address",
- `(?i)luan\.chat|run\.garden|elevenmessenger\.com|paulca`: "a real hostname/instance",
- `(?i)hetzner|\bmiren\b|dnsimple|1password`: "an operator/tooling name",
- `(?i)\.p8\b|admintok|LCHAT_ADMIN_TOKEN|Bearer\s+\w`: "a key/token",
- `(?i)BEGIN (?:EC |RSA |OPENSSH )?PRIVATE KEY`: "a private key",
- `(?i)ssh\s+root@|root@\d`: "an SSH target",
+ `\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b`: "an IP address",
+ `(?i)run\.garden|elevenmessenger\.com|paulca`: "a real hostname/instance",
+ `(?i)hetzner|\bmiren\b|dnsimple|1password`: "an operator/tooling name",
+ `(?i)\.p8\b|admintok|LCHAT_ADMIN_TOKEN|Bearer\s+\w`: "a key/token",
+ `(?i)BEGIN (?:EC |RSA |OPENSSH )?PRIVATE KEY`: "a private key",
+ `(?i)ssh\s+root@|root@\d`: "an SSH target",
}
for pat, what := range forbidden {
if regexp.MustCompile(pat).MatchString(appKnowledge) {
diff --git a/ios/LChat/LChat.entitlements b/ios/LChat/LChat.entitlements
index bd54bab..9ec861f 100644
--- a/ios/LChat/LChat.entitlements
+++ b/ios/LChat/LChat.entitlements
@@ -14,7 +14,7 @@
opens the app directly (delivered to onOpenURL, fragment included). -->
<key>com.apple.developer.associated-domains</key>
<array>
- <string>applinks:*.luan.chat</string>
+ <string>applinks:*.elevenmessenger.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
diff --git a/ios/LChatCore/Tests/LChatCoreTests/InviteLinkTests.swift b/ios/LChatCore/Tests/LChatCoreTests/InviteLinkTests.swift
index 16e4ea3..cfae44f 100644
--- a/ios/LChatCore/Tests/LChatCoreTests/InviteLinkTests.swift
+++ b/ios/LChatCore/Tests/LChatCoreTests/InviteLinkTests.swift
@@ -6,20 +6,20 @@ import XCTest
// both the new "e1."-prefixed fragments and older unprefixed ones.
final class InviteLinkTests: XCTestCase {
func testParseJoinLink() {
- XCTAssertNotNil(parseJoinLink("https://family.luan.chat/join/join#e1.abc123"),
+ XCTAssertNotNil(parseJoinLink("https://family.example.com/join/join#e1.abc123"),
"e1.-prefixed fragment parses")
- XCTAssertNotNil(parseJoinLink("https://family.luan.chat/join/join#abc123"),
+ XCTAssertNotNil(parseJoinLink("https://family.example.com/join/join#abc123"),
"unprefixed fragment parses (older links)")
- XCTAssertNotNil(parseJoinLink(" https://h.luan.chat/join/x#e1.T \n"),
+ XCTAssertNotNil(parseJoinLink(" https://h.example.com/join/x#e1.T \n"),
"whitespace is trimmed")
XCTAssertEqual(parseJoinLink("http://192.168.1.5:8080/join/x#e1.T")?.origin,
"http://192.168.1.5:8080",
"origin keeps scheme/host/port, drops path+fragment")
- XCTAssertNil(parseJoinLink("https://h.luan.chat/join/x#k=legacykey"),
+ XCTAssertNil(parseJoinLink("https://h.example.com/join/x#k=legacykey"),
"legacy #k= links are rejected")
- XCTAssertNil(parseJoinLink("https://h.luan.chat/join/x"),
+ XCTAssertNil(parseJoinLink("https://h.example.com/join/x"),
"missing fragment is rejected")
- XCTAssertNil(parseJoinLink("https://h.luan.chat/other/x#e1.T"),
+ XCTAssertNil(parseJoinLink("https://h.example.com/other/x#e1.T"),
"non-/join/ path is rejected")
XCTAssertNil(parseJoinLink("elevenmessenger://add?url=x"),
"custom scheme is not itself a join link")
@@ -27,9 +27,9 @@ final class InviteLinkTests: XCTestCase {
func testInviteLinkFromOpenedURL() {
// The welcome page's custom-scheme form.
- let encoded = "https%3A%2F%2Ffamily.luan.chat%2Fjoin%2Fjoin%23e1.abc123"
+ let encoded = "https%3A%2F%2Ffamily.example.com%2Fjoin%2Fjoin%23e1.abc123"
XCTAssertEqual(opened("elevenmessenger://add?url=\(encoded)"),
- "https://family.luan.chat/join/join#e1.abc123",
+ "https://family.example.com/join/join#e1.abc123",
"elevenmessenger://add unwraps + percent-decodes the url param")
XCTAssertNil(opened("elevenmessenger://add"),
"custom scheme without a url param is rejected")
@@ -38,8 +38,8 @@ final class InviteLinkTests: XCTestCase {
// …and the universal-link form: the https invite passes straight through,
// fragment intact.
- XCTAssertEqual(opened("https://family.luan.chat/join/join#e1.abc123"),
- "https://family.luan.chat/join/join#e1.abc123",
+ XCTAssertEqual(opened("https://family.example.com/join/join#e1.abc123"),
+ "https://family.example.com/join/join#e1.abc123",
"https universal link passes through with its fragment")
XCTAssertNil(opened("ftp://h/join/x#e1.T"), "other schemes are rejected")
}
diff --git a/push.go b/push.go
index e18b6be..5effa73 100644
--- a/push.go
+++ b/push.go
@@ -281,7 +281,7 @@ func (s *Server) sendOne(payload []byte, kind string, sub PushSub) bool {
HTTPClient: pushClient,
// Pass the bare e-mail: webpush-go prepends "mailto:" itself. Passing
// "mailto:…" yields "mailto:mailto:…", which Apple rejects (BadJwtToken).
- Subscriber: "admin@luan.chat",
+ Subscriber: "admin@elevenmessenger.com",
VAPIDPublicKey: s.vapidPub,
VAPIDPrivateKey: s.vapidPriv,
TTL: 60,
diff --git a/push/db.go b/push/db.go
index 558c6b9..3834d4c 100644
--- a/push/db.go
+++ b/push/db.go
@@ -44,7 +44,7 @@ func (db *DB) migrate() error {
_, err := db.sql.Exec(`
CREATE TABLE IF NOT EXISTS relay_tokens (
id TEXT PRIMARY KEY, -- random public handle, used to revoke
- label TEXT NOT NULL, -- e.g. "family.luan.chat" — for logs/listing, not auth
+ label TEXT NOT NULL, -- e.g. "family.example.com" — for logs/listing, not auth
token_hash TEXT NOT NULL UNIQUE, -- sha256(bearer token); the secret itself is never stored
created_at INTEGER NOT NULL,
last_used_at INTEGER
diff --git a/push/push_test.go b/push/push_test.go
index b04d4ce..12977ce 100644
--- a/push/push_test.go
+++ b/push/push_test.go
@@ -114,7 +114,7 @@ func TestPushAuth(t *testing.T) {
if got := push(t, base, "bogus-token", liveDevice, ""); got != http.StatusUnauthorized {
t.Errorf("bad token: want 401, got %d", got)
}
- _, tok := mint(t, base, "family.luan.chat")
+ _, tok := mint(t, base, "family.example.com")
if got := push(t, base, tok, liveDevice, ""); got != http.StatusOK {
t.Errorf("minted token: want 200, got %d", got)
}
@@ -122,7 +122,7 @@ func TestPushAuth(t *testing.T) {
func TestPushRevoke(t *testing.T) {
base := newTestRelay(t).URL
- id, tok := mint(t, base, "test.luan.chat")
+ id, tok := mint(t, base, "test.example.com")
if got := push(t, base, tok, liveDevice, ""); got != http.StatusOK {
t.Fatalf("pre-revoke: want 200, got %d", got)
}
@@ -138,7 +138,7 @@ func TestPushRevoke(t *testing.T) {
func TestPushGonePassthrough(t *testing.T) {
base := newTestRelay(t).URL
- _, tok := mint(t, base, "test.luan.chat")
+ _, tok := mint(t, base, "test.example.com")
// Apple says the token is dead → relay must return a bare 410 so the
// instance prunes it.
if got := push(t, base, tok, deadDevice, ""); got != http.StatusGone {
@@ -148,7 +148,7 @@ func TestPushGonePassthrough(t *testing.T) {
func TestPushBadDeviceToken(t *testing.T) {
base := newTestRelay(t).URL
- _, tok := mint(t, base, "test.luan.chat")
+ _, tok := mint(t, base, "test.example.com")
if got := push(t, base, tok, "zznothex", ""); got != http.StatusBadRequest {
t.Errorf("non-hex device token: want 400, got %d", got)
}
@@ -156,7 +156,7 @@ func TestPushBadDeviceToken(t *testing.T) {
func TestPushOversized(t *testing.T) {
base := newTestRelay(t).URL
- _, tok := mint(t, base, "test.luan.chat")
+ _, tok := mint(t, base, "test.example.com")
// A blob well over maxPushBytes must 413, never 410 (a false prune).
big := `,"pad":"` + strings.Repeat("a", 9000) + `"`
if got := push(t, base, tok, liveDevice, big); got != http.StatusRequestEntityTooLarge {
diff --git a/test/knowledge.test.mjs b/test/knowledge.test.mjs
index 2d5f5f9..c3a5aea 100644
--- a/test/knowledge.test.mjs
+++ b/test/knowledge.test.mjs
@@ -29,7 +29,7 @@ test("the knowledge base is substantial (not accidentally emptied)", () => {
test("the knowledge base leaks NO operational secrets", () => {
const forbidden = [
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/, "an IP address"],
- [/luan\.chat|run\.garden|elevenmessenger\.com|paulca/i, "a real hostname/instance"],
+ [/run\.garden|elevenmessenger\.com|paulca/i, "a real hostname/instance"],
[/hetzner|\bmiren\b|dnsimple|1password|\bop\b\s+(?:read|item)/i, "an operator/tooling name"],
[/\.p8\b|J5NDF5|ZKYQ9|9X6NKTF|admintok|LCHAT_ADMIN_TOKEN|Bearer\s+\w/i, "a key/token"],
[/BEGIN (?:EC |RSA |OPENSSH )?PRIVATE KEY/i, "a private key"],
diff --git a/unfurl.go b/unfurl.go
index 56f39a3..592800e 100644
--- a/unfurl.go
+++ b/unfurl.go
@@ -108,7 +108,7 @@ func fetchOG(rawURL string) (LinkPreview, error) {
if err != nil {
return pv, err
}
- req.Header.Set("User-Agent", "lchat-linkpreview/1.0 (+https://luan.chat)")
+ req.Header.Set("User-Agent", "lchat-linkpreview/1.0 (+https://elevenmessenger.com)")
req.Header.Set("Accept", "text/html,application/xhtml+xml")
resp, err := ogClient.Do(req)
diff --git a/versions_test.go b/versions_test.go
index dede662..a78242a 100644
--- a/versions_test.go
+++ b/versions_test.go
@@ -62,10 +62,10 @@ func TestStageAndPointLive(t *testing.T) {
// Pointing an instance's live link resolves to the staged binary and reports
// the right version.
- if err := pointLive("test.luan.chat", "abc123"); err != nil {
+ if err := pointLive("test.example.com", "abc123"); err != nil {
t.Fatalf("pointLive: %v", err)
}
- got, err := liveVersion("test.luan.chat")
+ got, err := liveVersion("test.example.com")
if err != nil {
t.Fatalf("liveVersion: %v", err)
}
@@ -73,7 +73,7 @@ func TestStageAndPointLive(t *testing.T) {
t.Errorf("liveVersion = %q, want abc123", got)
}
// The symlink actually points at the version binary.
- resolved, err := filepath.EvalSymlinks(liveLink("test.luan.chat"))
+ resolved, err := filepath.EvalSymlinks(liveLink("test.example.com"))
if err != nil {
t.Fatalf("EvalSymlinks: %v", err)
}
@@ -84,7 +84,7 @@ func TestStageAndPointLive(t *testing.T) {
func TestPointLiveUnstagedFails(t *testing.T) {
withTempOpt(t)
- if err := pointLive("test.luan.chat", "nope"); err == nil {
+ if err := pointLive("test.example.com", "nope"); err == nil {
t.Error("pointLive to an unstaged version should fail, but succeeded")
}
}