Skip to main content
The product name is never hardcoded in the Go or web source. Every user-visible identity string comes from one Brand struct in internal/brand, loaded at startup from a brand.yaml file and optionally overridden by environment variables. This page covers what that layer controls, so you can run a renamed fork or just see why the platform names things the way it does.

How the brand is loaded

  1. The control plane reads brand.yaml from the path in APP_BRAND_FILE, or ./brand.yaml relative to its working directory when that is unset. The installer’s systemd unit runs in the data directory, so the copy install.sh writes there is the one that is read.
  2. Any APP_BRAND_* environment variable that is set and non-empty overrides the matching file field. Env wins over the file, so a deployment can be rebranded without editing a file or rebuilding.
  3. primary_color_dark falls back to primary_color when empty.
  4. Startup fails if name or binary_name is empty, or if the file cannot be read or parsed. Running the binary directly without a brand.yaml produces an error telling you to supply one or set APP_BRAND_FILE.
The node agent reads the same file and overrides to derive the same short name the control plane uses (see what the brand drives). The dashboard does not embed any name: it fetches GET /api/v1/brand on boot, which is reachable before sign-in so the login screen can be branded, and applies the colors as CSS variables. The env prefix is APP_BRAND_ on purpose, not one containing the product name, so a rename never needs an env migration.

Fields

Each field maps to one key in brand.yaml and one env var. The optional contact fields follow one rule: when empty, the corresponding link is not rendered. APP_BRAND_FILE is not a brand field. It only selects which file to load.

What the brand drives

The CLI takes its command name from os.Args[0], so renaming the *-cli binary renames the command in every usage message and in generated shell completions.

Building a renamed binary

The binary file name comes from how you build and install it, not from brand.yaml. The release workflow builds levelrail, levelrail-cli and levelrail-agent and injects only the version, through -ldflags "-X github.com/GLINCKER/levelrail/internal/version.Version=...". Set binary_name in brand.yaml to match whatever you name the control plane binary, so the derived agent and CLI names line up with the files you ship.

What a fork must still change

The brand layer covers names and prefixes. These places stay upstream until you change them:
  • Agent image registry: provisioned nodes pull ghcr.io/glincker/<agent-name>:<tag>. The ghcr.io/glincker/ owner is a constant in internal/provision/cloudinit.go and in the SSH provisioning path in internal/api/node_ssh_provision.go, not a brand field. A fork publishing its own agent image must change both.
  • Go module path: the module is github.com/GLINCKER/levelrail. Renaming it means rewriting imports, and the shared kit has its own module path.
  • The installer: install.sh writes a default brand.yaml into the data directory only when none exists, and that default carries the upstream values. Edit the script’s write_brand step, or place your own brand.yaml in the data directory before the first run.
  • Release and docs hosting: repo_url and docs_url point where you tell them to, but your fork needs its own releases and its own docs site for those links to resolve.
  • Documentation text: these docs name the product directly. The brand layer does not rewrite prose.

Set a brand at runtime

Override only what you need, in the control plane’s environment file:
Restart the control plane after changing a brand value, then reload the dashboard. Because the agent derives the mesh interface name from the same inputs, set the same APP_BRAND_* values (or the same brand.yaml) wherever an agent runs, or its interface name will not match the control plane’s.

Shared Go kit

The brand-neutral Go packages the platform shares with other programs.

Architecture

How the control plane, agent and dashboard fit together.
Last modified on October 12, 2026