55
66let
77 # Create restricted Python environment, remove dangerous modules
8- restrictedPython = pkgs . python312 . override {
9- # Custom Python configuration, disable dangerous features
10- pythonAttr = "python312" ;
11- } ;
8+ restrictedPython = pkgs . python312 ;
129
13- # Only include safe scientific computing packages, remove dangerous modules
10+ # Final solution for protobuf conflicts:
11+ # Install OR-Tools to /.local (separate from Nix store packages)
12+ # This completely avoids buildEnv conflicts
13+
14+ # Main Python environment with all core packages
1415 pythonWithPackages = restrictedPython . withPackages ( ps : with ps ; [
1516 pip
1617 setuptools
1718 wheel
18- # Only include safe scientific computing packages
19+ # Scientific computing packages
1920 cython
2021 numpy
2122 scipy
2223 pandas
2324 matplotlib
2425 scikit-learn
25- # Add gurobipy for optimization
26- gurobipy
27- # Add yfinance for financial data
28- yfinance
29- # Add seaborn for statistical data visualization
3026 seaborn
31- # Does not include dangerous modules like os, subprocess, sys
27+ # Optimization solvers
28+ gurobipy
29+ pulp # PuLP - Python Linear Programming (no protobuf dependency)
30+ # Exclude: yfinance (protobuf 6.x conflicts), ortools (installed separately)
31+ ] ) ;
32+
33+ # Separate OR-Tools environment (will be copied to /.local)
34+ pythonWithOrtools = restrictedPython . withPackages ( ps : with ps ; [
35+ ortools
36+ # Automatically includes: protobuf 5.x, absl-py, immutabledict, etc.
3237 ] ) ;
3338
34- # Use system Python installation directly
39+ # Extract OR-Tools packages to a derivation
40+ ortoolsPackages = pkgs . runCommand "ortools-packages" { } ''
41+ mkdir -p $out
42+ # Copy only the site-packages content (not bin/python to avoid conflicts)
43+ cp -r ${ pythonWithOrtools } /lib/python3.12/site-packages $out/
44+ '' ;
45+
46+ # Use main Python environment (only reference it once)
3547 systemPython = pythonWithPackages ;
3648
3749 # Create restricted Python interpreter startup script
3850 securePythonScript = pkgs . writeScriptBin "python" ''
3951 #!${ pkgs . bash } /bin/bash
4052
4153 # Set restricted environment
42- export PYTHONPATH="/app:/tmp/.local/lib/python3.12/site-packages"
54+ # Use /.local instead of /tmp/.local because /tmp is mounted with noexec flag
55+ export PYTHONPATH="/app:/opt/ortools/lib/python3.12/site-packages:/.local/lib/python3.12/site-packages"
4356 export PYTHONUNBUFFERED=1
4457 export PYTHONDONTWRITEBYTECODE=1
4558
4659 # Create writable site-packages directory if it doesn't exist
47- mkdir -p /tmp/ .local/lib/python3.12/site-packages
60+ mkdir -p /.local/lib/python3.12/site-packages
4861
4962 # Ensure the directory is writable and has correct permissions
50- chmod 755 /tmp/ .local/lib/python3.12/site-packages
63+ chmod 755 /.local/lib/python3.12/site-packages
5164
5265 # Restrict system tool access - ensure util-linux tools are accessible
5366 export PATH="${ pkgs . coreutils } /bin:${ pkgs . util-linux } /bin:/usr/local/bin:/usr/bin"
@@ -134,7 +147,8 @@ for func_name in dangerous_os_functions:
134147 setattr(os, func_name, lambda *args, **kwargs: safe_os_function(func_name, *args, **kwargs))
135148
136149# Add uv-installed packages directory to sys.path
137- uv_packages_path = "/tmp/.local/lib/python3.12/site-packages"
150+ # Use /.local instead of /tmp/.local because /tmp is mounted with noexec flag
151+ uv_packages_path = "/.local/lib/python3.12/site-packages"
138152if os.path.exists(uv_packages_path) and uv_packages_path not in sys.path:
139153 sys.path.insert(0, uv_packages_path)
140154
@@ -206,7 +220,8 @@ finally:
206220 #!${ pkgs . bash } /bin/bash
207221
208222 # Set restricted environment
209- export PYTHONPATH="/app:/tmp/.local/lib/python3.12/site-packages"
223+ # Use /.local instead of /tmp/.local because /tmp is mounted with noexec flag
224+ export PYTHONPATH="/app:/opt/ortools/lib/python3.12/site-packages:/.local/lib/python3.12/site-packages"
210225 export PYTHONUNBUFFERED=1
211226 export PYTHONDONTWRITEBYTECODE=1
212227
@@ -215,7 +230,7 @@ finally:
215230 export UV_LINK_MODE="copy"
216231
217232 # Configure uv to install packages to writable directory
218- export UV_PYTHON_SITE_PACKAGES="/tmp/ .local/lib/python3.12/site-packages"
233+ export UV_PYTHON_SITE_PACKAGES="/.local/lib/python3.12/site-packages"
219234
220235 # Set UV cache directory to writable location
221236 export UV_CACHE_DIR="/tmp/.uv_cache"
@@ -240,7 +255,7 @@ finally:
240255
241256 # Create necessary directories
242257 mkdir -p /tmp/.uv_cache
243- mkdir -p /tmp/ .local/lib/python3.12/site-packages
258+ mkdir -p /.local/lib/python3.12/site-packages
244259
245260 # Start uv
246261 exec ${ pkgs . uv } /bin/uv "$@"
@@ -250,13 +265,15 @@ finally:
250265 pythonWithGurobipy = pythonWithPackages ;
251266
252267 # Create secure environment with all necessary dependencies
268+ # Note: OR-Tools is NOT added here to avoid protobuf conflicts
269+ # Instead, it's copied to /.local via dockerSetup
253270 runtimeEnv = pkgs . buildEnv {
254271 name = "python-runtime-secure" ;
255272 paths = [
256- # Include Python with gurobipy pre-installed
257- pythonWithGurobipy
258- systemPython # Add system Python installation
259- securePythonScript # Add secure Python script
273+ # Include Python with gurobipy pre-installed (provides python binary)
274+ systemPython
275+ # OR-Tools is installed via dockerSetup to /.local (avoid protobuf conflict)
276+ # securePythonScript conflicts with systemPython's bin/python, skip it
260277 secureUvScript
261278 pkgs . gurobi # Direct use of gurobi package from nixpkgs (12.0.3)
262279 # Core runtime libraries (required)
@@ -293,8 +310,8 @@ finally:
293310 pkgs . gcc . cc . lib
294311 pkgs . stdenv . cc . cc . lib
295312 ] ;
296- # Avoid duplicate package issues
297- ignoreCollisions = true ;
313+ # No longer need ignoreCollisions since we're not merging conflicting paths
314+ ignoreCollisions = false ;
298315 } ;
299316
300317 # Create a package with necessary directories and files for non-root user
@@ -309,18 +326,24 @@ finally:
309326 mkdir -p $out/etc/passwd.d
310327 mkdir -p $out/etc/group.d
311328 mkdir -p $out/etc/shadow.d
312- mkdir -p $out/tmp/.local/lib/python3.12/site-packages
329+ mkdir -p $out/.local/lib/python3.12/site-packages
330+
331+ # Create ortools directory in a separate location (not /.local since it's tmpfs in containers)
332+ mkdir -p $out/opt/ortools/lib/python3.12/site-packages
333+ # Copy OR-Tools to /opt/ortools to avoid protobuf conflicts and tmpfs覆盖
334+ # This allows OR-Tools to use its own protobuf version (5.x)
335+ cp -r ${ ortoolsPackages } /site-packages/* $out/opt/ortools/lib/python3.12/site-packages/
313336
314337 # Create uv configuration
315- cat > $out/etc/uv/uv.toml << 'EOF '
316- # Global uv configuration
317- # Force use of system Python interpreter
318- python-preference = "system"
319- # Use copy mode for Docker environment
320- link-mode = "copy"
321- # Set cache directory
322- cache-dir = "/tmp/.uv_cache"
323- EOF
338+ cat > $out/etc/uv/uv.toml << 'EOFUV '
339+ # Global uv configuration
340+ # Force use of system Python interpreter
341+ python-preference = "system"
342+ # Use copy mode for Docker environment
343+ link-mode = "copy"
344+ # Set cache directory
345+ cache-dir = "/tmp/.uv_cache"
346+ EOFUV
324347
325348 # Create Gurobi setup script
326349 cat > $out/setup-gurobi.sh << 'EOF'
@@ -360,8 +383,62 @@ finally:
360383 echo "Or specify license file path via GRB_LICENSE_FILE environment variable"
361384 EOF
362385
386+ # Create OR-Tools verification script
387+ cat > $out/verify-ortools.sh << 'EOFSCRIPT'
388+ #!/bin/bash
389+ # Verify OR-Tools is pre-installed and working
390+ echo "Verifying OR-Tools installation..."
391+ echo ""
392+
393+ python << 'EOFPYTHON'
394+ import sys
395+
396+ try:
397+ import ortools
398+ from ortools.linear_solver import pywraplp
399+ from ortools.sat.python import cp_model
400+ import google.protobuf
401+
402+ print("✓ OR-Tools is pre-installed!")
403+ print(" OR-Tools version:", ortools.__version__)
404+ print(" Protobuf version:", google.protobuf.__version__)
405+ print("")
406+ print("Available solvers:")
407+ print(" - GLOP (Google Linear Optimization Package)")
408+ print(" - CBC (COIN-OR Branch and Cut)")
409+ print(" - SCIP (Solving Constraint Integer Programs)")
410+ print(" - CP-SAT (Constraint Programming - SAT)")
411+ print("")
412+
413+ # Test GLOP solver
414+ solver = pywraplp.Solver.CreateSolver("GLOP")
415+ if solver:
416+ print("✓ GLOP solver working correctly!")
417+ else:
418+ print("✗ Failed to create GLOP solver")
419+ sys.exit(1)
420+
421+ # Test CP-SAT solver
422+ model = cp_model.CpModel()
423+ sat_solver = cp_model.CpSolver()
424+ print("✓ CP-SAT solver working correctly!")
425+ print("")
426+ print("OR-Tools is ready to use! 🎉")
427+
428+ except ImportError as e:
429+ print("✗ OR-Tools not found:", str(e))
430+ print("")
431+ print("This should not happen - OR-Tools is pre-installed in the image.")
432+ sys.exit(1)
433+ except Exception as e:
434+ print("✗ Error verifying OR-Tools:", str(e))
435+ sys.exit(1)
436+ EOFPYTHON
437+ EOFSCRIPT
438+
363439 chmod +x $out/setup-gurobi.sh
364440 chmod +x $out/verify-gurobi.sh
441+ chmod +x $out/verify-ortools.sh
365442
366443 # Note: python3 binary is provided by pythonWithPackages in runtimeEnv
367444 # No need to create a symlink here to avoid collision
@@ -389,7 +466,7 @@ finally:
389466 # Set up user environment
390467 echo 'export HOME=/home/python-user' >> /home/python-user/.bashrc
391468 echo 'export PATH=/usr/local/bin:/usr/bin' >> /home/python-user/.bashrc
392- echo 'export PYTHONPATH=/app:/tmp/ .local/lib/python3.12/site-packages' >> /home/python-user/.bashrc
469+ echo 'export PYTHONPATH=/app:/.local/lib/python3.12/site-packages' >> /home/python-user/.bashrc
393470 EOF
394471
395472 chmod +x $out/setup-user.sh
410487 } ;
411488
412489 config = {
413- WorkingDir = "/app " ;
490+ WorkingDir = "/tmp " ;
414491 Env = [
415- "PYTHONPATH=/app:/tmp /.local/lib/python3.12/site-packages"
492+ "PYTHONPATH=/app:/opt/ortools/lib/python3.12/site-packages: /.local/lib/python3.12/site-packages"
416493 "PYTHONUNBUFFERED=1"
417494 "PYTHONDONTWRITEBYTECODE=1"
418495 # Force uv to use system Python
419496 "UV_PYTHON_PREFERENCE=system"
420497 "UV_LINK_MODE=copy"
421- "UV_PYTHON_SITE_PACKAGES=/tmp/ .local/lib/python3.12/site-packages"
498+ "UV_PYTHON_SITE_PACKAGES=/.local/lib/python3.12/site-packages"
422499 "UV_CACHE_DIR=/tmp/.uv_cache"
423500 "UV_PYTHON=${ systemPython } /bin/python3.12"
424501 # Set PATH to include our secure commands - ensure util-linux tools are accessible
0 commit comments