A fork of mtelver's day10 project

Improve Docker build compatibility and add devcontainer

- Use pre-built opam binary instead of make cold for faster builds
- Add --network=host to docker build for firewall compatibility
- Fix cp --update=none to -n for coreutils <9.3 compatibility
- Add devcontainer config with Docker-in-Docker support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+82 -8
+9
.devcontainer/devcontainer-lock.json
··· 1 + { 2 + "features": { 3 + "ghcr.io/devcontainers/features/docker-in-docker:2": { 4 + "version": "2.13.0", 5 + "resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:ac0a882e936ba6275d7f4ed5ebfc09e4ddca8cbcaeaad18b412beacddeb2fa91", 6 + "integrity": "sha256:ac0a882e936ba6275d7f4ed5ebfc09e4ddca8cbcaeaad18b412beacddeb2fa91" 7 + } 8 + } 9 + }
+61
.devcontainer/devcontainer.json
··· 1 + { 2 + "name": "Claude Code OCaml Sandbox", 3 + "image": "ghcr.io/avsm/claude-ocaml-devcontainer:main", 4 + "runArgs": [ 5 + "--cap-add=NET_ADMIN", 6 + "--cap-add=NET_RAW", 7 + "--privileged" 8 + ], 9 + "features": { 10 + "ghcr.io/devcontainers/features/docker-in-docker:2": {} 11 + }, 12 + "customizations": { 13 + "vscode": { 14 + "extensions": [ 15 + "anthropic.claude-code", 16 + "dbaeumer.vscode-eslint", 17 + "esbenp.prettier-vscode", 18 + "eamodio.gitlens", 19 + "ocamllabs.ocaml-platform" 20 + ], 21 + "settings": { 22 + "editor.formatOnSave": true, 23 + "editor.defaultFormatter": "esbenp.prettier-vscode", 24 + "editor.codeActionsOnSave": { 25 + "source.fixAll.eslint": "explicit" 26 + }, 27 + "terminal.integrated.defaultProfile.linux": "zsh", 28 + "terminal.integrated.profiles.linux": { 29 + "bash": { 30 + "path": "bash", 31 + "icon": "terminal-bash" 32 + }, 33 + "zsh": { 34 + "path": "zsh" 35 + } 36 + } 37 + } 38 + } 39 + }, 40 + "remoteUser": "node", 41 + "mounts": [ 42 + "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume", 43 + "source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind", 44 + "source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly", 45 + "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly", 46 + "source=/cache,target=/cache,type=bind", 47 + "source=opam-repository,target=/opam-repository,type=volume" 48 + ], 49 + "containerEnv": { 50 + "NODE_OPTIONS": "--max-old-space-size=4096", 51 + "CLAUDE_CONFIG_DIR": "/home/node/.claude", 52 + "POWERLEVEL9K_DISABLE_GITSTATUS": "true", 53 + "DAY10_CACHE_DIR": "/cache", 54 + "DAY10_OPAM_REPO": "/opam-repository" 55 + }, 56 + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", 57 + "workspaceFolder": "/workspace", 58 + "postCreateCommand": "sudo /usr/local/bin/init-firewall.sh && sudo chown node:node /opam-repository /cache", 59 + "postStartCommand": "[ -d /opam-repository ] || git clone --depth 1 https://github.com/ocaml/opam-repository.git /opam-repository", 60 + "waitFor": "postStartCommand" 61 + }
+10 -6
bin/docker.ml
··· 12 12 | arch -> "linux/" ^ arch 13 13 14 14 let opam ~(config : Config.t) base_image = 15 + let opam_arch = match config.arch with 16 + | "x86_64" | "amd64" -> "x86_64" 17 + | "aarch64" -> "aarch64" 18 + | "armv7l" -> "armhf" 19 + | "i386" | "i486" | "i586" | "i686" -> "i686" 20 + | arch -> arch 21 + in 15 22 from ~platform:(platform config.arch) ~alias:"opam-builder" base_image 16 - @@ run "apt update && apt install -y build-essential git curl libcap-dev sudo" 17 - @@ run "git clone --depth 1 --branch 2.4.1 https://github.com/ocaml/opam.git /tmp/opam" 18 - @@ workdir "/tmp/opam" 19 - @@ run "make cold" 20 - @@ run "make install" 23 + @@ run "apt update && apt install -y curl" 24 + @@ run "curl -fsSL https://github.com/ocaml/opam/releases/download/2.4.1/opam-2.4.1-%s-linux -o /usr/local/bin/opam && chmod +x /usr/local/bin/opam" opam_arch 21 25 22 26 let opam_build ~(config : Config.t) base_image = 23 27 from ~platform:(platform config.arch) ~alias:"opam-build-builder" base_image ··· 54 58 let dockerfile_path = Path.(temp_dir / "Dockerfile") in 55 59 let () = Os.write_to_file dockerfile_path (Dockerfile.string_of_t dockerfile) in 56 60 let tag = Printf.sprintf "day10-%s:%s" config.os_distribution config.os_version in 57 - let build_result = Os.exec ~stdout:build_log ~stderr:build_log [ "docker"; "build"; "-t"; tag; temp_dir ] in 61 + let build_result = Os.exec ~stdout:build_log ~stderr:build_log [ "docker"; "build"; "--network=host"; "-t"; tag; temp_dir ] in 58 62 match build_result with 59 63 | 0 -> 60 64 let rootfs = Path.(temp_dir / "fs") in
+1 -1
bin/linux.ml
··· 188 188 = Os.sudo 189 189 [ 190 190 "cp"; 191 - "--update=none"; 191 + "-n"; 192 192 "--archive"; 193 193 "--no-dereference"; 194 194 "--recursive";
+1 -1
bin/main.ml
··· 432 432 in 433 433 let () = Printf.printf "Importing layers into Docker with tag: %s\n%!" tag in 434 434 let temp_dir = Filename.temp_dir ~temp_dir:config.dir ~perms:0o755 "docker-import-" "" in 435 - let cp s d = [ "cp"; "--update=none"; "--archive"; "--no-dereference"; "--recursive"; "--link"; "--no-target-directory"; s; d ] in 435 + let cp s d = [ "cp"; "-n"; "--archive"; "--no-dereference"; "--recursive"; "--link"; "--no-target-directory"; s; d ] in 436 436 let () = 437 437 List.iter 438 438 (fun hash ->