From fad41d0fbeaeb3577e27b44a49a774e588b43196 Mon Sep 17 00:00:00 2001 From: retoor Date: Wed, 14 Oct 2026 08:37:37 +0200 Subject: [PATCH] Document that pravda.education is no longer DevPlace and what this host runs Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 29 +++++++++++++---------------- pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1f4e8633..99c1d1a2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -385,11 +385,11 @@ On every commit the hook increments the patch component (`1.0.0` -> `1.0.1`) and This procedure exists because a single "the editor is down" report turned out to be **three unrelated faults stacked on each other** (a stale URL, a firewalled network leg, and a corrupt database), and the investigation wasted hours by guessing before measuring. Work the layers outward from the browser; each step is cheap and each one eliminates a whole class of cause. **Never skip to a hypothesis, and never repair anything before the layer above it is proven healthy.** -**Layer 0 - is the request even arriving here?** Fetch the hostname over the public internet exactly as it resolves (`curl -sS -o /dev/null -w '%{http_code} %{remote_ip}' https://host/`). Compare the answering IP against this machine's own addresses (`ip -6 addr`, `curl https://api.ipify.org`). Two hostnames serve this platform by different routes - see the topology section above. **Never use `curl --resolve` to force a hostname onto an IP it does not resolve to**; that fabricates a path that no real traffic takes and produces confident, wrong conclusions. +**Layer 0 - is the request even arriving here?** Fetch the hostname over the public internet exactly as it resolves (`curl -sS -o /dev/null -w '%{http_code} %{remote_ip}' https://host/`). Compare the answering IP against this machine's own addresses (`ip -6 addr`, `curl https://api.ipify.org`). DevPlace is served publicly on `devplace.net` only - see the topology section below. **Never use `curl --resolve` to force a hostname onto an IP it does not resolve to**; that fabricates a path that no real traffic takes and produces confident, wrong conclusions. -**Layer 1 - which edge answered?** The error body identifies it. `application/problem+json` with `No site configured for host` is molohttp. A DevPlace HTML error page is the application. An nginx error page is the nginx container. A browser `ERR_*` with no body means nothing well-formed was returned at all. +**Layer 1 - which edge answered?** The error body identifies it. A DevPlace HTML error page is the application. An nginx error page is the nginx container. A browser `ERR_*` with no body means nothing well-formed was returned at all. -**Layer 2 - same failure on both hostnames?** Run the identical authenticated request against `pravda.education` and `devplace.net`. Failing on **both** means the application or the database; failing on **one** means that host's edge. This single comparison is the highest-value measurement available and costs one command. +**Layer 2 - same failure on the public hostname and on the host itself?** Run the identical authenticated request against `https://devplace.net` and `http://127.0.0.1:10500`. Failing on **both** means the application or the database; failing only on `devplace.net` means the front host's proxy or the SSH tunnel. This single comparison is the highest-value measurement available and costs one command. **Layer 3 - the application log, before any theory.** `docker logs --since 5m devplace-app-1`. Count error classes rather than reading prose (`grep -c malformed`). A recurring service-loop error is a systemic fault even when it looks unrelated to the symptom. @@ -410,23 +410,20 @@ This procedure exists because a single "the editor is down" report turned out to ## Production hostnames and the devplace.net SSH tunnel (verified topology, do not re-derive) -**The platform answers on two public hostnames, and they reach the same application by two completely different paths.** This has already cost one debugging session; the failure mode is that a `curl --resolve devplace.net:443:` "test" reports `No site configured for host: devplace.net` and looks like a total outage, when in fact devplace.net never touches the production edge at all. +**DevPlace answers publicly on `devplace.net` only.** `pravda.education` used to be a second hostname for it; it is now a different site. Measured 2026-10-14: it still resolves to this machine (`95.216.15.238`) but answers with `Server: grr` and a different application (`/robots.txt` and `/openapi.json` are 404, `/docs` is 200 where DevPlace returns 302), and the owner confirmed it is no longer DevPlace. Never use it to verify a DevPlace change, and never read a 404 there as a DevPlace fault. -| | `pravda.education` | `devplace.net` | -|---|---|---| -| DNS | `95.216.15.238`, `2a01:4f9:2a:100e::2` | `88.198.21.243`, `2a01:4f8:222:2c45::2` | -| Machine | the production host itself | a separate front host (Hetzner, PTR `static.88-198-21-243.clients.your-server.de`) | -| Path in | molohttp on `:443` -> `127.0.0.1:10500` | its own proxy -> **SSH tunnel** -> `127.0.0.1:10500` on production | -| Reaches molohttp | yes | **no, never** | +| | `devplace.net` | +|---|---| +| DNS | `88.198.21.243`, `2a01:4f8:222:2c45::2` | +| Machine | a separate front host (Hetzner, PTR `static.88-198-21-243.clients.your-server.de`) | +| Path in | its own proxy -> **SSH tunnel** -> `127.0.0.1:10500` on this host | -**`devplace.net` is a front host that forwards over SSH.** It holds a persistent SSH session into the production host (visible there as an established inbound connection from `88.198.21.243` to port 22) and forwards through it to `127.0.0.1:10500`, which is the `docker-proxy` for the `devplace-nginx` container. The listening socket lives on the **front** host (an `ssh -L` style local forward), so the production host shows **no** sshd-owned listener - that absence is expected and is not evidence against the tunnel. +**`devplace.net` is a front host that forwards over SSH.** It holds a persistent SSH session into this host (visible here as an established inbound connection from `88.198.21.243` to port 22) and forwards through it to `127.0.0.1:10500`. In the Docker Compose layout that port is the `docker-proxy` for the `devplace-nginx` container; on a host running `make dev` it is uvicorn itself. The listening socket lives on the **front** host (an `ssh -L` style local forward), so this host shows **no** sshd-owned listener - that absence is expected and is not evidence against the tunnel. -Two consequences that must not be forgotten: +- **The front host's proxy is the edge to suspect when only `devplace.net` fails** while `127.0.0.1:10500` is healthy (WebSocket `Upgrade` headers are the usual culprit, exactly as for the production nginx locations below). +- **The machine the repository is checked out on may run `make dev`, not the Compose stack** (observed 2026-10-14: `uvicorn --reload` on port 10500, no `docker` binary, `podman` present). `make docker-reload` fails there with `docker: No such file or directory`, and the `--reload` server already serves committed code. Check `ss -ltnp | grep 10500` and `which docker` before assuming a deploy path. -- **molohttp has no `devplace.net` site, and that is correct.** Its site list is `mail`/`smtp`/`imap.molodetz.nl`, `pravda.education` and `*.tunnel.pravda.education`. devplace.net traffic enters below molohttp, straight into `127.0.0.1:10500`, so it needs no site. **Never "fix" this by adding a devplace.net site to molohttp** - devplace.net does not resolve to the production host, so such a site could never match, and its absence is not a bug. -- **Both hostnames land on the same nginx and the same app**, so a request that fails on both is failing in the application, not in either edge. That comparison is the fastest triage available here: run the same authenticated request against both hostnames. Same failure on both means look at the app or the database; a failure only on devplace.net means look at the front host's proxy (WebSocket `Upgrade` headers are the usual culprit, exactly as for the production nginx locations below). - -**Testing rule.** Never point a hostname at an IP it does not resolve to in order to "test" it. Fetch each hostname over the public internet as it really resolves (`curl https://devplace.net/...` and `curl https://pravda.education/...`), because forcing devplace.net onto the production IP tests molohttp with a `Host` it deliberately does not serve and proves nothing about the real path. +**Testing rule.** Never point a hostname at an IP it does not resolve to in order to "test" it. Fetch `https://devplace.net/...` over the public internet as it really resolves, and compare it with `http://127.0.0.1:10500/...` on the host itself. ## Production deployment diff --git a/pyproject.toml b/pyproject.toml index cab640e8..7ac1be92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "devplacepy" -version = "1.0.19" +version = "1.0.20" description = "DevPlace - The Developer Social Network" requires-python = ">=3.12" dependencies = [