From a33f2b2c985434ef08e482e09fb3868e1443a5be Mon Sep 17 00:00:00 2001 From: soraefir Date: Sun, 7 Jun 2026 20:49:53 +0200 Subject: [PATCH] fix --- modules/server/containers/apps/favicon.nix | 72 ++++++++++++---------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/modules/server/containers/apps/favicon.nix b/modules/server/containers/apps/favicon.nix index 5bc2b1f..c02b37f 100644 --- a/modules/server/containers/apps/favicon.nix +++ b/modules/server/containers/apps/favicon.nix @@ -33,9 +33,7 @@ let // lib.optionalAttrs (bg != null) { bg = bg; } // lib.optionalAttrs (fg != null) { fg = fg; }; hostMappings = lib.mapAttrs' (mapping: profile: - lib.nameValuePair - (if lib.hasInfix "." mapping then mapping else "${mapping}.${serverCfg.domain}") - (normalizeProfile profile) + lib.nameValuePair mapping (normalizeProfile profile) ) (containerCfg.extra.mappings or {}); traefikAssetPathRegexp = "^/(.*/)?" @@ -49,6 +47,7 @@ let configFile = pkgs.writeText "favicon-config.json" (builtins.toJSON { inherit cacheControl; borderRadius = borderRadius; + domain = serverCfg.domain; mappings = hostMappings; default = if containerCfg.extra ? default then normalizeProfile containerCfg.extra.default @@ -83,18 +82,36 @@ let APP_CONFIG = json.load(fh) with open(LOGO_PATH, "rb") as fh: LOGO_BYTES = fh.read() + MAPPINGS = APP_CONFIG.get("mappings", {}) + DEFAULT_PROFILE = APP_CONFIG.get("default") + APP_DOMAIN = (APP_CONFIG.get("domain", "") or "").strip().lower() + CACHE_CONTROL = APP_CONFIG.get("cacheControl", DEFAULT_CACHE_CONTROL) LOGO_HASH = hashlib.sha256(LOGO_BYTES).hexdigest() def _normalize_host(host): - return (host or "").split(":", 1)[0].strip().lower() + host = (host or "").split(",", 1)[0].split(":", 1)[0].strip().lower() + if APP_DOMAIN and host.endswith(f".{APP_DOMAIN}"): + return host[:-(len(APP_DOMAIN) + 1)] + return host + + def _request_host(headers): + forwarded = headers.get("X-Forwarded-Host", "") + original = headers.get("X-Original-Host", "") + host = forwarded or original or headers.get("Host", "") + return _normalize_host(host) def _pick_profile(host): - mappings = APP_CONFIG.get("mappings", {}) - return mappings.get(host) or APP_CONFIG.get("default") + return MAPPINGS.get(host) or DEFAULT_PROFILE def _color(value, fallback): return value if isinstance(value, str) and value else fallback + def _resolved_profile(profile): + return { + "bg": _color(profile.get("bg") or profile.get("background"), "#111827"), + "fg": _color(profile.get("fg") or profile.get("foreground"), "#f8fafc"), + } + def _replace_svg_color(svg, attribute, color): if attribute in {"fill", "stroke"}: svg = re.sub( @@ -122,51 +139,44 @@ let svg = _replace_svg_color(svg, "stroke", color) return "data:image/svg+xml;base64," + base64.b64encode(svg.encode("utf-8")).decode("ascii") - def _border_radius(): - value = APP_CONFIG.get("borderRadius", "8") - text = str(value).strip() - if text.endswith("px"): - return text - return f"{text}px" + border_radius = str(APP_CONFIG.get("borderRadius", "8")).strip() + if not border_radius.endswith("px"): + border_radius = f"{border_radius}px" - def _render_svg(profile): - bg = _color(profile.get("bg") or profile.get("background"), "#111827") - fg = _color(profile.get("fg") or profile.get("foreground"), "#f8fafc") - border_radius = _border_radius() - logo_data_uri = _tinted_logo_data_uri(fg) + def _render_svg(colors): + logo_data_uri = _tinted_logo_data_uri(colors["fg"]) canvas = 256 return f""" - + """ - def _cache_key(host, profile): - bg = _color(profile.get("bg") or profile.get("background"), "#111827") - fg = _color(profile.get("fg") or profile.get("foreground"), "#f8fafc") + def _cache_key(host, colors): cache_inputs = { "asset_size": ASSET_SIZE, - "bg": bg, - "border_radius": _border_radius(), - "fg": fg, + "bg": colors["bg"], + "border_radius": border_radius, + "fg": colors["fg"], "host": host, "logo_hash": LOGO_HASH, } payload = json.dumps(cache_inputs, sort_keys=True, separators=(",", ":")) return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16] - def _cache_name(host, profile): + def _cache_name(host, colors): safe_host = re.sub(r"[^a-z0-9.-]+", "_", host or "default") - return f"{safe_host}-{_cache_key(host, profile)}.ico" + return f"{safe_host}-{_cache_key(host, colors)}.ico" def _generate_asset(host, profile): - cache_name = _cache_name(host, profile) + colors = _resolved_profile(profile) + cache_name = _cache_name(host, colors) target = CACHE_DIR / cache_name if target.exists(): return target CACHE_DIR.mkdir(parents=True, exist_ok=True) - svg = _render_svg(profile).encode("utf-8") + svg = _render_svg(colors).encode("utf-8") png_bytes = cairosvg.svg2png(bytestring=svg, output_width=ASSET_SIZE, output_height=ASSET_SIZE) image = Image.open(BytesIO(png_bytes)) image.save(target, format="ICO", sizes=[(ASSET_SIZE, ASSET_SIZE)]) @@ -177,7 +187,7 @@ let server_version = "favicon-router/1.0" def _serve(self, include_body): - host = _normalize_host(self.headers.get("Host", "")) + host = _request_host(self.headers) profile = _pick_profile(host) if not profile: self.send_error(404, "No favicon mapping for host") @@ -188,7 +198,7 @@ let if self.headers.get("If-None-Match") == etag: self.send_response(304) self.send_header("ETag", etag) - self.send_header("Cache-Control", APP_CONFIG.get("cacheControl", DEFAULT_CACHE_CONTROL)) + self.send_header("Cache-Control", CACHE_CONTROL) self.end_headers() return @@ -196,7 +206,7 @@ let self.send_response(200) self.send_header("Content-Type", "image/x-icon") self.send_header("Content-Length", str(len(payload))) - self.send_header("Cache-Control", APP_CONFIG.get("cacheControl", DEFAULT_CACHE_CONTROL)) + self.send_header("Cache-Control", CACHE_CONTROL) self.send_header("ETag", etag) self.end_headers() if include_body: