Skip to content

20260928 - Ship the CARTO basemap key to nodes with the OS image - #63

Merged
Purple10101 merged 1 commit into
mainfrom
20260928-carto-basemap-key
Sep 28, 2026
Merged

Purple10101 merged 1 commit into
mainfrom
20260928-carto-basemap-key

Conversation

@Purple10101

@Purple10101 Purple10101 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

What

retina-gui reads CARTO_API_KEY to stop the setup wizard's tower map coming back watermarked (retina-gui#100), and nothing supplies it. CARTO does not refuse an unkeyed request: it answers HTTP 200 with every tile stamped "API KEY REQUIRED", which is the state every node is in today.

The key is not in this repo, and cannot be

This repository is public, and a key in its history outlives every rotation.

  • configuration/retina-gui/retina-gui.yml carries carto_api_key: "" — committed, empty on purpose.
  • CI writes the real value from a repository secret into retina-gui_custom.yml beside it, which is gitignored.
  • with_first_found in radar_data_bootstrap prefers the CI-written file.

That is exactly the arrangement configuration/mender/ already uses for the tenant token, and the same reasoning retina-server states in its own deploy/env.prod.example. It is also the same key at the other end.

Needs one thing before merge: a CARTO_API_KEY repository secret. Without it the build still succeeds and produces the watermarked map it produces today.

Why /etc and not /data

This is the part worth pausing on, because the obvious choice is wrong.

/data survives an OS update by design. That means a file the image writes there only ever reaches a freshly flashed node, and never the existing fleet. /etc is on the rootfs, which every A/B update replaces wholesale, so it reaches existing nodes on the next release and rotates with it.

retina-gui's unit reads /etc/retina-gui/carto.env first and then /data/retina-gui/carto.env, so a node handed its own key by hand keeps it across updates. Later assignment wins in systemd.

No key means no file

Rather than a file assigning an empty string. The rootfs is rebuilt for every image, so "absent" cannot be a stale leftover, while an empty assignment would read as a key that had stopped working.

Verification

8 tests in tests/deployment-values/, covering the empty default, the gitignore entry, the CI step, the with_first_found ordering, the /etc path, the skip-when-empty guard and the 0600 mode.

Two of them fail if a key is ever pasted into the committed default — verified by doing exactly that and watching them go red, then restoring.

All four changed/added YAML files parse, and the full suite passes in a venv built the way CI builds one (pip install 'pytest>=8.0' and nothing else).

The first push of this branch failed CI: the new test imported yaml, which CI does not install. Fixed by asserting the line directly instead, rather than adding a dependency to a deliberately lean CI.

tests/contact-email shows two failures in my local environment. They are not a problem with this branch or with main — the Tests workflow is green on every recent main push — so they look environmental and nothing here touches them.

Companion change

This is inert on its own. retina-gui's systemd/retina-gui.service currently reads only /data/retina-gui/carto.env and needs the /etc line added above it. One line, not in this PR.

🤖 Generated with Claude Code

retina-gui reads CARTO_API_KEY to stop the setup wizard's tower map coming
back watermarked, and nothing supplies it. CARTO does not refuse an unkeyed
request: it answers HTTP 200 with every tile stamped "API KEY REQUIRED",
which is the state every node is in today.

The key cannot be committed. This repository is public, and a key in its
history outlives every rotation. So configuration/retina-gui/retina-gui.yml
carries an empty default, CI writes the real value from a repository secret
into retina-gui_custom.yml beside it, and with_first_found prefers the
latter. That is the arrangement configuration/mender/ already uses for the
tenant token, and the same reasoning retina-server states in its own
deploy/env.prod.example.

Written to /etc/retina-gui/carto.env rather than to /data, which is the
part worth pausing on. /data survives an OS update by design, so a file the
image writes there only ever reaches a freshly flashed node and never the
fleet. /etc is replaced wholesale by every A/B update, so this reaches
existing nodes on the next release and rotates with it. retina-gui's unit
reads /etc first and then /data, so a node handed its own key by hand keeps
it across updates.

No key supplied means no file, rather than a file assigning an empty
string: the rootfs is rebuilt for every image, so absent cannot be a stale
leftover, while an empty assignment would read as a key that had stopped
working. A build without the secret still succeeds and produces the
watermarked map it produces today.

Eight tests cover the arrangement, including two that fail if a key is ever
pasted into the committed default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Purple10101
Purple10101 force-pushed the 20260928-carto-basemap-key branch from 41590e7 to 468c9df Compare September 28, 2026 09:19
@Purple10101
Purple10101 merged commit bb4253e into main Sep 28, 2026
1 check passed
@Purple10101
Purple10101 deleted the 20260928-carto-basemap-key branch September 28, 2026 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant