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