fixed
This commit is contained in:
@@ -5,6 +5,14 @@ let
|
||||
palette = serverCfg.colorScheme.palette or { };
|
||||
port = 8080;
|
||||
assetSize = 64;
|
||||
cacheMode = containerCfg.extra.cacheMode or "off";
|
||||
cacheControl =
|
||||
if cacheMode == "disk" then
|
||||
containerCfg.extra.cacheControl or "public, max-age=3600"
|
||||
else if cacheMode == "off" then
|
||||
"no-store"
|
||||
else
|
||||
throw "favicon cacheMode must be either `off` or `disk`";
|
||||
priority = toString (containerCfg.extra.priority or 2147482647);
|
||||
logoSvgFileName = builtins.baseNameOf (toString mediaCfg.logo.svg);
|
||||
logoSvgMount = "/assets/${logoSvgFileName}";
|
||||
@@ -68,7 +76,11 @@ let
|
||||
]);
|
||||
serverScript = pkgs.writeText "favicon-server.py" ''
|
||||
from io import BytesIO
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
import cairosvg
|
||||
@@ -78,7 +90,9 @@ let
|
||||
LISTEN_HOST = "0.0.0.0"
|
||||
LISTEN_PORT = ${toString port}
|
||||
ASSET_SIZE = ${toString assetSize}
|
||||
CACHE_CONTROL = "no-store"
|
||||
CACHE_MODE = ${builtins.toJSON cacheMode}
|
||||
CACHE_CONTROL = ${builtins.toJSON cacheControl}
|
||||
CACHE_DIR = Path("/cache")
|
||||
|
||||
with open(LOGO_PATH, "rb") as fh:
|
||||
LOGO_BYTES = fh.read()
|
||||
@@ -86,6 +100,8 @@ let
|
||||
DEFAULT_PROFILE = ${if defaultProfile == null then "None" else builtins.toJSON defaultProfile}
|
||||
APP_DOMAIN = (${builtins.toJSON serverCfg.domain} or "").strip().lower()
|
||||
DEFAULT_COLORS = {"bg": "#111827", "fg": "#f8fafc"}
|
||||
LOGO_HASH = hashlib.sha256(LOGO_BYTES).hexdigest()
|
||||
ICON_CACHE_LOCK = threading.Lock()
|
||||
|
||||
def _request_host(headers):
|
||||
host = (
|
||||
@@ -113,25 +129,19 @@ let
|
||||
return candidates
|
||||
|
||||
def _profile_for_host(host):
|
||||
candidates = _host_candidates(host)
|
||||
print(f"favicon-profile host={host!r} candidates={candidates!r} mappings={list(MAPPINGS.keys())!r}")
|
||||
for candidate in candidates:
|
||||
for candidate in _host_candidates(host):
|
||||
profile = MAPPINGS.get(candidate)
|
||||
print(f"favicon-profile-check candidate={candidate!r} hit={profile is not None}")
|
||||
if profile:
|
||||
print(f"favicon-profile-match candidate={candidate!r} profile={profile!r}")
|
||||
return candidate, profile
|
||||
print(f"favicon-profile-default host={host!r} default={DEFAULT_PROFILE!r}")
|
||||
return None, DEFAULT_PROFILE
|
||||
|
||||
def _replace_logo_fill(svg, color):
|
||||
svg, count = re.subn(
|
||||
svg, _ = re.subn(
|
||||
"fill:#3193f5",
|
||||
f"fill:{color}",
|
||||
svg,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
print(f"favicon-fill replace_count={count} color={color}")
|
||||
return svg
|
||||
|
||||
def _colors(profile):
|
||||
@@ -154,8 +164,6 @@ let
|
||||
svg = LOGO_BYTES.decode("utf-8")
|
||||
svg = _replace_logo_fill(svg, colors["fg"])
|
||||
svg = _add_background(svg, colors["bg"])
|
||||
print(f"favicon-render fg={colors['fg']} bg={colors['bg']} mode=svg2png")
|
||||
print(f"favicon-svg {svg}")
|
||||
|
||||
png = cairosvg.svg2png(
|
||||
bytestring=svg.encode("utf-8"),
|
||||
@@ -168,21 +176,43 @@ let
|
||||
rgba.save(output, format="ICO", sizes=[(ASSET_SIZE, ASSET_SIZE)])
|
||||
return output.getvalue()
|
||||
|
||||
def _cache_path(colors):
|
||||
digest = hashlib.sha256(
|
||||
f"{ASSET_SIZE}:{LOGO_HASH}:{colors['bg']}:{colors['fg']}".encode("utf-8")
|
||||
).hexdigest()
|
||||
return CACHE_DIR / f"{digest}.ico"
|
||||
|
||||
def _payload_for(colors):
|
||||
if CACHE_MODE != "disk":
|
||||
return _render_icon(colors)
|
||||
|
||||
cache_path = _cache_path(colors)
|
||||
with ICON_CACHE_LOCK:
|
||||
if cache_path.exists():
|
||||
return cache_path.read_bytes()
|
||||
|
||||
payload = _render_icon(colors)
|
||||
with ICON_CACHE_LOCK:
|
||||
if cache_path.exists():
|
||||
return cache_path.read_bytes()
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
tmp_path = cache_path.with_suffix(".tmp")
|
||||
tmp_path.write_bytes(payload)
|
||||
os.replace(tmp_path, cache_path)
|
||||
return payload
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
server_version = "favicon-router/1.0"
|
||||
|
||||
def _serve(self, include_body):
|
||||
host = _request_host(self.headers)
|
||||
matched_host, profile = _profile_for_host(host)
|
||||
print(f"favicon-request host={host!r} matched={matched_host!r} include_body={include_body}")
|
||||
if not profile:
|
||||
print("favicon-request no-profile")
|
||||
self.send_error(404, "No favicon mapping for host")
|
||||
return
|
||||
|
||||
colors = _colors(profile)
|
||||
print(f"favicon-colors bg={colors['bg']} fg={colors['fg']}")
|
||||
payload = _render_icon(colors)
|
||||
payload = _payload_for(colors)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "image/x-icon")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
@@ -222,13 +252,13 @@ let
|
||||
};
|
||||
in {
|
||||
runtime = {
|
||||
# paths = [
|
||||
# {
|
||||
# path = "${serverCfg.path.config.path}/favicon";
|
||||
# mode = "0755";
|
||||
# dirs = [ "cache" ];
|
||||
# }
|
||||
# ];
|
||||
paths = [
|
||||
{
|
||||
path = "${serverCfg.path.config.path}/favicon";
|
||||
mode = "0755";
|
||||
dirs = [ "cache" ];
|
||||
}
|
||||
];
|
||||
|
||||
containers = {
|
||||
server = builder.mkContainer {
|
||||
@@ -244,7 +274,7 @@ in {
|
||||
};
|
||||
overrides = {
|
||||
volumes = [
|
||||
# "${serverCfg.path.config.path}/favicon/cache:/cache"
|
||||
"${serverCfg.path.config.path}/favicon/cache:/cache"
|
||||
"${mediaCfg.logo.svg}:${logoSvgMount}:ro"
|
||||
];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user