Files
meshcore-analyzer/cmd/server
nullrouten0andClaude Opus 5 478f74a452 fix(config): write the geo filter through a symlinked config.json (#2137)
Found while reworking #2125, unrelated to that PR's change.

In the Docker image the entrypoint symlinks `/app/config.json` to the
bind-mounted `/app/data/config.json`. `SaveGeoFilter` takes the first
path `os.Stat` resolves, so `configPath` stays the symlink, and the
tmp+rename then **replaces the link with a regular file inside the
container**:

- the mounted `config.json` keeps the old `geo_filter`, so the filter
the operator just saved appears nowhere on the host;
- server and ingestor read the detached file from then on, so the UI
keeps showing the new filter and nothing looks wrong;
- the next container start reverts it: the entrypoint's `ln -sf` puts
the link back, and re-creating the container drops the file anyway.

**Fix:** resolve a symlink at `configPath` before the write, so the save
lands in the file it points at and the link stays a link. Only a regular
file is followed — a link to a device, fifo or directory is left alone
and handled as before.

This is deliberately the opposite of `writeStatsAtomic` (refs #1170),
where replacing a destination symlink is the point because the default
stats path can be world-writable `/tmp`. `config.json` is different: the
link is one the image itself creates, and the directory is not
world-writable. Happy to narrow it further (e.g. only follow a link that
stays inside `configDir`) if you'd rather keep the two paths consistent.

**Verification:** `TestSaveGeoFilterSymlinkedConfig` fails on master —

```
--- FAIL: TestSaveGeoFilterSymlinkedConfig
    config_symlink_test.go:47: .../config.json is no longer a symlink (mode -rw-------) — the rename replaced it
    config_symlink_test.go:60: geo_filter missing from .../data/config.json: {"port":3000,"_comment":"keep me"}
```

— and passes here. `TestSaveGeoFilterKeepsOwner` covers keeping the
file's owner, and `TestSaveGeoFilterFifoSymlinkTarget` checks that only
regular files are followed. `go test ./...` in `cmd/server` passes.

**Single-file mounts:** when `config.json` is mounted as a single file,
the save now fails with 500 (`EBUSY`) instead of writing a copy that the
next start discards.

Our own instance has no geo filter configured, which is why this went
unnoticed there; its `/app/config.json` is still a symlink.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-13 08:44:49 +02:00
..