Levelrail
Skip to content

White-labeling ​

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 explains what that layer controls so you can run a renamed fork, or just understand 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 deliberately APP_BRAND_, 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.

brand.yaml keyEnv varPurpose
nameAPP_BRAND_NAMEDisplay name. Required. Used in emails (invites, password reset), the two-factor authenticator label, the passkey relying-party name, the GitHub App display name, and the agent unit description.
short_nameAPP_BRAND_SHORT_NAMENamespace stem for Docker networks, resources and prefixes. Lowercased where a prefix is built.
binary_nameAPP_BRAND_BINARY_NAMEControl plane binary name. Required. Drives the agent binary name, the CLI name shown by diagnostics, and app spec file discovery (see below).
domainAPP_BRAND_DOMAINProduct domain string.
support_urlAPP_BRAND_SUPPORT_URLSupport destination, for example an issue tracker. Also the fallback web push contact when no support email is set.
support_emailAPP_BRAND_SUPPORT_EMAILDirect contact address. Used as the web push subscriber contact. Optional.
primary_colorAPP_BRAND_PRIMARY_COLORAccent color, applied as a CSS variable in the dashboard.
primary_color_darkAPP_BRAND_PRIMARY_COLOR_DARKAccent color in dark mode. Falls back to primary_color.
logo_svgAPP_BRAND_LOGO_SVGInline SVG logo markup.
docs_urlAPP_BRAND_DOCS_URLDocumentation root, used for links to guides.
discussions_urlAPP_BRAND_DISCUSSIONS_URLCommunity destination. Optional.
repo_urlAPP_BRAND_REPO_URLSource repository root. Also decides where release checks look (see below).

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 ​

Derived valueRuleUsed for
Agent namelowercased short_name plus -agentThe node agent's systemd unit, container name, environment file, data directory stem and default image name on provisioned nodes.
Firewall rule taglowercased short_name plus :The comment prefix on every host ufw rule the platform creates, so it only ever removes its own rules. See Host firewall.
Mesh interfaceshort_name lowercased, letters and digits only, plus 0, at most 15 characters; wg0 if nothing usable remainsThe WireGuard interface name on every node. The agent and control plane derive it from the same file, so they agree.
Update repositoryowner/name parsed from repo_url, lowercasedWhere the control plane checks for new releases, version skew and the self-upgrade channels. An empty or malformed repo_url yields no slug, so no release lookups.
Agent binary namebinary_name plus -agentThe agent binary named in the join token response when enrolling a node.
Diagnostics CLI namebinary_name plus -cliThe command named in doctor fix hints.
App spec filenameslowercased binary_name plus .yaml or .ymlOne of the filenames discovered in a repo, after app.yaml, app.yml, deploy.yaml and deploy.yml.
Docker namespaceshort_nameNetwork and resource name prefix for app teardown, deploy previews, supply-chain scans, model resources and the syslog log-drain sender.
Badge labelshort_name plus deployThe text on the deploy status badge.

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 are concrete locations that 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:

bash
APP_BRAND_NAME="Acme Deploy"
APP_BRAND_PRIMARY_COLOR="#0a7a5a"
APP_BRAND_SUPPORT_EMAIL="platform@acme.example"

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.

Released under the Apache 2.0 License.