@@ -835,6 +835,11 @@ finally:
835835 pkgs . blas
836836 pkgs . gcc . cc . lib
837837 pkgs . stdenv . cc . cc . lib
838+ # Chinese font support for matplotlib
839+ # Note: noto-fonts-cjk has been renamed to noto-fonts-cjk-sans
840+ pkgs . noto-fonts-cjk-sans
841+ pkgs . noto-fonts-cjk-serif
842+ pkgs . fontconfig
838843 ] ;
839844 } ;
840845
@@ -846,6 +851,31 @@ finally:
846851 mkdir -p $out/app $out/bin $out/tmp $out/etc/uv $out/home/python-user
847852 mkdir -p $out/etc/passwd.d $out/etc/group.d $out/etc/shadow.d
848853 mkdir -p $out/.local/lib/python3.12/site-packages
854+ mkdir -p $out/usr/share/fonts/truetype/noto-cjk
855+ mkdir -p $out/etc/fonts/conf.d
856+
857+ # Create fontconfig main configuration file
858+ # Fontconfig requires fonts.conf to exist, even if minimal
859+ cat > $out/etc/fonts/fonts.conf << 'FONTSCONF'
860+ <?xml version="1.0"?>
861+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
862+ <fontconfig>
863+ <!-- Font directory list -->
864+ <dir>/usr/share/fonts</dir>
865+ <dir>/usr/share/fonts/truetype</dir>
866+ <dir>/usr/share/fonts/truetype/noto-cjk</dir>
867+ <dir>/usr/share/fonts/opentype</dir>
868+ <dir>/usr/share/fonts/opentype/noto</dir>
869+
870+ <!-- Include conf.d directory for additional configurations -->
871+ <include ignore_missing="yes">conf.d</include>
872+
873+ <!-- Rescan interval (in seconds) -->
874+ <rescan>
875+ <int>30</int>
876+ </rescan>
877+ </fontconfig>
878+ FONTSCONF
849879
850880 # Create uv cache dir
851881 mkdir -p $out/tmp/.uv_cache
@@ -876,6 +906,151 @@ finally:
876906 # Copy MOSEK Python API from uv installation
877907 cp -r ${ mosekPythonPackages } /site-packages/* $out/opt/mosek/lib/python3.12/site-packages/
878908
909+ # Copy Noto CJK fonts to system font directory at build time
910+ # Note: noto-fonts-cjk has been split into noto-fonts-cjk-sans and noto-fonts-cjk-serif
911+ # Font files are typically in share/fonts/opentype/noto-cjk/ or share/fonts/truetype/noto-cjk/
912+ echo "Copying Noto CJK fonts..."
913+
914+ # Copy all font files from both packages (sans and serif)
915+ for font_pkg in "${ pkgs . noto-fonts-cjk-sans } " "${ pkgs . noto-fonts-cjk-serif } "; do
916+ # Check opentype/noto-cjk directory (most common location)
917+ if [ -d "$font_pkg/share/fonts/opentype/noto-cjk" ]; then
918+ echo " Copying from $font_pkg/share/fonts/opentype/noto-cjk"
919+ find "$font_pkg/share/fonts/opentype/noto-cjk" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.ttc" -o -name "*.otf.ttc" \) -exec cp {} $out/usr/share/fonts/truetype/noto-cjk/ \; 2>/dev/null || true
920+ fi
921+ # Check truetype/noto-cjk directory
922+ if [ -d "$font_pkg/share/fonts/truetype/noto-cjk" ]; then
923+ echo " Copying from $font_pkg/share/fonts/truetype/noto-cjk"
924+ find "$font_pkg/share/fonts/truetype/noto-cjk" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.ttc" -o -name "*.otf.ttc" \) -exec cp {} $out/usr/share/fonts/truetype/noto-cjk/ \; 2>/dev/null || true
925+ fi
926+ # Also check top-level fonts directory (fallback)
927+ if [ -d "$font_pkg/share/fonts" ]; then
928+ echo " Copying from $font_pkg/share/fonts (recursive)"
929+ find "$font_pkg/share/fonts" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.ttc" -o -name "*.otf.ttc" \) -exec cp {} $out/usr/share/fonts/truetype/noto-cjk/ \; 2>/dev/null || true
930+ fi
931+ done
932+
933+ # Verify fonts were copied
934+ FONT_COUNT=$(find $out/usr/share/fonts/truetype/noto-cjk -type f 2>/dev/null | wc -l)
935+ if [ "$FONT_COUNT" -eq 0 ]; then
936+ echo " WARNING: No font files were copied! Check font package paths."
937+ else
938+ echo " Successfully copied $FONT_COUNT font files to $out/usr/share/fonts/truetype/noto-cjk"
939+ fi
940+
941+ # Create symbolic links in alternative paths for compatibility with user scripts
942+ # This allows scripts that check specific paths (like /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc) to find the fonts
943+ mkdir -p $out/usr/share/fonts/opentype/noto
944+ if [ -f $out/usr/share/fonts/truetype/noto-cjk/NotoSansCJK-VF.otf.ttc ]; then
945+ ln -sf ../../truetype/noto-cjk/NotoSansCJK-VF.otf.ttc $out/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc 2>/dev/null || true
946+ echo " Created symlink: /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
947+ fi
948+
949+ # Create fontconfig configuration
950+ # IMPORTANT: No leading spaces before XML declaration
951+ cat > $out/etc/fonts/conf.d/99-noto-cjk.conf << 'FONTCONF'
952+ <?xml version="1.0"?>
953+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
954+ <fontconfig>
955+ <dir>/usr/share/fonts/truetype/noto-cjk</dir>
956+ <alias>
957+ <family>sans-serif</family>
958+ <prefer>
959+ <family>Noto Sans CJK SC</family>
960+ <family>Noto Sans CJK TC</family>
961+ <family>Noto Sans CJK JP</family>
962+ <family>Noto Sans CJK KR</family>
963+ </prefer>
964+ </alias>
965+ <alias>
966+ <family>serif</family>
967+ <prefer>
968+ <family>Noto Serif CJK SC</family>
969+ <family>Noto Serif CJK TC</family>
970+ <family>Noto Serif CJK JP</family>
971+ <family>Noto Serif CJK KR</family>
972+ </prefer>
973+ </alias>
974+ </fontconfig>
975+ FONTCONF
976+
977+ # Create matplotlib configuration directory and file at build time
978+ mkdir -p $out/home/python-user/.matplotlib
979+ cat > $out/home/python-user/.matplotlib/matplotlibrc << 'MATPLOTLIBRC'
980+ font.family: sans-serif
981+ font.sans-serif: Noto Sans CJK JP, Noto Sans CJK TC, Noto Sans CJK SC, Noto Sans CJK KR, DejaVu Sans
982+ axes.unicode_minus: False
983+ MATPLOTLIBRC
984+
985+ # Create font setup script for runtime
986+ cat > $out/setup-fonts.sh << 'EOF'
987+ #!/bin/bash
988+ # Setup Chinese fonts for matplotlib
989+ echo "Setting up Chinese font support..."
990+
991+ # Ensure user cache directories exist (these can be created in tmpfs)
992+ mkdir -p /home/python-user/.cache/fontconfig 2>/dev/null || true
993+ mkdir -p /home/python-user/.matplotlib 2>/dev/null || true
994+
995+ # Copy matplotlibrc from Nix store to runtime directory (if it exists)
996+ # The runtime directory is tmpfs, so we need to copy the config file
997+ if [ -f /nix/store/*-docker-setup/home/python-user/.matplotlib/matplotlibrc ]; then
998+ MATPLOTLIBRC_SOURCE=$(find /nix/store -path "*/docker-setup/home/python-user/.matplotlib/matplotlibrc" 2>/dev/null | head -1)
999+ if [ -n "$MATPLOTLIBRC_SOURCE" ]; then
1000+ cp "$MATPLOTLIBRC_SOURCE" /home/python-user/.matplotlib/matplotlibrc 2>/dev/null || true
1001+ fi
1002+ fi
1003+
1004+ # Create/update matplotlibrc with correct font names
1005+ # Use actual available font names: Noto Sans CJK JP (available) instead of SC (may not be available)
1006+ cat > /home/python-user/.matplotlib/matplotlibrc << 'MATPLOTLIBRC'
1007+ font.family: sans-serif
1008+ font.sans-serif: Noto Sans CJK JP, Noto Sans CJK TC, Noto Sans CJK SC, Noto Sans CJK KR, DejaVu Sans
1009+ axes.unicode_minus: False
1010+ MATPLOTLIBRC
1011+
1012+ # Update font cache (if fontconfig is available)
1013+ # Note: Font files are already in /usr/share/fonts/truetype/noto-cjk (copied at build time)
1014+ if command -v fc-cache >/dev/null 2>&1; then
1015+ fc-cache -fv 2>/dev/null || true
1016+ fi
1017+
1018+ # Verify font files are accessible
1019+ if [ -f /usr/share/fonts/truetype/noto-cjk/NotoSansCJK-VF.otf.ttc ]; then
1020+ echo "✓ Noto CJK font files found in /usr/share/fonts/truetype/noto-cjk/"
1021+ else
1022+ echo "⚠ Warning: Noto CJK font files not found in expected location"
1023+ fi
1024+
1025+ # Set matplotlib font configuration environment variables
1026+ export MPLCONFIGDIR=/home/python-user/.matplotlib
1027+ export FONTCONFIG_PATH=/etc/fonts
1028+
1029+ # Ensure fonts.conf exists (fontconfig requires it)
1030+ if [ ! -f /etc/fonts/fonts.conf ]; then
1031+ mkdir -p /etc/fonts
1032+ cat > /etc/fonts/fonts.conf << 'FONTSCONF'
1033+ <?xml version="1.0"?>
1034+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
1035+ <fontconfig>
1036+ <dir>/usr/share/fonts</dir>
1037+ <dir>/usr/share/fonts/truetype</dir>
1038+ <dir>/usr/share/fonts/truetype/noto-cjk</dir>
1039+ <dir>/usr/share/fonts/opentype</dir>
1040+ <dir>/usr/share/fonts/opentype/noto</dir>
1041+ <include ignore_missing="yes">conf.d</include>
1042+ <rescan>
1043+ <int>30</int>
1044+ </rescan>
1045+ </fontconfig>
1046+ FONTSCONF
1047+ fi
1048+
1049+ echo "Chinese font support configured"
1050+ EOF
1051+
1052+ chmod +x $out/setup-fonts.sh
1053+
8791054 # Create uv configuration
8801055 cat > $out/etc/uv/uv.toml << 'EOFUV'
8811056# Global uv configuration
@@ -1139,6 +1314,28 @@ in
11391314
11401315 # Ensure runtime directories exist in the layer
11411316 mkdir -p tmp tmp/.uv_cache .local/lib/python3.12/site-packages home/python-user app
1317+ mkdir -p usr/share/fonts/truetype/noto-cjk
1318+ mkdir -p usr/share/fonts/opentype/noto
1319+ mkdir -p etc/fonts/conf.d
1320+
1321+ # Ensure fonts.conf exists (fontconfig requires it)
1322+ if [ ! -f etc/fonts/fonts.conf ]; then
1323+ cat > etc/fonts/fonts.conf << 'FONTSCONF'
1324+ <?xml version="1.0"?>
1325+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
1326+ <fontconfig>
1327+ <dir>/usr/share/fonts</dir>
1328+ <dir>/usr/share/fonts/truetype</dir>
1329+ <dir>/usr/share/fonts/truetype/noto-cjk</dir>
1330+ <dir>/usr/share/fonts/opentype</dir>
1331+ <dir>/usr/share/fonts/opentype/noto</dir>
1332+ <include ignore_missing="yes">conf.d</include>
1333+ <rescan>
1334+ <int>30</int>
1335+ </rescan>
1336+ </fontconfig>
1337+ FONTSCONF
1338+ fi
11421339
11431340 # /tmp must be writable for non-root (cplex.log, matplotlib cache, tempfiles, uv cache)
11441341 chmod 1777 tmp
11871384 "SSL_CERT_FILE=${ pkgs . cacert } /etc/ssl/certs/ca-bundle.crt"
11881385 "CURL_CA_BUNDLE=${ pkgs . cacert } /etc/ssl/certs/ca-bundle.crt"
11891386 "REQUESTS_CA_BUNDLE=${ pkgs . cacert } /etc/ssl/certs/ca-bundle.crt"
1190- # Matplotlib configuration directory
1191- "MPLCONFIGDIR=/tmp/.matplotlib"
1387+ # Font configuration for matplotlib
1388+ "FONTCONFIG_PATH=/etc/fonts"
1389+ "MPLCONFIGDIR=/home/python-user/.matplotlib"
11921390 # Disable dangerous modules
11931391 "PYTHON_DISABLE_MODULES=os,subprocess,sys,importlib,exec,eval,compile,__import__,open,file,input,raw_input,urllib,requests,socket,ftplib,smtplib,poplib,imaplib,nntplib,telnetlib"
11941392 # Restrict network access
@@ -1203,7 +1401,10 @@ in
12031401 "MAIL="
12041402 ] ;
12051403 # Use tail -f /dev/null for container management (keeps container running)
1206- Cmd = [ "tail" "-f" "/dev/null" ] ;
1404+ # Note: setup-fonts.sh is best-effort (uses || true), so it won't fail the container startup
1405+ # The container will continue to work even if font setup fails
1406+ # Font files are already copied at build time, this just updates the font cache
1407+ Cmd = [ "bash" "-c" "cat /etc/passwd.d/python-user >> /etc/passwd 2>/dev/null || true && cat /etc/group.d/python-user >> /etc/group 2>/dev/null || true && cat /etc/shadow.d/python-user >> /etc/shadow 2>/dev/null || true && (/setup-fonts.sh || true) && tail -f /dev/null" ] ;
12071408 # Set security parameters - use non-root user
12081409 User = "1000:1000" ;
12091410 # Additional security settings
0 commit comments