feat(branding): apply TFX rebrand to Kuma at container boot

Kuma ships branding baked into hashed frontend assets, so patch at boot
instead of forking the image:
- seed/branding/apply-branding.sh: swaps "Uptime Kuma" -> "R2D2 Status"
  in dist/assets and replaces icon.svg, then execs the original command
- docker-compose: entrypoint runs the branding step before server start,
  mounts branding/ read-only
- public/favicon.png: TFX favicon
This commit is contained in:
Josh Belke 2026-07-07 10:18:43 -04:00
commit 0c779e49b1
4 changed files with 63 additions and 0 deletions

View file

@ -0,0 +1,52 @@
#!/bin/sh
# Self-healing R2D2 rebrand of the prebuilt Uptime Kuma frontend.
#
# Runs as the container entrypoint on every start, so the branding survives
# `docker compose down/up` and even `louislam/uptime-kuma:2` image pulls — it
# re-derives the content-hashed bundle name at runtime instead of pinning it.
# Source of truth is this script + icon.svg on the host, bind-mounted at
# /branding (see docker-compose.yml). After patching it exec's the real server.
#
# Why patch at runtime instead of bind-mounting the files directly:
# - The served logo has .br/.gz precompressed twins that Kuma serves with
# priority; a plain icon.svg mount would be shadowed by them.
# - The JS bundle filename (index-<hash>.js) changes every Kuma release, so a
# static mount would break on update. A glob resolves it fresh each boot.
set -e
BRAND_DIR="$(dirname "$0")"
DIST=/app/dist
# 1. Logo — overwrite the served /icon.svg (and the public-dir copy) and drop
# the precompressed twins so our SVG is what actually ships.
if [ -f "$BRAND_DIR/icon.svg" ]; then
for target in "$DIST/icon.svg" /app/public/icon.svg; do
cp "$BRAND_DIR/icon.svg" "$target" 2>/dev/null || true
done
rm -f "$DIST/icon.svg.br" "$DIST/icon.svg.gz"
fi
# 2. App-name / sidebar text -> "R2D2 Status". Patterns are minification-
# agnostic (no reliance on mangled variable names):
# $t("Uptime Kuma") -> $t("R2D2 Status") vue-i18n returns the key when a
# translation is missing, so this
# renders literally "R2D2 Status"
# "Uptime Kuma",-1) -> "R2D2 Status",-1) Vue hoisted static text nodes
for f in "$DIST"/assets/index-*.js; do
[ -f "$f" ] || continue
if grep -q "Uptime Kuma" "$f"; then
node -e '
const fs = require("fs");
const f = process.argv[1];
let s = fs.readFileSync(f, "utf8");
s = s.split(`$t("Uptime Kuma")`).join(`$t("R2D2 Status")`);
s = s.split(`"Uptime Kuma",-1)`).join(`"R2D2 Status",-1)`);
fs.writeFileSync(f, s);
' "$f"
rm -f "$f.br" "$f.gz"
echo "[branding] patched $(basename "$f")"
fi
done
echo "[branding] applied — starting Kuma"
exec "$@"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 108 KiB

View file

@ -1,12 +1,22 @@
# Local Uptime Kuma for the R2D2 status conversion.
# Host port 3011 (3001 is taken by dokploy on this machine). Data persists in
# ./data next to this file.
#
# Branding (TFX logo + "R2D2 Status" name) is re-applied on every start by
# ./branding/apply-branding.sh, which runs as the entrypoint before the server.
# That's how the rebrand survives container recreation and image pulls — the
# prebuilt image has it baked into hashed frontend assets, so we patch at boot.
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: r2d2-kuma
restart: unless-stopped
# Keep dumb-init (zombie reaping) from the base image, insert the branding
# step, then hand off to the original command (below).
entrypoint: ["/usr/bin/dumb-init", "--", "/branding/apply-branding.sh"]
command: ["node", "server/server.js"]
volumes:
- ./data:/app/data
- ./branding:/branding:ro
ports:
- "3011:3001"

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB