ci/gitea-settings.sh told the reader to "create a Gitea repo secret
GITEA_SETTINGS_TOKEN". That instruction is IMPOSSIBLE to follow: Gitea reserves the
GITEA_ prefix for secret names and rejects the PUT with HTTP 400 "invalid secret name".
Same defect as the one fixed in PR #15 and in the README in PR #19 -- this was the third
file carrying it, found by an agent reading the file rather than trusting my description
of it. A comment that instructs an impossible action is not cosmetic: this exact wrong
name is why the drift detector sat inert while a token existed and no secret did.
Now names SETTINGS_API_TOKEN, and states explicitly that GITEA_SETTINGS_TOKEN remains
honoured ONLY as a local-run env var -- it can never be the CI wiring, because the secret
that would feed it cannot exist.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-29 19:30:35 -05:00
3 changed files with 18 additions and 927 deletions
**26 packets of world traffic were allowed in on 19132/UDP.** The policy did not drop them; the
application had no socket to receive them. `reserved:remote-node 19132/UDP` is also an Allow (0
bytes — the packets landed on the node that both announces the VIP and runs the pod, so no second
hop), which re-confirms the 2026-09-14 finding that `cluster` subsumes `remote-node` here.
**Verdict: this is not a platform defect and not a manifest defect. It is the Bedrock Dedicated
Server's default `transport=nethernet`, plus a README that told the operator creating a server on
19132 was sufficient.** The repo-side defect is documentation, and that is what this branch changes.
### The platform path, measured sound (so the operator can rule it out for good)
| Claim | Measured |
|---|---|
| `crafty-l4-udp` really is `Cluster` | `kubectl get svc crafty-l4-udp -o yaml` → `spec.externalTrafficPolicy: Cluster` (the 2026-09-14 fix IS live, not just in git) |
| Datapath frontend present | `cilium-dbg service list` on **both** k3s-prod-1 and k3s-prod-3: `192.168.2.22:19132/UDP LoadBalancer 1 => 10.0.0.130:19132/UDP (active)` — i.e. any node can serve it, which is exactly what `Cluster` is for |
should now show a wildcard bind, and the `raknet_ping` timeout warnings in
`/crafty/logs/commander.log` should stop. **Both checks, not one.**
2. **Then tell the players how to connect.** Windows/mobile client → **Add Server** →
`crafty-mc.home.keithjackson.dev`, port **19132**. It will *not* appear in the LAN/Friends tab, and
a Switch/Xbox/PlayStation client cannot reach it at all. If the reported users are on consoles,
step 1 will not help them and the answer is "unsupported without a DNS redirect".
3. **Fix `auto_start` on the new server.** DB row `K3s-Bedrock` has `auto_start=0` **and**
`crash_detection=0` — the create-API trap already documented in this repo, live again.
`PATCH /api/v2/servers/c5837301-…` setting both true, **and read them back**. Until then the
Bedrock server does not survive a pod restart — and merging
`fix/crafty-oauth2-proxy-signin` restarts the pod (`strategy: Recreate`, RWO volume), stopping
*both* Minecraft servers.
4. **Optional, cosmetic:** to stop two nodes ARP-announcing `192.168.2.22`, delete
`lease/cilium-l2announce-crafty-crafty-l4-tcp` in `kube-system` and let it re-elect — a 1-in-3
chance of landing on k3s-prod-1. Not worth doing: under `Cluster` the split costs nothing but ARP
churn, and a redraw is equally random.
5. **After step 1, watch for the SNAT/RakNet question (h6 above)** — if a *second* simultaneous player
evicts the first, that is the SNAT theory becoming real and it needs its own measured
investigation (masquerade port reuse, UDP conntrack), not a guess.
## 2026-09-29 (later) — Bedrock IS reachable on 19132/udp. The `TRANSPORT TYPE ERROR` banner is advisory, and the last entry's "REASONED, NOT OBSERVED" call has now been OBSERVED — correct
**Do not read this as overturning the previous entry's recommendation. It confirms it.** What it
overturns is the conclusion the operator and I both drew from the server's *reaction* to it.
### The sequence
The entry above ended with operator action 1: stop `K3s-Bedrock`, set `transport=raknet`, start it —
explicitly flagged **"REASONED, NOT OBSERVED … Nobody has seen a packet answered on 19132/udp on this
cluster."** The operator did it. `/crafty/logs/audit.log`, measured:
21:32:32 issued command stop_server for server K3s-Bedrock
So *"NetherNet is the only supported transport type"* is a **support statement, not a capability
statement**: Microsoft declines to support RakNet and ships it working. Treat the banner as a
deprecation notice with an **unknown expiry** — a future BDS may genuinely remove RakNet, and that is
the day this exposure needs GeyserMC (see *If RakNet is ever really removed* below).
> **The shipped-comment trap, stated plainly.** `server.properties` lists `raknet` as allowed and the
> binary logs an ERROR saying it is not supported. Both are the vendor, and they disagree. The tiebreak
> is neither prose: it is `/proc/net/udp` and a pong on the wire. Whenever this file says a Bedrock
> server is or is not reachable, it is because someone pinged it — not because a comment said so.
#### The steady-state `too few fields` warning is benign and is now the expected signature
Crafty will log `Bedrock ping returned a string with too few fields - missing values [None]` every
~5 s forever. **That means the ping SUCCEEDED.** BDS 1.26.52's pong carries 16 `;`-separated fields;
Crafty 4.10.8's `pingpong_string_fields` table knows 14 and expects the last to be an empty
terminator. It instead finds `'0'`, so `last_enumeration` stops at index 12 and the parser reports the
one `None` terminator entry as missing. Every field that matters (edition, MOTD, protocol, version
name, player count, max players, uuid, game mode, both ports) parsed correctly. **Do not chase this
warning.** The failure signature is the *other* message, `Got timeout`.
### "You are not invited to play on this server" — the join gate is the allowlist, not the network (MEASURED 2026-09-29 21:56; **RESOLVED and confirmed 22:03**)
A Bedrock client reported **"Unable to connect / You are not invited to play on this server"**. That is
an **authorization** refusal, and it is the expected, guaranteed behaviour of this server's current
3. **Turn the allowlist off entirely** — `allow-list=false` in `server.properties` (restart), or
`allowlist off` in the console. Note the vendor's warning: `allowlist on|off` *"does not change the
value in the server.properties file"*, so a console `off` silently reverts on the next start.
**Not recommended:** the game port is deliberately un-gated at the network layer (see *Game-port L4
exposure*), so the allowlist is the **only** thing deciding who may join. Keep it `true` and enumerate
players.
> **`allowlist.json` is application state on the PVC**, exactly like `transport=raknet`. Neither is in
> git and **Flux can restore neither**. Be precise about what breaks them, because the two cases differ:
>
> - **A pod or server *restart* preserves both** — observed: the 22:02:48 restart (pid 47006 → 47875)
> kept `transport=raknet` (`server.properties` mtime unchanged at 21:33:31) and kept the allowlist
> entry. What a **pod** restart *does* break is that `K3s-Bedrock` has `auto_start=0`, so the Bedrock
> server simply **does not come back** until someone starts it by hand.
> - **A server *recreate* or reinstall silently resets both** — a fresh `server.properties` ships
> `transport=nethernet` (nothing bound on 19132/udp) and a fresh `allowlist.json` is `[]` (everyone
> refused). Neither change announces itself, and the server will look healthy and started in the panel
> while being unreachable *and* unjoinable.
>
> **Treat both files as backup-relevant, and re-run the 5-step connect procedure above after any Bedrock
> recreate or BDS update.**
#### Why this does not change the transport recommendation
`transport=raknet` is correct, and with the allowlist entry in place the combination is **proven
end-to-end by a player who has joined** — not merely the best-reasoned option. The allowlist refusal was
orthogonal to transport all along: it would have refused players identically under NetherNet. Switching
to NetherNet would *add* work (TCP 19132 in `crafty-l4-tcp` **and** in `crafty-allow-ingress`, plus
`server-udp-ports` pinned and exposed) and would admit **not one extra player**. The ranking below stands,
and option 1 is now settled rather than provisional.
#### Decision (PROVEN, not provisional): `transport=raknet` stays; the 19132-19134/UDP exposure is correct and live
No manifest change is needed or wanted. `service-l4.yaml`, `networkpolicy.yaml` and the
`containerPort`s were already shaped for exactly RakNet, and that shape is now **proven by a player who
has connected and joined over `192.168.2.22:19132`** — not assumed, and not merely reasoned. The options
that would have required manifest work are all **not recommended**:
| Option | Manifest consequence | Verdict |
|---|---|---|
| **Keep `transport=raknet`** + allowlist entries (**this is what works**) | **none** — the existing Service, policy and ports were already correct | **Done; proven.** A player has joined over the VIP. Zero manifest change, vanilla parity, real Bedrock world. |
| **GeyserMC + Floodgate** on the `homelab` Paper server | none either (Geyser also binds `19132/udp`), but it would **collide** with the live BDS on 19132 — one of them must move | Hold in reserve. Only worth it if RakNet is really removed, or if you *want* one shared world. Cost: Bedrock players join the **Java** world, not a Bedrock world; Java-only mechanics and some content differ; it is another moving part to patch. |
| **Pin/downgrade BDS** to a pre-nethernet release | none | Unnecessary — the *current* build already speaks RakNet. Also fragile: Microsoft removes old BDS downloads, and Crafty's Bedrock installer tracks latest. UNVERIFIED whether Crafty can pin a version at all. |
| **A third-party server** (PocketMine-MP, Nukkit) | none | No. Loses vanilla parity and the world format; a large change to solve a problem that is not occurring. |
| **Accept NetherNet** | 19132-19134/UDP becomes **inert**; nethernet would instead need **TCP** 19132 added to `crafty-l4-tcp` *and* to `crafty-allow-ingress`, plus `server-udp-ports` pinned and exposed for the negotiated media path | No — and note it is **not currently in use**: `transport=raknet` and there is no TCP 19132 listener. Strictly more manifest work than the option that already works, it admits no extra player (the allowlist is the gate), and WebRTC media behind a shared VIP and SNAT is unproven here. |
#### If RakNet is ever really removed
Then BDS opens no UDP listener and this exposure has nothing to bind to — an architecture problem, not
a config one. The migration is **GeyserMC + Floodgate as a plugin on the existing `homelab` Paper
server**, because Geyser speaks RakNet on `19132/udp` itself: the VIP, the Service, the policy and the
`containerPort`s all stay **byte-for-byte as they are**, and Bedrock clients keep connecting to
`crafty-mc.home.keithjackson.dev:19132`. What changes is the world: Bedrock players land in the Java
world via Floodgate (no Xbox-authenticated Bedrock account needed on the Java side), the Bedrock server
is retired, and `K3s-Bedrock` stops the day Geyser starts — **they cannot share 19132**. Installing it
is a PVC write plus a Paper restart, i.e. an **operator action**: drop `Geyser-Spigot.jar` and
`floodgate-spigot.jar` into `/crafty/servers/13c7b85d-…/plugins/` and restart `homelab`, then set
Geyser's `bedrock.port: 19132` and `clone-remote-port: false` in its generated `config.yml`.
Geyser's own Bedrock-version support is **external and unmeasured here** — check that it supports the
Bedrock client version in use before committing.
### Bedrock realities that are NOT platform defects but present identically
- **LAN / Friends-tab discovery can never work here.** Bedrock discovery is a UDP broadcast on 19132
inside one L2 segment; this server sits behind a LoadBalancer VIP (and SNAT), not on the LAN, so it
will **never** appear in a client's Friends/LAN list. Players must use **Add Server** and type the
address and port by hand: `crafty-mc.home.keithjackson.dev` (or the VIP) with port **19132**.
`external-dns` publishes that name from the **-tcp** Service, so it resolves to the shared VIP and
is correct for Bedrock too — only the port differs from Java's 25565.
- **Consoles cannot reach this at all.** Switch/Xbox/PlayStation Bedrock clients have no
arbitrary-server-address field; only Windows/mobile/Fire do. Reaching it from a console needs a DNS
redirect of a featured-server hostname, which is out of scope for `service-l4.yaml`.
- **`enable-lan-visibility=true` (the shipped default) collides with Bedrock slots 1 and 2 — now
OBSERVED, not merely documented.** Under `transport=raknet` it makes a server bind the **default**
ports 19132 *and* 19133 even when `server-port`/`server-portv6` say otherwise; the shipped
`bedrock_server_how_to.html` says so, and on 2026-09-29 a RakNet ping to
`192.168.2.22:19133` returned **the same pong, from the same `server_guid`, as `:19132`** — one
server occupying two of the three Bedrock slots. `:19134` correctly timed out. So a second Bedrock
server put on 19133 will fight the first. Set `enable-lan-visibility=false` on every Bedrock server
after the first; LAN discovery cannot work here anyway (above).
- **`server-portv6` is the IPv6 listener and is not exposed; `19133` is nonetheless in use.**
`service-l4.yaml` is IPv4 single-stack, so the *IPv6* socket `…:4ABD` that BDS opens for
`server-portv6=19133` is unreachable from the LAN. But because `enable-lan-visibility=true` (above)
the server *also* answers on **IPv4 19133**, which the UDP Service does expose — measured. So
`19133` in `server.properties` genuinely does mean "slot 1 is taken", contrary to what this file
previously said.
## Policies
`namespace.yaml` sets only `gateway-access=shared` (platform namespace, like headlamp/homepage), so
@@ -383,48 +114,14 @@ Gateway VIP). The sidecar→Crafty hop is pod-local (`127.0.0.1`) and not subjec
local admin. A **non-platform-admins** user → **403** (authenticated but not authorized).
2.**Pod health / fsGroup:**`kubectl -n crafty get pod` Ready 2/2; Crafty writes its DB under
`/crafty/app/config` and `/crafty/servers` without EACCES.
3. **Server-create + game reach (Java):** in the Crafty UI create a Java server bound to `25565`;
start it; connect a Minecraft client to `crafty-mc.home.keithjackson.dev:25565` (or
`192.168.2.22:25565`) from the LAN and confirm the world loads.
4. **Bedrock: the connect procedure (VERIFIED end-to-end 2026-09-29 — a player has joined).**
Creating and starting the server is **not sufficient**. Do these in order; each step has a distinct
failure signature:
1. **`transport=raknet`** in `server.properties`. The vendor default is `nethernet`, which opens
**no UDP listener at all**.
2. **A live UDP listener:** `grep 4ABC /proc/net/udp /proc/net/udp6` → a wildcard bind. Under
`nethernet` you instead see `4ABC` on **TCP**, which nothing here exposes.
3. **A pong from off-cluster:** a RakNet Unconnected Ping to `192.168.2.22:19132` returns a `0x1c`
packet whose MOTD string starts `MCPE;`. This proves the VIP, Service and policy path.
4. **An allowlist entry for the player's Xbox Gamertag.** `allow-list=true` with
`allowlist.json == []` refuses **every** player with *"You are not invited to play on this
server"* while the server is otherwise perfectly reachable. In the panel console:
`allowlist add <Gamertag>`.
5. **Add Server** by hand in a Windows/mobile client: `crafty-mc.home.keithjackson.dev`, port
`19132`. It will never appear in the LAN/Friends tab, and a console cannot reach it at all.
**Two log messages appear on a fully working server and must be ignored:** BDS's
`================ TRANSPORT TYPE ERROR ===================` banner (advisory — see *Trap 2*), and
Crafty's perpetual `Bedrock ping returned a string with too few fields`. Neither indicates a fault;
chasing them is what cost two sessions. The **real** failure signatures are `Got timeout while
issuing bedrock ping` (no listener) and *"You are not invited"* (allowlist).
3.**Server-create + game reach:** in the Crafty UI create a Java server bound to `25565`; start it;
connect a Minecraft client to `crafty-mc.home.keithjackson.dev:25565` (or`192.168.1.55:25565`)
from the LAN and confirm the world loads. (Bedrock: a server on `19132/udp` reached at the VIP.)
## Runtime state: the servers themselves are NOT in git
As of 2026-09-29 **two** servers exist (both measured live, `select * from servers`):
`K3s-Bedrock` was created 2026-09-29 20:51 and **still** has `auto_start=0` **and**
`crash_detection=0` — re-measured 2026-09-29 21:41 straight out of `crafty.sqlite` (read-only), so the
create-API trap documented at the bottom of this file is still live and still unfixed. **It will not
come back after a pod restart**, and merging any change to `deployment.yaml` restarts the pod
(`strategy: Recreate`). PATCH both to true and read them back.
`homelab` was created through Crafty's v2 API, and **Mojang's
As of 2026-09-14 one Java server exists — **"homelab"**, Paper 26.2, TCP 25565, 2 GiB heap, autostart
on, UUID `13c7b85d-00bb-4a58-8c86-2433ca47891c`. It was created through Crafty's v2 API, and **Mojang's
EULA (<https://aka.ms/MinecraftEULA>) was accepted on the operator's behalf** to let it start; see the
DECISIONS-LOG entry of that date.
@@ -435,10 +132,6 @@ application state on the `crafty-data` Longhorn PVC, in two places, and **Flux c
heap sizes, `auto_start` / `crash_detection`.
-`/crafty/servers/<uuid>/` (`servers` subPath) — `paper.jar`, `eula.txt`, `server.properties`, and
**the generated world**. The jar is re-downloadable; the world is not.
**`transport=raknet` lives here too.** It is the one setting that makes Bedrock reachable at all (see
*the transport trap*), it is not in git, Flux cannot restore it, and a freshly created or reinstalled
Bedrock server gets the vendor default `transport=nethernet` back — i.e. it silently regresses to
"nothing bound on 19132/udp". Re-apply and re-ping after any Bedrock server recreate or BDS update.
So a rebuild from git alone yields a healthy but **empty** Crafty with nothing listening on 25565.
Recreating a server by hand means: create it in the UI or API, accept the EULA again, and PATCH
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.