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
- The control plane reads
brand.yamlfrom the path inAPP_BRAND_FILE, or./brand.yamlrelative to its working directory when that is unset. The installer’s systemd unit runs in the data directory, so the copyinstall.shwrites there is the one that is read. - 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. primary_color_darkfalls back toprimary_colorwhen empty.- Startup fails if
nameorbinary_nameis empty, or if the file cannot be read or parsed. Running the binary directly without abrand.yamlproduces an error telling you to supply one or setAPP_BRAND_FILE.
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 inbrand.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 frombrand.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>. Theghcr.io/glincker/owner is a constant ininternal/provision/cloudinit.goand in the SSH provisioning path ininternal/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.shwrites a defaultbrand.yamlinto the data directory only when none exists, and that default carries the upstream values. Edit the script’swrite_brandstep, or place your ownbrand.yamlin the data directory before the first run. - Release and docs hosting:
repo_urlanddocs_urlpoint 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: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.