Skip to content
Merged
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
10 changes: 5 additions & 5 deletions .github/workflows/branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -363,22 +363,22 @@ jobs:
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: domains/r3dr/apps/iili_web/package-lock.json
cache-dependency-path: domains/iili/apps/iili_web/package-lock.json

- name: Install dependencies
working-directory: domains/r3dr/apps/iili_web
working-directory: domains/iili/apps/iili_web
run: npm ci

- name: Typecheck
working-directory: domains/r3dr/apps/iili_web
working-directory: domains/iili/apps/iili_web
run: npm run typecheck

- name: Run tests
working-directory: domains/r3dr/apps/iili_web
working-directory: domains/iili/apps/iili_web
run: npm test

- name: Build
working-directory: domains/r3dr/apps/iili_web
working-directory: domains/iili/apps/iili_web
run: npm run build

# iOS targets can't build on Ubuntu; run Swift tests on macOS when relevant files change.
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ out
vcpkg_installed
conan
rust-project.json
go/r3dr/deploy/deploy.sh
models
checkpoints

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The repository is organized into **domains**, each containing its own libraries,
- [**🎨 Graphics**](domains/graphics/README.md): Ray tracers, image processing, and rendering tools.
- [**🎮 Games**](domains/games/README.md): Game engines, chess tools, and multiplayer backends.
- [**🌐 Platform**](domains/platform/README.md): Core infrastructure, gRPC/HTTP wrappers, and utilities.
- [**🔗 r3dr**](domains/r3dr/README.md): URL shortening ecosystem.
- [**🔗 iili**](domains/iili/README.md): URL shortening ecosystem.
- [**💬 Chat**](domains/chat/README.md): Real-time communication services.
- [**🤖 AI**](domains/ai/README.md): Neural network and machine learning experiments.
- [**🏆 Leet**](domains/leet/README.md): Algorithmic solutions and C utility libraries.
Expand Down
2 changes: 0 additions & 2 deletions bazel/go.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ use_repo(
"com_github_golang_jwt_jwt_v5",
"com_github_google_uuid",
"com_github_gorilla_websocket",
"com_github_hashicorp_golang_lru_v2",
"com_github_lib_pq",
"com_github_prometheus_client_golang",
"com_github_prometheus_common",
"com_github_stretchr_testify",
Expand Down
4 changes: 2 additions & 2 deletions bazel/smithy.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ bazel_dep(name = "smithy_cpp", version = "0.0.0")
# Evaluation: https://github.com/muchq/MoonBase/issues/1168
#
# Pinned past muchq/smithy-cpp#187 (46bcd4d): modeled 3xx redirects — a
# typed success carrying Location instead of a GenericError. r3dr_v2's
# GET /r3dr/v2/r/{slug} needs it (#1359).
# typed success carrying Location instead of a GenericError. iili's
# GET /iili/v1/r/{slug} needs it (#1359).
#
# Carries muchq/smithy-cpp#173 (4208f791): terminal transitions fire the
# parked send before the parked receive so End()/Close cannot deadlock when a
Expand Down
29 changes: 13 additions & 16 deletions deploy/consolidated/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ api.muchq.com {
path /v2/analyze
}

@post_r3dr_shorten {
@post_iili_shorten {
method POST
path /r3dr/v2/shorten
path /iili/v1/shorten
}

@get_r3dr_redirect {
@get_iili_redirect {
method GET
path /r3dr/v2/r/*
path /iili/v1/r/*
}

@ws_thoughts {
Expand All @@ -90,7 +90,7 @@ api.muchq.com {
method OPTIONS
}

# Browser origins that call this API: muchq.com pages (incl. /r3dr)
# Browser origins that call this API: muchq.com pages (incl. /iili)
# and the iili.uk SPA (plus its vite dev — every API here is public
# and credential-less, so a localhost origin grants nothing curl
# doesn't have). ACAO takes one value, so it's echoed per origin;
Expand Down Expand Up @@ -155,9 +155,9 @@ api.muchq.com {
# client sees and the route the model declares are one string.
reverse_proxy @post_v2_analyze one_d4_v2:8090

# Same pattern (#1359): r3dr_v2 serves /r3dr/v2/* unrewritten.
reverse_proxy @post_r3dr_shorten r3dr_v2:8091
reverse_proxy @get_r3dr_redirect r3dr_v2:8091
# Same pattern (#1359): iili serves /iili/v1/* unrewritten.
reverse_proxy @post_iili_shorten iili:8091
reverse_proxy @get_iili_redirect iili:8091

log {
output file /var/log/caddy/access.log {
Expand All @@ -168,20 +168,17 @@ api.muchq.com {
}
}

# Short-link redirects for iili.uk (#1359). SPA stays on Cloudflare at
# iili.uk; this host (A-record to the consolidated box) only answers
# /r/{slug}. Public path is rewritten onto r3dr_v2's modeled
# /r3dr/v2/r/{slug} — the deliberate exception to "gateway == model path"
# on api.muchq.com. HEAD is forwarded with GET for unfurlers; end-to-end
# 302 on HEAD waits on #1433 (smithy registers GET only today → 405).
# Short-link redirects for iili.uk (#1359); the SPA is on Cloudflare at
# iili.uk. The public /r/{slug} is rewritten onto iili's modeled
# /iili/v1/r/{slug} — the deliberate exception to "gateway == model path".
i.iili.uk {
@getredir {
method GET HEAD
path /r/*
}
handle @getredir {
rewrite * /r3dr/v2{path}
reverse_proxy r3dr_v2:8091
rewrite * /iili/v1{path}
reverse_proxy iili:8091
}
respond 404

Expand Down
18 changes: 9 additions & 9 deletions deploy/consolidated/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ individually targetable.
- [`microgpt-serve`](../../domains/ai/apis/microgpt_serve) (port 8087)
- [`one_d4`](../../domains/games/apis/one_d4) (port 8088)
- [`one_d4_v2`](../../domains/games/apis/one_d4_v2) (port 8090)
- [`r3dr_v2`](../../domains/r3dr/apis/r3dr_v2) (port 8091, `/r3dr/v2/*`)
- [`iili`](../../domains/iili/apis/iili) (port 8091, `/iili/v1/*`)

- **i.iili.uk** - Short-link redirects (#1359)
- Caddy rewrites `GET|HEAD /r/{slug}` → `r3dr_v2` `/r3dr/v2/r/{slug}`
- Caddy rewrites `GET|HEAD /r/{slug}` → `iili` `/iili/v1/r/{slug}`
- SPA lives on Cloudflare at `iili.uk` (not this host)
- `HEAD` end-to-end 302 tracked in #1433

- **Observability Stack**
- Prometheus (port 9090)
Expand Down Expand Up @@ -200,11 +199,12 @@ nothing from the host filesystem.

### Database URLs

golf_hub, one_d4 and r3dr_v2 take their database URLs from `compose.yaml`, interpolating a
password from the host's `~/.env`: `GOLF_HUB_DB_URL` and `R3DR_V2_DB_URL` (libpq form, read from
golf_hub, one_d4 and iili take their database URLs from `compose.yaml`, interpolating a
password from the host's `~/.env`: `GOLF_HUB_DB_URL` and `IILI_DB_URL` (libpq form, read from
C++) and `INDEXER_DB_URL` (JDBC form, read by pgjdbc). All point at the `shared_postgres` service.
**`R3DR_V2_DB_PASSWORD` is a new secret** in `~/.env` (#1359); `r3dr_v2_db_init` provisions the
`r3dr_v2` role and database with it on every deploy, idempotently. Keep it URL-safe (no
**`R3DR_V2_DB_PASSWORD`** in `~/.env` (#1359) is the one place the old name survives: `iili_db_init`
provisions the `r3dr_v2` role and database with it on every deploy, idempotently, because renaming
a role and database holding live rows is an operation, not a rename. Keep it URL-safe (no
`@ / ? # %` or quotes): it rides in a libpq URL and a single-quoted SQL literal. Compose
refuses to start the service if it's unset.

Expand Down Expand Up @@ -237,8 +237,8 @@ All services run on the `muchq_network` Docker bridge network.
## The shared database

`shared_postgres` is the one Postgres instance on the host. `one_d4`,
`golf_hub` and `r3dr_v2` each keep a database on it; `golf_hub_db_init` and
`r3dr_v2_db_init` provision their roles and databases on every deploy
`golf_hub` and `iili` each keep a database on it; `golf_hub_db_init` and
`iili_db_init` provision their roles and databases on every deploy
(idempotently — the `docker-entrypoint-initdb.d` hook only fires on a
fresh volume).

Expand Down
22 changes: 11 additions & 11 deletions deploy/consolidated/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ services:
- one_d4_v2
- portrait
- prom_proxy
- r3dr_v2
- iili
- mcpserver
networks:
# Static address so services can name Caddy — and only Caddy — as
Expand Down Expand Up @@ -244,24 +244,24 @@ services:
retries: 3
start_period: 10s

r3dr_v2:
image: ghcr.io/muchq/r3dr_v2:${R3DR_V2_SHA:-${DEPLOY_SHA:-latest}}
iili:
image: ghcr.io/muchq/iili:${IILI_SHA:-${DEPLOY_SHA:-latest}}
labels:
com.muchq.description: "URL shortener v2 API (C++)"
restart: always
ports:
- "8091:8091"
environment:
PORT: "8091"
R3DR_V2_DB_URL: postgresql://r3dr_v2:${R3DR_V2_DB_PASSWORD:?unset}@shared_postgres:5432/r3dr_v2
IILI_DB_URL: postgresql://r3dr_v2:${R3DR_V2_DB_PASSWORD:?unset}@shared_postgres:5432/r3dr_v2
TRUSTED_PROXY_CIDRS: *caddy_ip
OTEL_EXPORTER_OTLP_ENDPOINT: http://otelcol:4318
OTEL_SERVICE_NAME: r3dr_v2
OTEL_RESOURCE_ATTRIBUTES: service.name=r3dr_v2,service.version=${R3DR_V2_SHA:-${DEPLOY_SHA:-latest}}
OTEL_SERVICE_NAME: iili
OTEL_RESOURCE_ATTRIBUTES: service.name=iili,service.version=${IILI_SHA:-${DEPLOY_SHA:-latest}}
networks:
- app_network
depends_on:
r3dr_v2_db_init:
iili_db_init:
condition: service_completed_successfully
deploy:
resources:
Expand Down Expand Up @@ -373,10 +373,10 @@ services:
shared_postgres:
condition: service_healthy

# Idempotent one-shot provisioning of r3dr_v2's database and role
# (#1359), mirroring golf_hub_db_init. Deliberately not named r3dr: the
# Go service's storage must stay un-aliasable from this file.
r3dr_v2_db_init:
# The role, database and password keep the r3dr_v2 names the live rows were
# provisioned under: renaming those is a live-database operation, not a
# rename of this file.
iili_db_init:
image: postgres:18
restart: "no"
environment:
Expand Down
60 changes: 21 additions & 39 deletions deploy/consolidated/deploy_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ var servicesWithSteadyProbes = []string{
"one_d4",
"one_d4_v2",
"portrait",
"iili",
"posterize",
"r3dr_v2",
}

func TestEveryServiceOnTheStandardRailsIsProbed(t *testing.T) {
Expand Down Expand Up @@ -673,7 +673,7 @@ func TestTheMcpCorsAllowListCarriesTheProtocolHeaders(t *testing.T) {
}
}

// api.muchq.com is called from browsers on muchq.com (incl. /r3dr) and on
// api.muchq.com is called from browsers on muchq.com (incl. /iili) and on
// iili.uk (the iili_web worker SPA; the localhost origin is vite dev).
// ACAO takes a single value, so the Caddyfile echoes it per allowed origin
// via named matchers — on ordinary responses and on the preflight handler
Expand Down Expand Up @@ -1022,9 +1022,9 @@ var publicRoutes = []struct {
// one_d4_v2 (#1389 phase 6): mcpserver reaches it directly over the
// Compose network, but the public route is served through Caddy.
{"@post_v2_analyze", []string{"method POST", "path /v2/analyze"}, "one_d4_v2:8090"},
// r3dr_v2 (#1359): the redirect matcher is the product.
{"@post_r3dr_shorten", []string{"method POST", "path /r3dr/v2/shorten"}, "r3dr_v2:8091"},
{"@get_r3dr_redirect", []string{"method GET", "path /r3dr/v2/r/*"}, "r3dr_v2:8091"},
// iili (#1359): the redirect matcher is the product.
{"@post_iili_shorten", []string{"method POST", "path /iili/v1/shorten"}, "iili:8091"},
{"@get_iili_redirect", []string{"method GET", "path /iili/v1/r/*"}, "iili:8091"},
}

func TestPublicRoutesAreDeliberatelyExact(t *testing.T) {
Expand Down Expand Up @@ -1077,10 +1077,9 @@ func TestPublicRoutesAreDeliberatelyExact(t *testing.T) {

// i.iili.uk is the short-link redirect host (#1359): A-record to the
// consolidated box, SPA stays on Cloudflare at iili.uk. Public path is
// /r/{slug}; r3dr_v2's modeled path is /r3dr/v2/r/{slug}, so this site
// rewrites — the deliberate exception to "gateway path == model path"
// on api.muchq.com. HEAD is forwarded with GET (#1433 pins end-to-end 302).
func TestIiliRedirectHostRewritesSlugPathsToR3drV2(t *testing.T) {
// /r/{slug}; iili's modeled path is /iili/v1/r/{slug}, so this site
// rewrites — the deliberate exception to "gateway path == model path".
func TestIiliRedirectHostRewritesSlugPathsToIili(t *testing.T) {
site := caddySiteBlock(t, "Caddyfile", "i.iili.uk")

matcher := ""
Expand All @@ -1092,10 +1091,10 @@ func TestIiliRedirectHostRewritesSlugPathsToR3drV2(t *testing.T) {
rewrites := false
proxies := false
for _, inner := range body {
if strings.HasPrefix(inner, "rewrite ") && strings.Contains(inner, "/r3dr/v2") {
if strings.HasPrefix(inner, "rewrite ") && strings.Contains(inner, "/iili/v1") {
rewrites = true
}
if strings.HasPrefix(inner, "reverse_proxy r3dr_v2:8091") {
if strings.HasPrefix(inner, "reverse_proxy iili:8091") {
proxies = true
}
}
Expand All @@ -1105,7 +1104,7 @@ func TestIiliRedirectHostRewritesSlugPathsToR3drV2(t *testing.T) {
}
}
if matcher == "" {
t.Fatalf("no `handle @… { rewrite …/r3dr/v2…; reverse_proxy r3dr_v2:8091 }` in the "+
t.Fatalf("no `handle @… { rewrite …/iili/v1…; reverse_proxy iili:8091 }` in the "+
"i.iili.uk block; short links would 404 or hit the wrong upstream. Block was:\n%s",
strings.Join(site, "\n"))
}
Expand Down Expand Up @@ -1138,35 +1137,18 @@ func TestIiliRedirectHostRewritesSlugPathsToR3drV2(t *testing.T) {
}
}

// Contains, not equality: the host also returns on a multi-host or
// scheme-prefixed address line.
func TestR3drNetCaddySitesAreGone(t *testing.T) {
for _, host := range []string{"r3dr.net", "www.r3dr.net"} {
for _, line := range directiveLines(t, "Caddyfile") {
if strings.Contains(line, host) {
t.Errorf("Caddyfile still names %q (%q); short links live on "+
"i.iili.uk now and the SPA on Cloudflare at iili.uk.", host, line)
}
}
}
}

// r3dr_v2 shares the prefix and would trip most of these tokens, so it is
// rewritten away before each line is checked.
func TestNoDeployConfigNamesTheGoR3drStack(t *testing.T) {
if _, ok := composeServiceLines(t, "compose.yaml")["r3dr"]; ok {
t.Error("compose.yaml declares an `r3dr` service; r3dr_v2 is the shortener")
}
forbidden := []string{"ghcr.io/muchq/r3dr:", "/var/www/r3dr", "r3dr-assets", "r3dr_web", "/etc/r3dr"}
files := []string{"compose.yaml", "Caddyfile", "Caddyfile.local", "deploy.sh", "local_deploy.sh", "initialize_host.sh"}
// Only the r3dr_v2 database role, database and password keep the old name
// (see compose.yaml); any other r3dr in the deploy surface is the retired Go
// stack or a missed rename.
func TestNoDeployConfigNamesR3dr(t *testing.T) {
dbIdentifier := regexp.MustCompile(`(?i)r3dr_v2`)
files := []string{"compose.yaml", "Caddyfile", "Caddyfile.local", "deploy.sh",
"local_deploy.sh", "initialize_host.sh"}
for _, name := range files {
for i, line := range strings.Split(readConfig(t, name), "\n") {
sansV2 := strings.ReplaceAll(line, "r3dr_v2", "V2")
for _, token := range forbidden {
if strings.Contains(sansV2, token) {
t.Errorf("%s:%d names %q (%q); r3dr_v2 is the shortener",
name, i+1, token, strings.TrimSpace(line))
}
if strings.Contains(strings.ToLower(dbIdentifier.ReplaceAllString(line, "")), "r3dr") {
t.Errorf("%s:%d names r3dr (%q); the shortener is iili", name, i+1,
strings.TrimSpace(line))
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions domains/iili/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# iili Domain

URL shortening services and related applications.

## APIs

- [**iili API**](apis/iili): The C++ shortener on smithy-cpp (#1359),
serving `/iili/v1/*` behind `api.muchq.com`.

## Apps

- [**iili web**](apps/iili_web): The standalone frontend (#1359) — a
Cloudflare Worker SPA at `iili.uk`. Short links mint as
`i.iili.uk/r/{slug}` (Caddy on the consolidated host → iili). A
second frontend lives at muchq.com/iili (the muchq.github.io repo).
Loading
Loading