From 7fb3fbace764d2a1825c3c4d05702745a038e728 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 17:34:38 +0100 Subject: [PATCH 01/13] Initial commit with task details Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/link-foundation/sandbox/issues/37 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9152c89 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/link-foundation/sandbox/issues/37 +Your prepared branch: issue-37-06e880b28d2a +Your prepared working directory: /tmp/gh-issue-solver-1769963677082 + +Proceed. From ed551efc962fa5658fa0b417c2efed9f39535e70 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 17:44:13 +0100 Subject: [PATCH 02/13] Add modular sandbox architecture with per-language install scripts Split the monolithic sandbox into modular components: - ubuntu/24.04/common.sh: Shared functions and utilities - ubuntu/24.04//install.sh: Per-language install scripts - ubuntu/24.04//Dockerfile: Per-language Docker images - ubuntu/24.04/essentials-sandbox/: Minimal image with git identity tools - ubuntu/24.04/full-sandbox/: Complete image with all languages Languages supported as individual modules: js, python, go, rust, java, kotlin, dotnet, r, ruby, php, perl, swift, lean, rocq, cpp, assembly The root Dockerfile remains backward-compatible and continues to build the full sandbox image using the existing install script. CI/CD workflow updated to detect changes in ubuntu/ directory. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/release.yml | 14 +- Dockerfile | 18 +- ubuntu/24.04/assembly/Dockerfile | 26 ++ ubuntu/24.04/assembly/install.sh | 30 ++ ubuntu/24.04/common.sh | 140 ++++++ ubuntu/24.04/cpp/Dockerfile | 26 ++ ubuntu/24.04/cpp/install.sh | 23 + ubuntu/24.04/dotnet/Dockerfile | 25 ++ ubuntu/24.04/dotnet/install.sh | 27 ++ ubuntu/24.04/essentials-sandbox/Dockerfile | 35 ++ ubuntu/24.04/essentials-sandbox/install.sh | 200 +++++++++ ubuntu/24.04/full-sandbox/Dockerfile | 51 +++ ubuntu/24.04/full-sandbox/install.sh | 478 +++++++++++++++++++++ ubuntu/24.04/go/Dockerfile | 29 ++ ubuntu/24.04/go/install.sh | 70 +++ ubuntu/24.04/java/Dockerfile | 27 ++ ubuntu/24.04/java/install.sh | 62 +++ ubuntu/24.04/js/Dockerfile | 38 ++ ubuntu/24.04/js/install.sh | 101 +++++ ubuntu/24.04/kotlin/Dockerfile | 27 ++ ubuntu/24.04/kotlin/install.sh | 54 +++ ubuntu/24.04/lean/Dockerfile | 27 ++ ubuntu/24.04/lean/install.sh | 36 ++ ubuntu/24.04/perl/Dockerfile | 27 ++ ubuntu/24.04/perl/install.sh | 66 +++ ubuntu/24.04/php/Dockerfile | 29 ++ ubuntu/24.04/php/install.sh | 90 ++++ ubuntu/24.04/python/Dockerfile | 32 ++ ubuntu/24.04/python/install.sh | 84 ++++ ubuntu/24.04/r/Dockerfile | 25 ++ ubuntu/24.04/r/install.sh | 27 ++ ubuntu/24.04/rocq/Dockerfile | 30 ++ ubuntu/24.04/rocq/install.sh | 82 ++++ ubuntu/24.04/ruby/Dockerfile | 28 ++ ubuntu/24.04/ruby/install.sh | 64 +++ ubuntu/24.04/rust/Dockerfile | 28 ++ ubuntu/24.04/rust/install.sh | 29 ++ ubuntu/24.04/swift/Dockerfile | 27 ++ ubuntu/24.04/swift/install.sh | 79 ++++ 39 files changed, 2306 insertions(+), 5 deletions(-) create mode 100644 ubuntu/24.04/assembly/Dockerfile create mode 100644 ubuntu/24.04/assembly/install.sh create mode 100644 ubuntu/24.04/common.sh create mode 100644 ubuntu/24.04/cpp/Dockerfile create mode 100644 ubuntu/24.04/cpp/install.sh create mode 100644 ubuntu/24.04/dotnet/Dockerfile create mode 100644 ubuntu/24.04/dotnet/install.sh create mode 100644 ubuntu/24.04/essentials-sandbox/Dockerfile create mode 100644 ubuntu/24.04/essentials-sandbox/install.sh create mode 100644 ubuntu/24.04/full-sandbox/Dockerfile create mode 100644 ubuntu/24.04/full-sandbox/install.sh create mode 100644 ubuntu/24.04/go/Dockerfile create mode 100644 ubuntu/24.04/go/install.sh create mode 100644 ubuntu/24.04/java/Dockerfile create mode 100644 ubuntu/24.04/java/install.sh create mode 100644 ubuntu/24.04/js/Dockerfile create mode 100644 ubuntu/24.04/js/install.sh create mode 100644 ubuntu/24.04/kotlin/Dockerfile create mode 100644 ubuntu/24.04/kotlin/install.sh create mode 100644 ubuntu/24.04/lean/Dockerfile create mode 100644 ubuntu/24.04/lean/install.sh create mode 100644 ubuntu/24.04/perl/Dockerfile create mode 100644 ubuntu/24.04/perl/install.sh create mode 100644 ubuntu/24.04/php/Dockerfile create mode 100644 ubuntu/24.04/php/install.sh create mode 100644 ubuntu/24.04/python/Dockerfile create mode 100644 ubuntu/24.04/python/install.sh create mode 100644 ubuntu/24.04/r/Dockerfile create mode 100644 ubuntu/24.04/r/install.sh create mode 100644 ubuntu/24.04/rocq/Dockerfile create mode 100644 ubuntu/24.04/rocq/install.sh create mode 100644 ubuntu/24.04/ruby/Dockerfile create mode 100644 ubuntu/24.04/ruby/install.sh create mode 100644 ubuntu/24.04/rust/Dockerfile create mode 100644 ubuntu/24.04/rust/install.sh create mode 100644 ubuntu/24.04/swift/Dockerfile create mode 100644 ubuntu/24.04/swift/install.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6381ed2..beb0894 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: paths: - 'Dockerfile' - 'scripts/**' + - 'ubuntu/**' - '.github/workflows/release.yml' - '.changeset/**' - 'VERSION' @@ -93,7 +94,7 @@ jobs: # Check if there are code changes (Dockerfile, scripts, etc.) git fetch origin "$GITHUB_BASE_REF" 2>/dev/null || true - CODE_CHANGES=$(git diff --name-only "origin/${GITHUB_BASE_REF}...HEAD" | grep -E '^(Dockerfile|scripts/|\.github/workflows/)' || true) + CODE_CHANGES=$(git diff --name-only "origin/${GITHUB_BASE_REF}...HEAD" | grep -E '^(Dockerfile|scripts/|ubuntu/|\.github/workflows/)' || true) if [ -z "$CODE_CHANGES" ]; then echo "No code changes detected, changeset not required" @@ -210,6 +211,7 @@ jobs: outputs: docker-changed: ${{ steps.changes.outputs.docker }} scripts-changed: ${{ steps.changes.outputs.scripts }} + ubuntu-changed: ${{ steps.changes.outputs.ubuntu }} workflow-changed: ${{ steps.changes.outputs.workflow }} version-changed: ${{ steps.changes.outputs.version }} should-build: ${{ steps.should-build.outputs.result }} @@ -264,6 +266,13 @@ jobs: echo "scripts=false" >> $GITHUB_OUTPUT fi + # Check for ubuntu/ modular scripts changes + if echo "$CHANGED_FILES" | grep -qE '^ubuntu/'; then + echo "ubuntu=true" >> $GITHUB_OUTPUT + else + echo "ubuntu=false" >> $GITHUB_OUTPUT + fi + # Check for workflow changes if echo "$CHANGED_FILES" | grep -qE '^\.github/workflows/'; then echo "workflow=true" >> $GITHUB_OUTPUT @@ -301,6 +310,9 @@ jobs: elif [ "${{ steps.changes.outputs.scripts }}" = "true" ]; then echo "result=true" >> $GITHUB_OUTPUT echo "Build triggered by: scripts changes" + elif [ "${{ steps.changes.outputs.ubuntu }}" = "true" ]; then + echo "result=true" >> $GITHUB_OUTPUT + echo "Build triggered by: ubuntu modular scripts changes" elif [ "${{ steps.changes.outputs.workflow }}" = "true" ]; then echo "result=true" >> $GITHUB_OUTPUT echo "Build triggered by: workflow changes" diff --git a/Dockerfile b/Dockerfile index 574b887..f35d065 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,12 @@ FROM ubuntu:24.04 -# Sandbox environment Docker image +# Full Sandbox environment Docker image # Contains common language runtimes without any AI-specific tools # This image is meant to be used as a base for other projects that need language runtimes. +# +# This is the "full-sandbox" image (konard/sandbox or konard/sandbox-full). +# For a lighter image with just essentials, see ubuntu/24.04/essentials-sandbox/Dockerfile. +# For individual language images, see ubuntu/24.04//Dockerfile. # Set non-interactive frontend for apt ENV DEBIAN_FRONTEND=noninteractive @@ -10,14 +14,20 @@ ENV DEBIAN_FRONTEND=noninteractive # Set working directory WORKDIR /workspace -# Copy the installation script +# Copy the modular installation scripts +COPY ubuntu/24.04/common.sh /tmp/sandbox-scripts/common.sh +COPY ubuntu/24.04/essentials-sandbox/install.sh /tmp/sandbox-scripts/essentials-sandbox/install.sh +COPY ubuntu/24.04/full-sandbox/install.sh /tmp/sandbox-scripts/full-sandbox/install.sh + +# Copy the legacy installation script (used by full-sandbox/install.sh) COPY scripts/ubuntu-24-server-install.sh /tmp/ubuntu-24-server-install.sh -# Make the script executable and run it +# Make scripts executable and run the full installation # Pass DOCKER_BUILD=1 environment variable to indicate Docker build environment RUN chmod +x /tmp/ubuntu-24-server-install.sh && \ DOCKER_BUILD=1 bash /tmp/ubuntu-24-server-install.sh && \ - rm -f /tmp/ubuntu-24-server-install.sh + rm -f /tmp/ubuntu-24-server-install.sh && \ + rm -rf /tmp/sandbox-scripts # Copy entrypoint script for proper environment initialization COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh diff --git a/ubuntu/24.04/assembly/Dockerfile b/ubuntu/24.04/assembly/Dockerfile new file mode 100644 index 0000000..13055d2 --- /dev/null +++ b/ubuntu/24.04/assembly/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:24.04 + +# Assembly sandbox: NASM, FASM (x86_64 only), GNU Assembler, LLVM MC + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +USER sandbox +WORKDIR /home/sandbox + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/assembly/install.sh b/ubuntu/24.04/assembly/install.sh new file mode 100644 index 0000000..1b4c250 --- /dev/null +++ b/ubuntu/24.04/assembly/install.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Assembly tools installation (NASM, FASM) +# Usage: curl -fsSL | bash OR bash install.sh +# Note: FASM is only available on x86_64 architecture + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_note() { echo "[i] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Assembly Tools" + +ARCH=$(uname -m) +if [ "$ARCH" = "x86_64" ]; then + maybe_sudo apt install -y nasm fasm + log_success "Assembly tools installed (NASM + FASM)" +else + maybe_sudo apt install -y nasm + log_success "Assembly tools installed (NASM only - FASM not available for $ARCH)" +fi + +log_success "Assembly tools installation complete" diff --git a/ubuntu/24.04/common.sh b/ubuntu/24.04/common.sh new file mode 100644 index 0000000..13c0856 --- /dev/null +++ b/ubuntu/24.04/common.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# Common functions and utilities shared across all sandbox install scripts +# Source this file at the top of each install.sh: +# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# source "$SCRIPT_DIR/../common.sh" + +set -euo pipefail + +# Color codes for enhanced output (disabled in non-TTY) +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + CYAN='\033[0;36m' + NC='\033[0m' +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + CYAN='' + NC='' +fi + +# Enhanced logging functions +log_info() { echo -e "${BLUE}[*]${NC} $1"; } +log_success() { echo -e "${GREEN}[✓]${NC} $1"; } +log_warning() { echo -e "${YELLOW}[!]${NC} $1"; } +log_error() { echo -e "${RED}[✗]${NC} $1"; } +log_note() { echo -e "${CYAN}[i]${NC} $1"; } +log_step() { echo -e "\n${GREEN}==>${NC} ${BLUE}$1${NC}\n"; } + +# Verification helper +verify_command() { + local tool_name="$1" + local command_name="${2:-$1}" + local version_flag="${3:---version}" + + if command -v "$command_name" &>/dev/null; then + local version=$("$command_name" $version_flag 2>/dev/null | head -n1 || echo "installed") + log_success "$tool_name: $version" + return 0 + else + log_warning "$tool_name: not found in PATH" + return 1 + fi +} + +# Check if a command exists (silent) +command_exists() { + command -v "$1" &>/dev/null +} + +# Run command with sudo only if not root and sudo is available +maybe_sudo() { + if [ "$EUID" -eq 0 ]; then + "$@" + elif command_exists sudo; then + sudo "$@" + else + "$@" + fi +} + +# Safe apt update +apt_update_safe() { + log_info "Updating apt sources..." + for f in /etc/apt/sources.list.d/*.list; do + if [ -f "$f" ] && ! grep -Eq "^deb " "$f"; then + log_warning "Removing malformed apt source: $f" + maybe_sudo rm -f "$f" + fi + done + maybe_sudo apt update -y || true +} + +# Cleanup apt cache +apt_cleanup() { + log_info "Cleaning up apt cache and temporary files..." + maybe_sudo apt-get clean + maybe_sudo apt-get autoclean + maybe_sudo apt-get autoremove -y + maybe_sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + log_success "Cleanup completed" +} + +# Cleanup duplicate APT sources +cleanup_duplicate_apt_sources() { + log_info "Checking for duplicate APT sources..." + local duplicates_found=false + + if [ -f /etc/apt/sources.list.d/microsoft-edge.list ] && \ + [ -f /etc/apt/sources.list.d/microsoft-edge-stable.list ]; then + log_info "Found duplicate Microsoft Edge APT sources" + maybe_sudo rm -f /etc/apt/sources.list.d/microsoft-edge.list + duplicates_found=true + fi + + if [ -f /etc/apt/sources.list.d/google-chrome.list ] && \ + [ -f /etc/apt/sources.list.d/google-chrome-stable.list ]; then + log_info "Found duplicate Google Chrome APT sources" + maybe_sudo rm -f /etc/apt/sources.list.d/google-chrome-stable.list + duplicates_found=true + fi + + if [ "$duplicates_found" = true ]; then + log_success "Duplicate APT sources cleaned up" + else + log_success "No duplicate APT sources found" + fi +} + +# Create sandbox user if missing +ensure_sandbox_user() { + if id "sandbox" &>/dev/null; then + log_info "sandbox user already exists." + else + log_info "Creating sandbox user..." + useradd -m -s /bin/bash sandbox 2>/dev/null || { + log_warning "User creation with useradd failed, trying adduser..." + adduser --disabled-password --gecos "" sandbox + } + passwd -d sandbox 2>/dev/null || log_note "Could not remove password requirement" + usermod -aG sudo sandbox 2>/dev/null || log_note "Could not add to sudo group" + log_success "sandbox user created and configured" + fi +} + +# Detect Docker environment +is_docker_build() { + if [ "${DOCKER_BUILD:-}" = "1" ]; then + return 0 + elif [ -f /.dockerenv ]; then + return 0 + elif grep -qE 'docker|buildkit|containerd' /proc/1/cgroup 2>/dev/null; then + return 0 + fi + return 1 +} diff --git a/ubuntu/24.04/cpp/Dockerfile b/ubuntu/24.04/cpp/Dockerfile new file mode 100644 index 0000000..13f8632 --- /dev/null +++ b/ubuntu/24.04/cpp/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:24.04 + +# C/C++ sandbox: build-essential, CMake, Clang, LLVM, LLD + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +USER sandbox +WORKDIR /home/sandbox + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/cpp/install.sh b/ubuntu/24.04/cpp/install.sh new file mode 100644 index 0000000..4a75d58 --- /dev/null +++ b/ubuntu/24.04/cpp/install.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# C/C++ development tools installation (CMake, Clang, LLVM, LLD) +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing C/C++ Development Tools" + +log_info "Installing build-essential, CMake, Clang/LLVM, LLD..." +maybe_sudo apt install -y build-essential cmake clang llvm lld +log_success "C/C++ development tools installed" + +log_success "C/C++ tools installation complete" diff --git a/ubuntu/24.04/dotnet/Dockerfile b/ubuntu/24.04/dotnet/Dockerfile new file mode 100644 index 0000000..633ebdb --- /dev/null +++ b/ubuntu/24.04/dotnet/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:24.04 + +# .NET sandbox: .NET SDK 8.0 + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/dotnet/install.sh b/ubuntu/24.04/dotnet/install.sh new file mode 100644 index 0000000..02a4b66 --- /dev/null +++ b/ubuntu/24.04/dotnet/install.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# .NET SDK 8.0 installation +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing .NET SDK 8.0" + +if ! command_exists dotnet; then + log_info "Installing .NET SDK 8.0..." + maybe_sudo apt install -y dotnet-sdk-8.0 + log_success ".NET SDK 8.0 installed" +else + log_info ".NET SDK already installed." +fi + +log_success ".NET installation complete" diff --git a/ubuntu/24.04/essentials-sandbox/Dockerfile b/ubuntu/24.04/essentials-sandbox/Dockerfile new file mode 100644 index 0000000..031f866 --- /dev/null +++ b/ubuntu/24.04/essentials-sandbox/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:24.04 + +# Essentials Sandbox Docker image +# Contains minimal tooling for gh-setup-git-identity and glab-setup-git-identity +# Includes: git, gh, glab, bun, deno, nvm/node, npm +# Published as: konard/sandbox-essentials + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +# Copy and run the essentials installation script +COPY install.sh /tmp/install.sh +COPY ../common.sh /tmp/common.sh + +RUN chmod +x /tmp/install.sh && \ + DOCKER_BUILD=1 bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh + +# Copy entrypoint script +COPY ../../scripts/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +USER sandbox +WORKDIR /home/sandbox + +# Environment variables for JS runtimes +ENV NVM_DIR="/home/sandbox/.nvm" +ENV BUN_INSTALL="/home/sandbox/.bun" +ENV DENO_INSTALL="/home/sandbox/.deno" +ENV PATH="/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/essentials-sandbox/install.sh b/ubuntu/24.04/essentials-sandbox/install.sh new file mode 100644 index 0000000..71a467d --- /dev/null +++ b/ubuntu/24.04/essentials-sandbox/install.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Essentials Sandbox Installation Script +# Installs minimal tooling required for gh-setup-git-identity and glab-setup-git-identity +# Components: system essentials, GitHub CLI, GitLab CLI, JavaScript (Bun, NVM/Node, Deno) +# +# This is the base image that full-sandbox builds upon. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + # Inline fallback logging + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_error() { echo "[✗] $1"; } + log_note() { echo "[i] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Essentials Sandbox" + +# --- Pre-flight Checks --- +log_step "Running pre-flight checks" + +if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then + log_error "This script requires sudo access." + exit 1 +fi + +if [ -f /etc/os-release ]; then + source /etc/os-release + if [[ "${ID:-}" == "ubuntu" ]]; then + log_success "Ubuntu ${VERSION_ID:-unknown} detected" + else + log_warning "This script is designed for Ubuntu. Detected: ${ID:-unknown}" + fi +fi + +log_success "Pre-flight checks passed" + +# --- Create sandbox user --- +log_step "Setting up sandbox user" +if id "sandbox" &>/dev/null; then + log_info "sandbox user already exists." +else + log_info "Creating sandbox user..." + useradd -m -s /bin/bash sandbox 2>/dev/null || adduser --disabled-password --gecos "" sandbox + passwd -d sandbox 2>/dev/null || true + usermod -aG sudo sandbox 2>/dev/null || true + log_success "sandbox user created" +fi + +# --- System prerequisites --- +log_step "Installing system prerequisites" + +# Clean up duplicate sources +for pair in "microsoft-edge:microsoft-edge-stable" "google-chrome:google-chrome-stable"; do + f1="/etc/apt/sources.list.d/${pair%%:*}.list" + f2="/etc/apt/sources.list.d/${pair##*:}.list" + if [ -f "$f1" ] && [ -f "$f2" ]; then + maybe_sudo rm -f "$f1" + fi +done + +maybe_sudo apt update -y || true +maybe_sudo apt install -y wget curl unzip zip git sudo ca-certificates gnupg build-essential expect screen +log_success "System prerequisites installed" + +# --- GitHub CLI --- +log_step "Installing GitHub CLI" +if ! command_exists gh; then + maybe_sudo mkdir -p -m 755 /etc/apt/keyrings + out=$(mktemp) + wget -nv -O"$out" https://cli.github.com/packages/githubcli-archive-keyring.gpg + cat "$out" | maybe_sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null + maybe_sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg + rm -f "$out" + + maybe_sudo mkdir -p -m 755 /etc/apt/sources.list.d + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + | maybe_sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + + maybe_sudo apt update -y + maybe_sudo apt install -y gh + log_success "GitHub CLI installed" +else + log_success "GitHub CLI already installed" +fi + +# --- GitLab CLI --- +log_step "Installing GitLab CLI" +if ! command_exists glab; then + maybe_sudo apt install -y glab + log_success "GitLab CLI installed" +else + log_success "GitLab CLI already installed" +fi + +# --- JavaScript runtimes (as sandbox user) --- +log_step "Installing JavaScript runtimes" + +cat > /tmp/essentials-js-setup.sh <<'EOF_JS' +#!/usr/bin/env bash +set -euo pipefail + +log_info() { echo "[*] $1"; } +log_success() { echo "[✓] $1"; } +command_exists() { command -v "$1" &>/dev/null; } + +# Bun +if ! command_exists bun; then + log_info "Installing Bun..." + curl -fsSL https://bun.sh/install | bash +fi +export BUN_INSTALL="$HOME/.bun" +export PATH="$BUN_INSTALL/bin:$PATH" + +# gh-setup-git-identity +if command_exists bun; then + if ! command_exists gh-setup-git-identity; then + log_info "Installing gh-setup-git-identity..." + bun install -g gh-setup-git-identity + fi +fi + +# glab-setup-git-identity +if command_exists bun; then + if ! command_exists glab-setup-git-identity; then + log_info "Installing glab-setup-git-identity..." + bun install -g glab-setup-git-identity + fi +fi + +# Deno +if ! command_exists deno; then + log_info "Installing Deno..." + curl -fsSL https://deno.land/install.sh | sh -s -- -y + export DENO_INSTALL="$HOME/.deno" + export PATH="$DENO_INSTALL/bin:$PATH" + if ! grep -q 'DENO_INSTALL' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Deno configuration' + echo 'export DENO_INSTALL="$HOME/.deno"' + echo 'export PATH="$DENO_INSTALL/bin:$PATH"' + } >> "$HOME/.bashrc" + fi +fi + +# NVM + Node.js +if [ ! -d "$HOME/.nvm" ]; then + log_info "Installing NVM..." + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash +fi + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +if ! nvm ls 20 2>/dev/null | grep -q 'v20'; then + log_info "Installing Node.js 20..." + nvm install 20 +fi +nvm use 20 + +log_info "Updating npm..." +npm install -g npm@latest --no-fund --silent + +# Git setup +if gh auth status &>/dev/null; then + log_info "Configuring Git with GitHub identity..." + git config --global user.name "$(gh api user --jq .login)" + git config --global user.email "$(gh api user/emails --jq '.[] | select(.primary==true).email')" + gh auth setup-git +fi + +log_success "Essentials sandbox user setup complete" +EOF_JS + +chmod +x /tmp/essentials-js-setup.sh +if [ "$EUID" -eq 0 ]; then + su - sandbox -c "bash /tmp/essentials-js-setup.sh" +else + sudo -i -u sandbox bash /tmp/essentials-js-setup.sh +fi +rm -f /tmp/essentials-js-setup.sh + +# --- Cleanup --- +log_step "Cleaning up" +maybe_sudo apt-get clean +maybe_sudo apt-get autoclean +maybe_sudo apt-get autoremove -y +maybe_sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +log_step "Essentials Sandbox setup complete!" +log_success "Installed: git, gh, glab, bun, deno, nvm/node, gh-setup-git-identity, glab-setup-git-identity" diff --git a/ubuntu/24.04/full-sandbox/Dockerfile b/ubuntu/24.04/full-sandbox/Dockerfile new file mode 100644 index 0000000..aca36de --- /dev/null +++ b/ubuntu/24.04/full-sandbox/Dockerfile @@ -0,0 +1,51 @@ +FROM ubuntu:24.04 + +# Full Sandbox Docker image +# Contains all language runtimes and development tools +# Built on top of essentials-sandbox install script +# Published as: konard/sandbox (or konard/sandbox-full) + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +# Copy the entire ubuntu/24.04 directory for access to all install scripts +COPY . /tmp/sandbox-scripts/ + +# Copy entrypoint script +COPY ../../scripts/entrypoint.sh /usr/local/bin/entrypoint.sh + +# Make scripts executable and run the full installation +RUN chmod +x /tmp/sandbox-scripts/full-sandbox/install.sh && \ + chmod +x /tmp/sandbox-scripts/common.sh && \ + chmod +x /tmp/sandbox-scripts/essentials-sandbox/install.sh && \ + chmod +x /usr/local/bin/entrypoint.sh && \ + DOCKER_BUILD=1 bash /tmp/sandbox-scripts/full-sandbox/install.sh && \ + rm -rf /tmp/sandbox-scripts + +USER sandbox +WORKDIR /home/sandbox + +# Environment variables for all tools +ENV NVM_DIR="/home/sandbox/.nvm" +ENV PYENV_ROOT="/home/sandbox/.pyenv" +ENV BUN_INSTALL="/home/sandbox/.bun" +ENV DENO_INSTALL="/home/sandbox/.deno" +ENV CARGO_HOME="/home/sandbox/.cargo" +ENV GOROOT="/home/sandbox/.go" +ENV GOPATH="/home/sandbox/.go/path" +ENV SDKMAN_DIR="/home/sandbox/.sdkman" +ENV PERLBREW_ROOT="/home/sandbox/.perl5" +ENV RBENV_ROOT="/home/sandbox/.rbenv" + +# PATH for tools that don't need special initialization +ENV PATH="/home/sandbox/.rbenv/bin:/home/sandbox/.rbenv/shims:/home/sandbox/.swift/usr/bin:/home/sandbox/.elan/bin:/home/sandbox/.opam/default/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/sandbox/.cargo/bin:/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:/home/sandbox/.go/bin:/home/sandbox/.go/path/bin:/home/linuxbrew/.linuxbrew/bin:${PATH}" + +# Opam environment variables +ENV OPAM_SWITCH_PREFIX="/home/sandbox/.opam/default" +ENV CAML_LD_LIBRARY_PATH="/home/sandbox/.opam/default/lib/stublibs:/home/sandbox/.opam/default/lib/ocaml/stublibs:/home/sandbox/.opam/default/lib/ocaml" +ENV OCAML_TOPLEVEL_PATH="/home/sandbox/.opam/default/lib/toplevel" + +SHELL ["/bin/bash", "-c"] + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/full-sandbox/install.sh b/ubuntu/24.04/full-sandbox/install.sh new file mode 100644 index 0000000..3926503 --- /dev/null +++ b/ubuntu/24.04/full-sandbox/install.sh @@ -0,0 +1,478 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Full Sandbox Installation Script +# Installs all supported language runtimes and development tools. +# This is the complete sandbox - equivalent to the original ubuntu-24-server-install.sh +# +# Architecture: Runs the essentials-sandbox install first, then adds all languages. +# Each language installer is a standalone script under ubuntu/24.04//install.sh. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + # Inline fallback logging + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_error() { echo "[✗] $1"; } + log_note() { echo "[i] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Full Sandbox" + +# --- Step 1: Run essentials-sandbox install --- +log_step "Phase 1: Installing essentials sandbox" +if [ -f "$SCRIPT_DIR/../essentials-sandbox/install.sh" ]; then + bash "$SCRIPT_DIR/../essentials-sandbox/install.sh" +else + log_error "essentials-sandbox/install.sh not found at $SCRIPT_DIR/../essentials-sandbox/install.sh" + exit 1 +fi + +# --- Step 2: Install additional system packages --- +log_step "Phase 2: Installing additional system packages" + +# Re-source common.sh after essentials may have modified apt +maybe_sudo apt update -y || true + +# .NET SDK +log_info "Installing .NET SDK 8.0..." +maybe_sudo apt install -y dotnet-sdk-8.0 +log_success ".NET SDK installed" + +# C/C++ tools +log_info "Installing C/C++ development tools..." +maybe_sudo apt install -y cmake clang llvm lld +log_success "C/C++ tools installed" + +# Assembly tools +log_info "Installing Assembly tools..." +ARCH=$(uname -m) +if [ "$ARCH" = "x86_64" ]; then + maybe_sudo apt install -y nasm fasm + log_success "Assembly tools installed (NASM + FASM)" +else + maybe_sudo apt install -y nasm + log_success "Assembly tools installed (NASM only)" +fi + +# R language +log_info "Installing R statistical language..." +maybe_sudo apt install -y r-base +log_success "R language installed" + +# Ruby build dependencies +log_info "Installing Ruby build dependencies..." +maybe_sudo apt install -y libyaml-dev +log_success "Ruby build dependencies installed" + +# Python build dependencies +log_info "Installing Python build dependencies..." +maybe_sudo apt install -y \ + libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ + libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \ + libffi-dev liblzma-dev +log_success "Python build dependencies installed" + +# --- Step 3: Prepare Homebrew directory --- +log_step "Phase 3: Preparing Homebrew directory" +if [ ! -d /home/linuxbrew/.linuxbrew ]; then + maybe_sudo mkdir -p /home/linuxbrew/.linuxbrew + if id "sandbox" &>/dev/null; then + maybe_sudo chown -R sandbox:sandbox /home/linuxbrew + fi +else + if id "sandbox" &>/dev/null; then + maybe_sudo chown -R sandbox:sandbox /home/linuxbrew + fi +fi + +# --- Step 4: Install all language runtimes as sandbox user --- +log_step "Phase 4: Installing language runtimes as sandbox user" + +cat > /tmp/full-sandbox-user-setup.sh <<'EOF_FULL_SETUP' +#!/usr/bin/env bash +set -euo pipefail + +log_info() { echo "[*] $1"; } +log_success() { echo "[✓] $1"; } +log_warning() { echo "[!] $1"; } +log_note() { echo "[i] $1"; } +log_step() { echo "==> $1"; } +command_exists() { command -v "$1" &>/dev/null; } + +# Ensure JS tools are available (installed by essentials) +export BUN_INSTALL="$HOME/.bun" +export DENO_INSTALL="$HOME/.deno" +export NVM_DIR="$HOME/.nvm" +export PATH="$BUN_INSTALL/bin:$DENO_INSTALL/bin:$PATH" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +# --- Python (Pyenv) --- +log_step "Installing Python" +if [ ! -d "$HOME/.pyenv" ]; then + curl https://pyenv.run | bash + if ! grep -q 'pyenv init' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Pyenv configuration' + echo 'export PYENV_ROOT="$HOME/.pyenv"' + echo 'export PATH="$PYENV_ROOT/bin:$PATH"' + echo 'eval "$(pyenv init --path)"' + echo 'eval "$(pyenv init -)"' + } >> "$HOME/.bashrc" + fi +fi + +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +if command -v pyenv >/dev/null 2>&1; then + eval "$(pyenv init --path)" + eval "$(pyenv init -)" + LATEST_PYTHON=$(pyenv install --list | grep -E '^\s*[0-9]+\.[0-9]+\.[0-9]+$' | tail -1 | tr -d '[:space:]') + if [ -n "$LATEST_PYTHON" ]; then + if ! pyenv versions --bare | grep -q "^${LATEST_PYTHON}$"; then + pyenv install "$LATEST_PYTHON" + fi + pyenv global "$LATEST_PYTHON" + fi +fi + +# --- Go --- +log_step "Installing Go" +if [ ! -d "$HOME/.go" ] && [ ! -d "/usr/local/go" ]; then + ARCH=$(uname -m) + case "$ARCH" in + x86_64) GO_ARCH="amd64" ;; + aarch64) GO_ARCH="arm64" ;; + *) GO_ARCH="" ;; + esac + if [ -n "$GO_ARCH" ]; then + GO_VERSION=$(curl -sL 'https://go.dev/VERSION?m=text' | head -n1) + if [ -n "$GO_VERSION" ]; then + TEMP_DIR=$(mktemp -d) + curl -sL "https://go.dev/dl/${GO_VERSION}.linux-${GO_ARCH}.tar.gz" -o "$TEMP_DIR/go.tar.gz" + mkdir -p "$HOME/.go" + tar -xzf "$TEMP_DIR/go.tar.gz" -C "$HOME/.go" --strip-components=1 + rm -rf "$TEMP_DIR" + if ! grep -q 'GOROOT.*\.go' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Go configuration' + echo 'export GOROOT="$HOME/.go"' + echo 'export GOPATH="$HOME/.go/path"' + echo 'export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"' + } >> "$HOME/.bashrc" + fi + export GOROOT="$HOME/.go" + export GOPATH="$HOME/.go/path" + export PATH="$GOROOT/bin:$GOPATH/bin:$PATH" + mkdir -p "$GOPATH" + fi + fi +fi + +# --- Rust --- +log_step "Installing Rust" +if [ ! -d "$HOME/.cargo" ]; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + [ -f "$HOME/.cargo/env" ] && \. "$HOME/.cargo/env" +fi + +# --- Java (SDKMAN) --- +log_step "Installing Java" +if [ ! -d "$HOME/.sdkman" ]; then + curl -s "https://get.sdkman.io?rcupdate=false&ci=true" | bash + if ! grep -q 'sdkman-init.sh' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# SDKMAN configuration' + echo 'export SDKMAN_DIR="$HOME/.sdkman"' + echo '[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"' + } >> "$HOME/.bashrc" + fi +fi + +export SDKMAN_DIR="$HOME/.sdkman" +if [ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then + set +u + source "$SDKMAN_DIR/bin/sdkman-init.sh" + set -u + + if ! sdk list java 2>/dev/null | grep -q "21.*tem.*installed"; then + set +u + sdk install java 21-tem < /dev/null || sdk install java 21-open < /dev/null || true + set -u + fi +fi + +# --- Kotlin (SDKMAN) --- +log_step "Installing Kotlin" +export SDKMAN_DIR="$HOME/.sdkman" +if [ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then + set +u + source "$SDKMAN_DIR/bin/sdkman-init.sh" + set -u + if ! command_exists kotlin; then + set +u + sdk install kotlin < /dev/null || true + set -u + fi +fi + +# --- Lean (elan) --- +log_step "Installing Lean" +if [ ! -d "$HOME/.elan" ]; then + curl https://elan.lean-lang.org/elan-init.sh -sSf | sh -s -- -y --default-toolchain stable + [ -f "$HOME/.elan/env" ] && \. "$HOME/.elan/env" + if ! grep -q 'elan' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Lean (elan) configuration' + echo 'export PATH="$HOME/.elan/bin:$PATH"' + } >> "$HOME/.bashrc" + fi +fi + +# --- Rocq/Coq (Opam) --- +log_step "Installing Rocq/Coq" +if ! command_exists opam; then + sudo apt install -y bubblewrap || true + bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --no-backup" <<< "y" || { + sudo apt install -y opam || true + } +fi + +if command_exists opam; then + if [ ! -d "$HOME/.opam" ]; then + opam init --disable-sandboxing --auto-setup -y || true + fi + eval "$(opam env --switch=default 2>/dev/null)" || true + + ROCQ_ACCESSIBLE=false + if command -v rocq &>/dev/null && rocq -v &>/dev/null; then ROCQ_ACCESSIBLE=true; fi + if command -v rocqc &>/dev/null; then ROCQ_ACCESSIBLE=true; fi + if command -v coqc &>/dev/null; then ROCQ_ACCESSIBLE=true; fi + + if [ "$ROCQ_ACCESSIBLE" = false ]; then + opam repo add rocq-released https://rocq-prover.org/opam/released 2>/dev/null || true + opam update 2>/dev/null || true + opam pin add rocq-prover --yes 2>/dev/null || opam install rocq-prover -y 2>/dev/null || opam install coq -y || true + eval "$(opam env --switch=default 2>/dev/null)" || true + fi + + if ! grep -q 'opam env' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Opam (OCaml/Rocq) configuration' + echo 'test -r $HOME/.opam/opam-init/init.sh && . $HOME/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true' + } >> "$HOME/.bashrc" + fi +fi + +# --- Homebrew + PHP --- +log_step "Installing Homebrew + PHP" +if ! command_exists brew; then + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 2>&1 || true + + if [[ -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + elif [[ -x "$HOME/.linuxbrew/bin/brew" ]]; then + eval "$("$HOME/.linuxbrew/bin/brew" shellenv)" + fi + + BREW_PREFIX=$(brew --prefix 2>/dev/null || echo "/home/linuxbrew/.linuxbrew") + if ! grep -q "brew shellenv" "$HOME/.profile" 2>/dev/null; then + echo "eval \"\$($BREW_PREFIX/bin/brew shellenv)\"" >> "$HOME/.profile" + fi + if ! grep -q "brew shellenv" "$HOME/.bashrc" 2>/dev/null; then + echo "eval \"\$($BREW_PREFIX/bin/brew shellenv)\"" >> "$HOME/.bashrc" + fi +else + eval "$(brew shellenv 2>/dev/null)" || true +fi + +if command_exists brew; then + if ! brew list --formula 2>/dev/null | grep -q "^php@"; then + if ! brew tap | grep -q "shivammathur/php"; then + brew tap shivammathur/php || true + fi + if brew tap | grep -q "shivammathur/php"; then + export HOMEBREW_NO_ANALYTICS=1 + export HOMEBREW_NO_AUTO_UPDATE=1 + brew install shivammathur/php/php@8.3 || true + if brew list --formula 2>/dev/null | grep -q "^php@8.3$"; then + brew link --overwrite --force shivammathur/php/php@8.3 2>&1 | grep -v "Warning" || true + BREW_PREFIX=$(brew --prefix 2>/dev/null || echo "") + if [[ -n "$BREW_PREFIX" && -d "$BREW_PREFIX/opt/php@8.3" ]]; then + export PATH="$BREW_PREFIX/opt/php@8.3/bin:$BREW_PREFIX/opt/php@8.3/sbin:$PATH" + if ! grep -q "php@8.3/bin" "$HOME/.bashrc" 2>/dev/null; then + cat >> "$HOME/.bashrc" << 'PHP_PATH_EOF' + +# PHP 8.3 PATH configuration +export PATH="$(brew --prefix)/opt/php@8.3/bin:$(brew --prefix)/opt/php@8.3/sbin:$PATH" +PHP_PATH_EOF + fi + fi + fi + fi + fi +fi + +# --- Perl (Perlbrew) --- +log_step "Installing Perl" +if [ ! -d "$HOME/.perl5" ]; then + export PERLBREW_ROOT="$HOME/.perl5" + curl -L https://install.perlbrew.pl | bash + + if ! grep -q 'perlbrew' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Perlbrew configuration' + echo 'if [ -n "$PS1" ]; then' + echo ' export PERLBREW_ROOT="$HOME/.perl5"' + echo ' [ -f "$PERLBREW_ROOT/etc/bashrc" ] && source "$PERLBREW_ROOT/etc/bashrc"' + echo 'fi' + } >> "$HOME/.bashrc" + fi + + if [ -f "$PERLBREW_ROOT/etc/bashrc" ]; then + sed -i 's/\$1/${1:-}/g' "$PERLBREW_ROOT/etc/bashrc" 2>/dev/null || true + sed -i 's/\$PERLBREW_LIB/${PERLBREW_LIB:-}/g' "$PERLBREW_ROOT/etc/bashrc" 2>/dev/null || true + sed -i 's/\$outsep/${outsep:-}/g' "$PERLBREW_ROOT/etc/bashrc" 2>/dev/null || true + + set +u + source "$PERLBREW_ROOT/etc/bashrc" + set -u + + PERLBREW_OUTPUT=$(perlbrew available 2>&1 || true) + LATEST_PERL=$(echo "$PERLBREW_OUTPUT" | grep -oE 'perl-5\.[0-9]+\.[0-9]+' | head -1 || true) + if [ -n "$LATEST_PERL" ]; then + if ! perlbrew list | grep -q "$LATEST_PERL"; then + perlbrew install "$LATEST_PERL" --notest || true + fi + if perlbrew list | grep -q "$LATEST_PERL"; then + perlbrew switch "$LATEST_PERL" + fi + fi + fi +fi + +# --- Ruby (rbenv) --- +log_step "Installing Ruby" +if [ ! -d "$HOME/.rbenv" ]; then + git clone https://github.com/rbenv/rbenv.git "$HOME/.rbenv" + mkdir -p "$HOME/.rbenv/plugins" + git clone https://github.com/rbenv/ruby-build.git "$HOME/.rbenv/plugins/ruby-build" + + if ! grep -q 'rbenv init' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# rbenv configuration' + echo 'export PATH="$HOME/.rbenv/bin:$PATH"' + echo 'eval "$(rbenv init - bash)"' + } >> "$HOME/.bashrc" + fi + + export PATH="$HOME/.rbenv/bin:$PATH" + eval "$(rbenv init - bash)" + + LATEST_RUBY=$(rbenv install -l 2>/dev/null | grep -E '^\s*3\.[0-9]+\.[0-9]+$' | tail -1 | tr -d '[:space:]') + if [ -n "$LATEST_RUBY" ]; then + if ! rbenv versions | grep -q "$LATEST_RUBY"; then + rbenv install "$LATEST_RUBY" + fi + rbenv global "$LATEST_RUBY" + fi +fi + +# --- Swift --- +log_step "Installing Swift" +if ! command_exists swift; then + ARCH=$(uname -m) + case "$ARCH" in + x86_64) SWIFT_DIR="ubuntu2404"; SWIFT_FILE_SUFFIX="ubuntu24.04" ;; + aarch64) SWIFT_DIR="ubuntu2404-aarch64"; SWIFT_FILE_SUFFIX="ubuntu24.04-aarch64" ;; + *) SWIFT_DIR=""; SWIFT_FILE_SUFFIX="" ;; + esac + + if [ -n "$SWIFT_DIR" ]; then + SWIFT_VERSION="6.0.3" + SWIFT_RELEASE="RELEASE" + SWIFT_PACKAGE="swift-${SWIFT_VERSION}-${SWIFT_RELEASE}-${SWIFT_FILE_SUFFIX}" + SWIFT_URL="https://download.swift.org/swift-${SWIFT_VERSION}-release/${SWIFT_DIR}/swift-${SWIFT_VERSION}-${SWIFT_RELEASE}/${SWIFT_PACKAGE}.tar.gz" + + TEMP_DIR=$(mktemp -d) + if curl -fsSL "$SWIFT_URL" -o "$TEMP_DIR/swift.tar.gz"; then + mkdir -p "$HOME/.swift" + tar -xzf "$TEMP_DIR/swift.tar.gz" -C "$TEMP_DIR" + cp -r "$TEMP_DIR/${SWIFT_PACKAGE}/usr" "$HOME/.swift/" + rm -rf "$TEMP_DIR" + + if ! grep -q 'swift' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Swift configuration' + echo 'export PATH="$HOME/.swift/usr/bin:$PATH"' + } >> "$HOME/.bashrc" + fi + export PATH="$HOME/.swift/usr/bin:$PATH" + else + rm -rf "$TEMP_DIR" + fi + fi +fi + +# --- Installation Summary --- +log_step "Installation Summary" + +echo "" +echo "System & Development Tools:" +command_exists gh && log_success "GitHub CLI: $(gh --version | head -n1)" || true +command_exists gh-setup-git-identity && log_success "gh-setup-git-identity: installed" || true +command_exists glab && log_success "GitLab CLI: $(glab --version | head -n1)" || true +command_exists glab-setup-git-identity && log_success "glab-setup-git-identity: installed" || true +command_exists git && log_success "Git: $(git --version)" || true +command_exists bun && log_success "Bun: $(bun --version)" || true +command_exists deno && log_success "Deno: $(deno --version | head -n1)" || true +command_exists node && log_success "Node.js: $(node --version)" || true +command_exists python && log_success "Python: $(python --version)" || true +command_exists go && log_success "Go: $(go version)" || true +command_exists rustc && log_success "Rust: $(rustc --version)" || true +command_exists java && log_success "Java: $(java -version 2>&1 | head -n1)" || true +command_exists kotlin && log_success "Kotlin: $(kotlin -version 2>&1 | head -n1)" || true +command_exists lean && log_success "Lean: $(lean --version)" || true +command_exists R && log_success "R: $(R --version | head -n1)" || true +command_exists ruby && log_success "Ruby: $(ruby --version)" || true +command_exists swift && log_success "Swift: $(swift --version 2>&1 | head -n1)" || true +command_exists brew && log_success "Homebrew: $(brew --version 2>/dev/null | head -n1)" || true +command_exists php && log_success "PHP: $(php --version 2>/dev/null | head -n1)" || true +command_exists perl && log_success "Perl: $(perl --version | head -n 2 | tail -n 1 | sed 's/^[[:space:]]*//')" || true +command_exists opam && log_success "Opam: $(opam --version)" || true + +echo "" +EOF_FULL_SETUP + +chmod +x /tmp/full-sandbox-user-setup.sh +if [ "$EUID" -eq 0 ]; then + su - sandbox -c "bash /tmp/full-sandbox-user-setup.sh" +else + sudo -i -u sandbox bash /tmp/full-sandbox-user-setup.sh +fi +rm -f /tmp/full-sandbox-user-setup.sh + +# --- Final cleanup --- +log_step "Final cleanup" +maybe_sudo apt-get clean +maybe_sudo apt-get autoclean +maybe_sudo apt-get autoremove -y +maybe_sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +log_step "Full Sandbox setup complete!" +log_success "All components installed successfully" +log_note "Please restart your shell or run: source ~/.bashrc" diff --git a/ubuntu/24.04/go/Dockerfile b/ubuntu/24.04/go/Dockerfile new file mode 100644 index 0000000..ae82f0e --- /dev/null +++ b/ubuntu/24.04/go/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:24.04 + +# Go sandbox: latest stable Go + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV GOROOT="/home/sandbox/.go" +ENV GOPATH="/home/sandbox/.go/path" +ENV PATH="/home/sandbox/.go/bin:/home/sandbox/.go/path/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/go/install.sh b/ubuntu/24.04/go/install.sh new file mode 100644 index 0000000..1e081c2 --- /dev/null +++ b/ubuntu/24.04/go/install.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Go (Golang) installation +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Go" + +if [ ! -d "$HOME/.go" ] && [ ! -d "/usr/local/go" ]; then + log_info "Installing Golang..." + + ARCH=$(uname -m) + case "$ARCH" in + x86_64) GO_ARCH="amd64" ;; + aarch64) GO_ARCH="arm64" ;; + armv7l) GO_ARCH="armv6l" ;; + *) GO_ARCH="" ;; + esac + + if [ -n "$GO_ARCH" ]; then + GO_VERSION=$(curl -sL 'https://go.dev/VERSION?m=text' | head -n1) + + if [ -n "$GO_VERSION" ]; then + GO_TARBALL="${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + GO_URL="https://go.dev/dl/${GO_TARBALL}" + + log_info "Downloading Go $GO_VERSION for $GO_ARCH..." + TEMP_DIR=$(mktemp -d) + curl -sL "$GO_URL" -o "$TEMP_DIR/$GO_TARBALL" + + log_info "Installing Go to $HOME/.go..." + mkdir -p "$HOME/.go" + tar -xzf "$TEMP_DIR/$GO_TARBALL" -C "$HOME/.go" --strip-components=1 + rm -rf "$TEMP_DIR" + + if ! grep -q 'GOROOT.*\.go' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Go configuration' + echo 'export GOROOT="$HOME/.go"' + echo 'export GOPATH="$HOME/.go/path"' + echo 'export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"' + } >> "$HOME/.bashrc" + fi + + export GOROOT="$HOME/.go" + export GOPATH="$HOME/.go/path" + export PATH="$GOROOT/bin:$GOPATH/bin:$PATH" + mkdir -p "$GOPATH" + + if command -v go &>/dev/null; then + log_success "Golang installed: $(go version)" + fi + fi + fi +else + log_info "Golang already installed." +fi + +log_success "Go installation complete" diff --git a/ubuntu/24.04/java/Dockerfile b/ubuntu/24.04/java/Dockerfile new file mode 100644 index 0000000..1329c9f --- /dev/null +++ b/ubuntu/24.04/java/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:24.04 + +# Java sandbox: SDKMAN + Eclipse Temurin 21 LTS + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates zip unzip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV SDKMAN_DIR="/home/sandbox/.sdkman" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/java/install.sh b/ubuntu/24.04/java/install.sh new file mode 100644 index 0000000..a1fb0cb --- /dev/null +++ b/ubuntu/24.04/java/install.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Java installation via SDKMAN (Eclipse Temurin 21 LTS) +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Java via SDKMAN" + +# --- SDKMAN --- +if [ ! -d "$HOME/.sdkman" ]; then + log_info "Installing SDKMAN (Java version manager)..." + curl -s "https://get.sdkman.io?rcupdate=false&ci=true" | bash + if ! grep -q 'sdkman-init.sh' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# SDKMAN configuration' + echo 'export SDKMAN_DIR="$HOME/.sdkman"' + echo '[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"' + } >> "$HOME/.bashrc" + fi + log_success "SDKMAN installed and configured" +else + log_info "SDKMAN already installed." +fi + +# Load SDKMAN and install Java +export SDKMAN_DIR="$HOME/.sdkman" +if [ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then + set +u + source "$SDKMAN_DIR/bin/sdkman-init.sh" + set -u + log_success "SDKMAN loaded for current session" + + log_info "Installing Java 21 LTS (OpenJDK via Eclipse Temurin)..." + set +u + if ! sdk list java 2>/dev/null | grep -q "21.*tem.*installed"; then + sdk install java 21-tem < /dev/null || { + log_warning "Eclipse Temurin installation failed, trying default OpenJDK..." + sdk install java 21-open < /dev/null || true + } + else + log_info "Java 21 (Temurin) already installed." + fi + set -u + + if command -v java &>/dev/null; then + log_success "Java version manager setup complete" + java -version 2>&1 | head -n1 + fi +fi + +log_success "Java installation complete" diff --git a/ubuntu/24.04/js/Dockerfile b/ubuntu/24.04/js/Dockerfile new file mode 100644 index 0000000..7e77542 --- /dev/null +++ b/ubuntu/24.04/js/Dockerfile @@ -0,0 +1,38 @@ +FROM ubuntu:24.04 + +# JavaScript/TypeScript sandbox: Node.js (NVM), Bun, Deno +# Includes: gh-setup-git-identity, glab-setup-git-identity + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +# Install minimal system prerequisites +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Create sandbox user +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +# Copy and run install script +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV NVM_DIR="/home/sandbox/.nvm" +ENV BUN_INSTALL="/home/sandbox/.bun" +ENV DENO_INSTALL="/home/sandbox/.deno" +ENV PATH="/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] + +# Entrypoint loads NVM +COPY --from=entrypoint /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint.sh +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/js/install.sh b/ubuntu/24.04/js/install.sh new file mode 100644 index 0000000..94c3c44 --- /dev/null +++ b/ubuntu/24.04/js/install.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# JavaScript/TypeScript runtime installation (Node.js via NVM, Bun, Deno) +# Usage: curl -fsSL | bash OR bash install.sh +# Requires: curl, git (should be pre-installed on Ubuntu 24.04) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing JavaScript/TypeScript runtimes" + +# --- Bun --- +if ! command_exists bun; then + log_info "Installing Bun..." + curl -fsSL https://bun.sh/install | bash + export BUN_INSTALL="$HOME/.bun" + export PATH="$BUN_INSTALL/bin:$PATH" + log_success "Bun installed" +else + log_info "Bun already installed." +fi + +export BUN_INSTALL="$HOME/.bun" +export PATH="$BUN_INSTALL/bin:$PATH" + +# --- Deno --- +if ! command_exists deno; then + log_info "Installing Deno..." + curl -fsSL https://deno.land/install.sh | sh -s -- -y + export DENO_INSTALL="$HOME/.deno" + export PATH="$DENO_INSTALL/bin:$PATH" + if ! grep -q 'DENO_INSTALL' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Deno configuration' + echo 'export DENO_INSTALL="$HOME/.deno"' + echo 'export PATH="$DENO_INSTALL/bin:$PATH"' + } >> "$HOME/.bashrc" + fi + log_success "Deno installed" +else + log_info "Deno already installed." +fi + +# --- NVM + Node.js --- +if [ ! -d "$HOME/.nvm" ]; then + log_info "Installing NVM..." + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash + log_success "NVM installed" +else + log_info "NVM already installed." +fi + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + +if ! nvm ls 20 2>/dev/null | grep -q 'v20'; then + log_info "Installing Node.js 20..." + nvm install 20 + log_success "Node.js 20 installed" +else + log_info "Node.js 20 already installed" +fi +nvm use 20 + +log_info "Updating npm to latest version..." +npm install -g npm@latest --no-fund --silent +log_success "npm updated to latest version" + +# --- gh-setup-git-identity --- +if command_exists bun; then + if ! command_exists gh-setup-git-identity; then + log_info "Installing gh-setup-git-identity..." + bun install -g gh-setup-git-identity + log_success "gh-setup-git-identity installed" + else + log_info "gh-setup-git-identity already installed." + fi +fi + +# --- glab-setup-git-identity --- +if command_exists bun; then + if ! command_exists glab-setup-git-identity; then + log_info "Installing glab-setup-git-identity..." + bun install -g glab-setup-git-identity + log_success "glab-setup-git-identity installed" + else + log_info "glab-setup-git-identity already installed." + fi +fi + +log_success "JavaScript/TypeScript runtimes installation complete" diff --git a/ubuntu/24.04/kotlin/Dockerfile b/ubuntu/24.04/kotlin/Dockerfile new file mode 100644 index 0000000..800c928 --- /dev/null +++ b/ubuntu/24.04/kotlin/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:24.04 + +# Kotlin sandbox: SDKMAN + Kotlin + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates zip unzip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV SDKMAN_DIR="/home/sandbox/.sdkman" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/kotlin/install.sh b/ubuntu/24.04/kotlin/install.sh new file mode 100644 index 0000000..60ba266 --- /dev/null +++ b/ubuntu/24.04/kotlin/install.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Kotlin installation via SDKMAN +# Usage: curl -fsSL | bash OR bash install.sh +# Requires: SDKMAN (install java first, or SDKMAN will be installed here) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Kotlin via SDKMAN" + +# Ensure SDKMAN is installed +if [ ! -d "$HOME/.sdkman" ]; then + log_info "SDKMAN not found, installing..." + curl -s "https://get.sdkman.io?rcupdate=false&ci=true" | bash + if ! grep -q 'sdkman-init.sh' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# SDKMAN configuration' + echo 'export SDKMAN_DIR="$HOME/.sdkman"' + echo '[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"' + } >> "$HOME/.bashrc" + fi +fi + +export SDKMAN_DIR="$HOME/.sdkman" +if [ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then + set +u + source "$SDKMAN_DIR/bin/sdkman-init.sh" + set -u + + if ! command_exists kotlin; then + log_info "Installing Kotlin via SDKMAN..." + set +u + sdk install kotlin < /dev/null || true + set -u + + if command -v kotlin &>/dev/null; then + log_success "Kotlin installed: $(kotlin -version 2>&1 | head -n1)" + fi + else + log_info "Kotlin already installed." + fi +fi + +log_success "Kotlin installation complete" diff --git a/ubuntu/24.04/lean/Dockerfile b/ubuntu/24.04/lean/Dockerfile new file mode 100644 index 0000000..e95591f --- /dev/null +++ b/ubuntu/24.04/lean/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:24.04 + +# Lean sandbox: Lean theorem prover via elan + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV PATH="/home/sandbox/.elan/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/lean/install.sh b/ubuntu/24.04/lean/install.sh new file mode 100644 index 0000000..0af4d14 --- /dev/null +++ b/ubuntu/24.04/lean/install.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Lean theorem prover installation via elan +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Lean via elan" + +if [ ! -d "$HOME/.elan" ]; then + log_info "Installing Lean (via elan)..." + curl https://elan.lean-lang.org/elan-init.sh -sSf | sh -s -- -y --default-toolchain stable + if [ -f "$HOME/.elan/env" ]; then + \. "$HOME/.elan/env" + log_success "Lean installed successfully" + fi + if ! grep -q 'elan' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Lean (elan) configuration' + echo 'export PATH="$HOME/.elan/bin:$PATH"' + } >> "$HOME/.bashrc" + fi +else + log_info "Lean (elan) already installed." +fi + +log_success "Lean installation complete" diff --git a/ubuntu/24.04/perl/Dockerfile b/ubuntu/24.04/perl/Dockerfile new file mode 100644 index 0000000..d018f3c --- /dev/null +++ b/ubuntu/24.04/perl/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:24.04 + +# Perl sandbox: Perlbrew + latest stable Perl + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV PERLBREW_ROOT="/home/sandbox/.perl5" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/perl/install.sh b/ubuntu/24.04/perl/install.sh new file mode 100644 index 0000000..61d8d0e --- /dev/null +++ b/ubuntu/24.04/perl/install.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Perl installation via Perlbrew +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Perl via Perlbrew" + +if [ ! -d "$HOME/.perl5" ]; then + log_info "Installing Perlbrew (Perl version manager)..." + + export PERLBREW_ROOT="$HOME/.perl5" + curl -L https://install.perlbrew.pl | bash + + if ! grep -q 'perlbrew' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Perlbrew configuration' + echo 'if [ -n "$PS1" ]; then' + echo ' export PERLBREW_ROOT="$HOME/.perl5"' + echo ' [ -f "$PERLBREW_ROOT/etc/bashrc" ] && source "$PERLBREW_ROOT/etc/bashrc"' + echo 'fi' + } >> "$HOME/.bashrc" + fi + + if [ -f "$PERLBREW_ROOT/etc/bashrc" ]; then + sed -i 's/\$1/${1:-}/g' "$PERLBREW_ROOT/etc/bashrc" 2>/dev/null || true + sed -i 's/\$PERLBREW_LIB/${PERLBREW_LIB:-}/g' "$PERLBREW_ROOT/etc/bashrc" 2>/dev/null || true + sed -i 's/\$outsep/${outsep:-}/g' "$PERLBREW_ROOT/etc/bashrc" 2>/dev/null || true + + set +u + source "$PERLBREW_ROOT/etc/bashrc" + set -u + log_success "Perlbrew installed and configured" + + log_info "Installing latest stable Perl version..." + PERLBREW_OUTPUT=$(perlbrew available 2>&1 || true) + LATEST_PERL=$(echo "$PERLBREW_OUTPUT" | grep -oE 'perl-5\.[0-9]+\.[0-9]+' | head -1 || true) + + if [ -n "$LATEST_PERL" ]; then + log_info "Installing $LATEST_PERL..." + if ! perlbrew list | grep -q "$LATEST_PERL"; then + perlbrew install "$LATEST_PERL" --notest || true + fi + + if perlbrew list | grep -q "$LATEST_PERL"; then + perlbrew switch "$LATEST_PERL" + log_success "Perl version manager setup complete" + fi + fi + fi +else + log_info "Perlbrew already installed." +fi + +log_success "Perl installation complete" diff --git a/ubuntu/24.04/php/Dockerfile b/ubuntu/24.04/php/Dockerfile new file mode 100644 index 0000000..09d811f --- /dev/null +++ b/ubuntu/24.04/php/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:24.04 + +# PHP sandbox: PHP 8.3 via Homebrew + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox && \ + mkdir -p /home/linuxbrew/.linuxbrew && \ + chown -R sandbox:sandbox /home/linuxbrew + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV PATH="/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/linuxbrew/.linuxbrew/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/php/install.sh b/ubuntu/24.04/php/install.sh new file mode 100644 index 0000000..53cf83f --- /dev/null +++ b/ubuntu/24.04/php/install.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# PHP 8.3 installation via Homebrew +# Usage: curl -fsSL | bash OR bash install.sh +# Requires: Homebrew (will be installed if not present) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing PHP 8.3 via Homebrew" + +# Ensure Homebrew directory exists +if [ ! -d /home/linuxbrew/.linuxbrew ]; then + log_info "Creating Homebrew directory..." + maybe_sudo mkdir -p /home/linuxbrew/.linuxbrew + maybe_sudo chown -R "$(whoami)":"$(whoami)" /home/linuxbrew 2>/dev/null || true +fi + +# Install Homebrew if not present +if ! command_exists brew; then + log_info "Installing Homebrew..." + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 2>&1 || true + + if [[ -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + elif [[ -x "$HOME/.linuxbrew/bin/brew" ]]; then + eval "$("$HOME/.linuxbrew/bin/brew" shellenv)" + fi + + if ! grep -q "brew shellenv" "$HOME/.bashrc" 2>/dev/null; then + BREW_PREFIX=$(brew --prefix 2>/dev/null || echo "/home/linuxbrew/.linuxbrew") + echo "eval \"\$($BREW_PREFIX/bin/brew shellenv)\"" >> "$HOME/.bashrc" + fi +else + eval "$(brew shellenv 2>/dev/null)" || true +fi + +# Install PHP via Homebrew +if command_exists brew; then + if ! brew list --formula 2>/dev/null | grep -q "^php@"; then + log_info "Installing PHP via Homebrew..." + + if ! brew tap | grep -q "shivammathur/php"; then + brew tap shivammathur/php || true + fi + + if brew tap | grep -q "shivammathur/php"; then + export HOMEBREW_NO_ANALYTICS=1 + export HOMEBREW_NO_AUTO_UPDATE=1 + + log_info "Installing PHP 8.3..." + brew install shivammathur/php/php@8.3 || true + + if brew list --formula 2>/dev/null | grep -q "^php@8.3$"; then + brew link --overwrite --force shivammathur/php/php@8.3 2>&1 | grep -v "Warning" || true + + BREW_PREFIX=$(brew --prefix 2>/dev/null || echo "") + if [[ -n "$BREW_PREFIX" && -d "$BREW_PREFIX/opt/php@8.3" ]]; then + export PATH="$BREW_PREFIX/opt/php@8.3/bin:$BREW_PREFIX/opt/php@8.3/sbin:$PATH" + + if ! grep -q "php@8.3/bin" "$HOME/.bashrc" 2>/dev/null; then + cat >> "$HOME/.bashrc" << 'PHP_PATH_EOF' + +# PHP 8.3 PATH configuration +export PATH="$(brew --prefix)/opt/php@8.3/bin:$(brew --prefix)/opt/php@8.3/sbin:$PATH" +PHP_PATH_EOF + fi + fi + + if command -v php &>/dev/null; then + PHP_VERSION=$(php --version 2>/dev/null | head -n 1 || echo "unknown version") + log_success "PHP installed and available: $PHP_VERSION" + fi + fi + fi + else + log_info "PHP already installed via Homebrew." + fi +fi + +log_success "PHP installation complete" diff --git a/ubuntu/24.04/python/Dockerfile b/ubuntu/24.04/python/Dockerfile new file mode 100644 index 0000000..0d6e489 --- /dev/null +++ b/ubuntu/24.04/python/Dockerfile @@ -0,0 +1,32 @@ +FROM ubuntu:24.04 + +# Python sandbox: Pyenv + latest stable Python + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +# Install system prerequisites and Python build dependencies +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential \ + libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ + libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \ + libffi-dev liblzma-dev && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV PYENV_ROOT="/home/sandbox/.pyenv" +ENV PATH="/home/sandbox/.pyenv/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/python/install.sh b/ubuntu/24.04/python/install.sh new file mode 100644 index 0000000..231db59 --- /dev/null +++ b/ubuntu/24.04/python/install.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Python installation via Pyenv +# Usage: curl -fsSL | bash OR bash install.sh +# Requires: build dependencies (libssl-dev, zlib1g-dev, etc.) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Python via Pyenv" + +# Install build dependencies (requires root/sudo) +log_info "Installing Python build dependencies..." +maybe_sudo apt install -y \ + libssl-dev \ + zlib1g-dev \ + libbz2-dev \ + libreadline-dev \ + libsqlite3-dev \ + libncursesw5-dev \ + xz-utils \ + tk-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libffi-dev \ + liblzma-dev +log_success "Python build dependencies installed" + +# --- Pyenv --- +if [ ! -d "$HOME/.pyenv" ]; then + log_info "Installing Pyenv..." + curl https://pyenv.run | bash + if ! grep -q 'pyenv init' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Pyenv configuration' + echo 'export PYENV_ROOT="$HOME/.pyenv"' + echo 'export PATH="$PYENV_ROOT/bin:$PATH"' + echo 'eval "$(pyenv init --path)"' + echo 'eval "$(pyenv init -)"' + } >> "$HOME/.bashrc" + fi + log_success "Pyenv installed and configured" +else + log_info "Pyenv already installed." +fi + +# Load pyenv for current session +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +if command -v pyenv >/dev/null 2>&1; then + eval "$(pyenv init --path)" + eval "$(pyenv init -)" + log_success "Pyenv loaded for current session" + + # Install latest stable Python version + log_info "Installing latest stable Python version..." + LATEST_PYTHON=$(pyenv install --list | grep -E '^\s*[0-9]+\.[0-9]+\.[0-9]+$' | tail -1 | tr -d '[:space:]') + + if [ -n "$LATEST_PYTHON" ]; then + log_info "Installing Python $LATEST_PYTHON..." + if ! pyenv versions --bare | grep -q "^${LATEST_PYTHON}$"; then + pyenv install "$LATEST_PYTHON" + else + log_info "Python $LATEST_PYTHON already installed." + fi + + log_info "Setting Python $LATEST_PYTHON as global default..." + pyenv global "$LATEST_PYTHON" + log_success "Python version manager setup complete" + python --version + fi +fi + +log_success "Python installation complete" diff --git a/ubuntu/24.04/r/Dockerfile b/ubuntu/24.04/r/Dockerfile new file mode 100644 index 0000000..abac0ab --- /dev/null +++ b/ubuntu/24.04/r/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:24.04 + +# R sandbox: R statistical language + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/r/install.sh b/ubuntu/24.04/r/install.sh new file mode 100644 index 0000000..c2290bb --- /dev/null +++ b/ubuntu/24.04/r/install.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# R language installation +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing R" + +if ! command_exists R; then + log_info "Installing R statistical language..." + maybe_sudo apt install -y r-base + log_success "R language installed" +else + log_info "R already installed." +fi + +log_success "R installation complete" diff --git a/ubuntu/24.04/rocq/Dockerfile b/ubuntu/24.04/rocq/Dockerfile new file mode 100644 index 0000000..a297b79 --- /dev/null +++ b/ubuntu/24.04/rocq/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:24.04 + +# Rocq/Coq sandbox: Opam + Rocq theorem prover + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential bubblewrap && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV OPAM_SWITCH_PREFIX="/home/sandbox/.opam/default" +ENV CAML_LD_LIBRARY_PATH="/home/sandbox/.opam/default/lib/stublibs:/home/sandbox/.opam/default/lib/ocaml/stublibs:/home/sandbox/.opam/default/lib/ocaml" +ENV OCAML_TOPLEVEL_PATH="/home/sandbox/.opam/default/lib/toplevel" +ENV PATH="/home/sandbox/.opam/default/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/rocq/install.sh b/ubuntu/24.04/rocq/install.sh new file mode 100644 index 0000000..442644b --- /dev/null +++ b/ubuntu/24.04/rocq/install.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# Rocq/Coq theorem prover installation via Opam +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Rocq/Coq via Opam" + +# --- Opam --- +if ! command_exists opam; then + log_info "Installing Opam (OCaml package manager)..." + maybe_sudo apt install -y bubblewrap || true + + bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --no-backup" <<< "y" || { + maybe_sudo apt install -y opam || true + } + + if command_exists opam; then + log_success "Opam installed successfully" + fi +else + log_info "Opam already installed." +fi + +# Initialize opam and install Rocq +if command_exists opam; then + if [ ! -d "$HOME/.opam" ]; then + log_info "Initializing Opam..." + opam init --disable-sandboxing --auto-setup -y || true + log_success "Opam initialized" + fi + + eval "$(opam env --switch=default 2>/dev/null)" || true + + ROCQ_ACCESSIBLE=false + if command -v rocq &>/dev/null && rocq -v &>/dev/null; then + ROCQ_ACCESSIBLE=true + elif command -v rocqc &>/dev/null; then + ROCQ_ACCESSIBLE=true + elif command -v coqc &>/dev/null; then + ROCQ_ACCESSIBLE=true + fi + + if [ "$ROCQ_ACCESSIBLE" = false ]; then + log_info "Installing Rocq Prover (this may take several minutes)..." + opam repo add rocq-released https://rocq-prover.org/opam/released 2>/dev/null || true + opam update 2>/dev/null || true + + if opam pin add rocq-prover --yes 2>/dev/null; then + log_success "Rocq Prover pinned and installed" + elif opam install rocq-prover -y 2>/dev/null; then + log_success "Rocq Prover installed via opam install" + else + opam install coq -y || true + fi + + eval "$(opam env --switch=default 2>/dev/null)" || true + else + log_info "Rocq Prover already installed." + fi + + if ! grep -q 'opam env' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Opam (OCaml/Rocq) configuration' + echo 'test -r $HOME/.opam/opam-init/init.sh && . $HOME/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true' + } >> "$HOME/.bashrc" + fi +fi + +log_success "Rocq/Coq installation complete" diff --git a/ubuntu/24.04/ruby/Dockerfile b/ubuntu/24.04/ruby/Dockerfile new file mode 100644 index 0000000..8d5621a --- /dev/null +++ b/ubuntu/24.04/ruby/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:24.04 + +# Ruby sandbox: rbenv + latest stable Ruby 3.x + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential libyaml-dev && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV RBENV_ROOT="/home/sandbox/.rbenv" +ENV PATH="/home/sandbox/.rbenv/bin:/home/sandbox/.rbenv/shims:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/ruby/install.sh b/ubuntu/24.04/ruby/install.sh new file mode 100644 index 0000000..b7bd621 --- /dev/null +++ b/ubuntu/24.04/ruby/install.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Ruby installation via rbenv +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Ruby via rbenv" + +# Install build dependencies +log_info "Installing Ruby build dependencies..." +maybe_sudo apt install -y libyaml-dev +log_success "Ruby build dependencies installed" + +if [ ! -d "$HOME/.rbenv" ]; then + log_info "Installing rbenv (Ruby version manager)..." + + git clone https://github.com/rbenv/rbenv.git "$HOME/.rbenv" + mkdir -p "$HOME/.rbenv/plugins" + git clone https://github.com/rbenv/ruby-build.git "$HOME/.rbenv/plugins/ruby-build" + + if ! grep -q 'rbenv init' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# rbenv configuration' + echo 'export PATH="$HOME/.rbenv/bin:$PATH"' + echo 'eval "$(rbenv init - bash)"' + } >> "$HOME/.bashrc" + fi + + export PATH="$HOME/.rbenv/bin:$PATH" + eval "$(rbenv init - bash)" + log_success "rbenv installed and configured" + + # Install latest stable Ruby 3.x version (avoid pre-release 4.x) + log_info "Installing latest stable Ruby version..." + LATEST_RUBY=$(rbenv install -l 2>/dev/null | grep -E '^\s*3\.[0-9]+\.[0-9]+$' | tail -1 | tr -d '[:space:]') + + if [ -n "$LATEST_RUBY" ]; then + log_info "Installing Ruby $LATEST_RUBY..." + if ! rbenv versions | grep -q "$LATEST_RUBY"; then + rbenv install "$LATEST_RUBY" + else + log_info "Ruby $LATEST_RUBY already installed." + fi + + rbenv global "$LATEST_RUBY" + log_success "Ruby version manager setup complete" + ruby --version + fi +else + log_info "rbenv already installed." +fi + +log_success "Ruby installation complete" diff --git a/ubuntu/24.04/rust/Dockerfile b/ubuntu/24.04/rust/Dockerfile new file mode 100644 index 0000000..22a0c86 --- /dev/null +++ b/ubuntu/24.04/rust/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:24.04 + +# Rust sandbox: rustup + latest stable Rust + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV CARGO_HOME="/home/sandbox/.cargo" +ENV PATH="/home/sandbox/.cargo/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/rust/install.sh b/ubuntu/24.04/rust/install.sh new file mode 100644 index 0000000..300c934 --- /dev/null +++ b/ubuntu/24.04/rust/install.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Rust installation via rustup +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Rust" + +if [ ! -d "$HOME/.cargo" ]; then + log_info "Installing Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + if [ -f "$HOME/.cargo/env" ]; then + \. "$HOME/.cargo/env" + log_success "Rust installed successfully" + fi +else + log_info "Rust already installed." +fi + +log_success "Rust installation complete" diff --git a/ubuntu/24.04/swift/Dockerfile b/ubuntu/24.04/swift/Dockerfile new file mode 100644 index 0000000..f0d05f2 --- /dev/null +++ b/ubuntu/24.04/swift/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:24.04 + +# Swift sandbox: Swift 6.x + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace + +RUN apt update -y && \ + apt install -y curl git sudo ca-certificates build-essential && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash sandbox && \ + passwd -d sandbox && \ + usermod -aG sudo sandbox + +COPY install.sh /tmp/install.sh +RUN chmod +x /tmp/install.sh && \ + su - sandbox -c "bash /tmp/install.sh" && \ + rm -f /tmp/install.sh + +USER sandbox +WORKDIR /home/sandbox + +ENV PATH="/home/sandbox/.swift/usr/bin:${PATH}" + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/swift/install.sh b/ubuntu/24.04/swift/install.sh new file mode 100644 index 0000000..8438cac --- /dev/null +++ b/ubuntu/24.04/swift/install.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Swift installation +# Usage: curl -fsSL | bash OR bash install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + source "$SCRIPT_DIR/../common.sh" +else + set -euo pipefail + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_error() { echo "[✗] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } +fi + +log_step "Installing Swift" + +if ! command_exists swift; then + log_info "Installing Swift..." + + ARCH=$(uname -m) + case "$ARCH" in + x86_64) + SWIFT_DIR="ubuntu2404" + SWIFT_FILE_SUFFIX="ubuntu24.04" + ;; + aarch64) + SWIFT_DIR="ubuntu2404-aarch64" + SWIFT_FILE_SUFFIX="ubuntu24.04-aarch64" + ;; + *) + SWIFT_DIR="" + SWIFT_FILE_SUFFIX="" + ;; + esac + + if [ -n "$SWIFT_DIR" ]; then + SWIFT_VERSION="6.0.3" + SWIFT_RELEASE="RELEASE" + SWIFT_PACKAGE="swift-${SWIFT_VERSION}-${SWIFT_RELEASE}-${SWIFT_FILE_SUFFIX}" + SWIFT_URL="https://download.swift.org/swift-${SWIFT_VERSION}-release/${SWIFT_DIR}/swift-${SWIFT_VERSION}-${SWIFT_RELEASE}/${SWIFT_PACKAGE}.tar.gz" + + log_info "Downloading Swift $SWIFT_VERSION for $ARCH..." + TEMP_DIR=$(mktemp -d) + + if curl -fsSL "$SWIFT_URL" -o "$TEMP_DIR/swift.tar.gz"; then + log_info "Installing Swift to $HOME/.swift..." + mkdir -p "$HOME/.swift" + tar -xzf "$TEMP_DIR/swift.tar.gz" -C "$TEMP_DIR" + cp -r "$TEMP_DIR/${SWIFT_PACKAGE}/usr" "$HOME/.swift/" + rm -rf "$TEMP_DIR" + + if ! grep -q 'swift' "$HOME/.bashrc" 2>/dev/null; then + { + echo '' + echo '# Swift configuration' + echo 'export PATH="$HOME/.swift/usr/bin:$PATH"' + } >> "$HOME/.bashrc" + fi + + export PATH="$HOME/.swift/usr/bin:$PATH" + + if command -v swift &>/dev/null; then + log_success "Swift installed: $(swift --version | head -n1)" + fi + else + log_error "Failed to download Swift from $SWIFT_URL" + rm -rf "$TEMP_DIR" + fi + else + log_warning "Swift installation skipped: unsupported architecture $ARCH" + fi +else + log_info "Swift already installed." +fi + +log_success "Swift installation complete" From c1de8b147bbbad4ca07f3e9741ed2cfe5905cfcf Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 17:47:59 +0100 Subject: [PATCH 03/13] Fix per-language Dockerfiles to build from repo root context Update all 16 language Dockerfiles to use repo-root-relative COPY paths so they can be built with `docker build -f ubuntu/24.04//Dockerfile .` from the repository root. Also add common.sh COPY and remove the invalid COPY --from=entrypoint in js/Dockerfile. Co-Authored-By: Claude Opus 4.5 --- ubuntu/24.04/assembly/Dockerfile | 4 +++- ubuntu/24.04/cpp/Dockerfile | 4 +++- ubuntu/24.04/dotnet/Dockerfile | 4 +++- ubuntu/24.04/go/Dockerfile | 4 +++- ubuntu/24.04/java/Dockerfile | 4 +++- ubuntu/24.04/js/Dockerfile | 7 +++---- ubuntu/24.04/kotlin/Dockerfile | 4 +++- ubuntu/24.04/lean/Dockerfile | 4 +++- ubuntu/24.04/perl/Dockerfile | 4 +++- ubuntu/24.04/php/Dockerfile | 4 +++- ubuntu/24.04/python/Dockerfile | 4 +++- ubuntu/24.04/r/Dockerfile | 4 +++- ubuntu/24.04/rocq/Dockerfile | 4 +++- ubuntu/24.04/ruby/Dockerfile | 4 +++- ubuntu/24.04/rust/Dockerfile | 4 +++- ubuntu/24.04/swift/Dockerfile | 4 +++- 16 files changed, 48 insertions(+), 19 deletions(-) diff --git a/ubuntu/24.04/assembly/Dockerfile b/ubuntu/24.04/assembly/Dockerfile index 13055d2..9014868 100644 --- a/ubuntu/24.04/assembly/Dockerfile +++ b/ubuntu/24.04/assembly/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/assembly/Dockerfile -t sandbox-assembly . # Assembly sandbox: NASM, FASM (x86_64 only), GNU Assembler, LLVM MC @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/assembly/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh && \ diff --git a/ubuntu/24.04/cpp/Dockerfile b/ubuntu/24.04/cpp/Dockerfile index 13f8632..6e51892 100644 --- a/ubuntu/24.04/cpp/Dockerfile +++ b/ubuntu/24.04/cpp/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/cpp/Dockerfile -t sandbox-cpp . # C/C++ sandbox: build-essential, CMake, Clang, LLVM, LLD @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/cpp/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh && \ diff --git a/ubuntu/24.04/dotnet/Dockerfile b/ubuntu/24.04/dotnet/Dockerfile index 633ebdb..c119996 100644 --- a/ubuntu/24.04/dotnet/Dockerfile +++ b/ubuntu/24.04/dotnet/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/dotnet/Dockerfile -t sandbox-dotnet . # .NET sandbox: .NET SDK 8.0 @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/dotnet/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/go/Dockerfile b/ubuntu/24.04/go/Dockerfile index ae82f0e..3ed5a9f 100644 --- a/ubuntu/24.04/go/Dockerfile +++ b/ubuntu/24.04/go/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/go/Dockerfile -t sandbox-go . # Go sandbox: latest stable Go @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/go/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/java/Dockerfile b/ubuntu/24.04/java/Dockerfile index 1329c9f..97b9bb8 100644 --- a/ubuntu/24.04/java/Dockerfile +++ b/ubuntu/24.04/java/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/java/Dockerfile -t sandbox-java . # Java sandbox: SDKMAN + Eclipse Temurin 21 LTS @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/java/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/js/Dockerfile b/ubuntu/24.04/js/Dockerfile index 7e77542..b52e405 100644 --- a/ubuntu/24.04/js/Dockerfile +++ b/ubuntu/24.04/js/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/js/Dockerfile -t sandbox-js . # JavaScript/TypeScript sandbox: Node.js (NVM), Bun, Deno # Includes: gh-setup-git-identity, glab-setup-git-identity @@ -17,7 +18,8 @@ RUN useradd -m -s /bin/bash sandbox && \ usermod -aG sudo sandbox # Copy and run install script -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/js/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh @@ -32,7 +34,4 @@ ENV PATH="/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:${PATH}" SHELL ["/bin/bash", "-c"] -# Entrypoint loads NVM -COPY --from=entrypoint /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint.sh -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] CMD ["/bin/bash"] diff --git a/ubuntu/24.04/kotlin/Dockerfile b/ubuntu/24.04/kotlin/Dockerfile index 800c928..4d000ed 100644 --- a/ubuntu/24.04/kotlin/Dockerfile +++ b/ubuntu/24.04/kotlin/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/kotlin/Dockerfile -t sandbox-kotlin . # Kotlin sandbox: SDKMAN + Kotlin @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/kotlin/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/lean/Dockerfile b/ubuntu/24.04/lean/Dockerfile index e95591f..3fe141a 100644 --- a/ubuntu/24.04/lean/Dockerfile +++ b/ubuntu/24.04/lean/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/lean/Dockerfile -t sandbox-lean . # Lean sandbox: Lean theorem prover via elan @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/lean/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/perl/Dockerfile b/ubuntu/24.04/perl/Dockerfile index d018f3c..0873922 100644 --- a/ubuntu/24.04/perl/Dockerfile +++ b/ubuntu/24.04/perl/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/perl/Dockerfile -t sandbox-perl . # Perl sandbox: Perlbrew + latest stable Perl @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/perl/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/php/Dockerfile b/ubuntu/24.04/php/Dockerfile index 09d811f..b9abf25 100644 --- a/ubuntu/24.04/php/Dockerfile +++ b/ubuntu/24.04/php/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/php/Dockerfile -t sandbox-php . # PHP sandbox: PHP 8.3 via Homebrew @@ -15,7 +16,8 @@ RUN useradd -m -s /bin/bash sandbox && \ mkdir -p /home/linuxbrew/.linuxbrew && \ chown -R sandbox:sandbox /home/linuxbrew -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/php/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/python/Dockerfile b/ubuntu/24.04/python/Dockerfile index 0d6e489..27725fb 100644 --- a/ubuntu/24.04/python/Dockerfile +++ b/ubuntu/24.04/python/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/python/Dockerfile -t sandbox-python . # Python sandbox: Pyenv + latest stable Python @@ -17,7 +18,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/python/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/r/Dockerfile b/ubuntu/24.04/r/Dockerfile index abac0ab..a94f7fe 100644 --- a/ubuntu/24.04/r/Dockerfile +++ b/ubuntu/24.04/r/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/r/Dockerfile -t sandbox-r . # R sandbox: R statistical language @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/r/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/rocq/Dockerfile b/ubuntu/24.04/rocq/Dockerfile index a297b79..4a84116 100644 --- a/ubuntu/24.04/rocq/Dockerfile +++ b/ubuntu/24.04/rocq/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/rocq/Dockerfile -t sandbox-rocq . # Rocq/Coq sandbox: Opam + Rocq theorem prover @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/rocq/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/ruby/Dockerfile b/ubuntu/24.04/ruby/Dockerfile index 8d5621a..aaacfac 100644 --- a/ubuntu/24.04/ruby/Dockerfile +++ b/ubuntu/24.04/ruby/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/ruby/Dockerfile -t sandbox-ruby . # Ruby sandbox: rbenv + latest stable Ruby 3.x @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/ruby/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/rust/Dockerfile b/ubuntu/24.04/rust/Dockerfile index 22a0c86..538e90a 100644 --- a/ubuntu/24.04/rust/Dockerfile +++ b/ubuntu/24.04/rust/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/rust/Dockerfile -t sandbox-rust . # Rust sandbox: rustup + latest stable Rust @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/rust/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh diff --git a/ubuntu/24.04/swift/Dockerfile b/ubuntu/24.04/swift/Dockerfile index f0d05f2..caf1695 100644 --- a/ubuntu/24.04/swift/Dockerfile +++ b/ubuntu/24.04/swift/Dockerfile @@ -1,4 +1,5 @@ FROM ubuntu:24.04 +# Build: docker build -f ubuntu/24.04/swift/Dockerfile -t sandbox-swift . # Swift sandbox: Swift 6.x @@ -13,7 +14,8 @@ RUN useradd -m -s /bin/bash sandbox && \ passwd -d sandbox && \ usermod -aG sudo sandbox -COPY install.sh /tmp/install.sh +COPY ubuntu/24.04/swift/install.sh /tmp/install.sh +COPY ubuntu/24.04/common.sh /common.sh RUN chmod +x /tmp/install.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ rm -f /tmp/install.sh From c29f9532672d7027b477af4614fa8e2c45c901dd Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 17:49:17 +0100 Subject: [PATCH 04/13] Update documentation and fix sandbox Dockerfiles - Update README.md with modular architecture table and usage examples - Update ARCHITECTURE.md with new file structure and modular design diagram - Fix essentials-sandbox and full-sandbox Dockerfiles to use repo root build context (docker build -f /Dockerfile .) - Add changeset for minor version bump Co-Authored-By: Claude Opus 4.5 --- .changeset/split-sandbox-modular.md | 15 +++ ARCHITECTURE.md | 122 ++++++++++++++++++--- README.md | 47 +++++++- ubuntu/24.04/essentials-sandbox/Dockerfile | 16 +-- ubuntu/24.04/full-sandbox/Dockerfile | 13 ++- 5 files changed, 184 insertions(+), 29 deletions(-) create mode 100644 .changeset/split-sandbox-modular.md diff --git a/.changeset/split-sandbox-modular.md b/.changeset/split-sandbox-modular.md new file mode 100644 index 0000000..bac7d7a --- /dev/null +++ b/.changeset/split-sandbox-modular.md @@ -0,0 +1,15 @@ +--- +bump: minor +--- + +Split sandbox into modular per-language components + +Added modular architecture under ubuntu/24.04/ with: +- Per-language install.sh scripts and Dockerfiles (16 languages) +- essentials-sandbox: minimal image for git identity tools +- full-sandbox: complete image built on top of essentials +- Shared common.sh utilities + +Each language can now be installed standalone on Ubuntu 24.04 or built +as an independent Docker image, enabling configurable disk usage and +parallel CI/CD builds. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0370a47..f9958e0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -108,24 +108,116 @@ See [Case Study: Docker ARM64 Build Timeout](docs/case-studies/issue-7/README.md sandbox/ ├── .github/ │ └── workflows/ -│ └── release.yml # CI/CD workflow -├── docs/ -│ └── case-studies/ -│ └── issue-7/ # ARM64 timeout analysis +│ └── release.yml # CI/CD workflow +├── ubuntu/ +│ └── 24.04/ +│ ├── common.sh # Shared functions for all install scripts +│ ├── js/ # JavaScript/TypeScript (Node.js, Bun, Deno) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── python/ # Python (Pyenv) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── go/ # Go +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── rust/ # Rust (rustup) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── java/ # Java (SDKMAN, Temurin) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── kotlin/ # Kotlin (SDKMAN) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── dotnet/ # .NET SDK 8.0 +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── r/ # R language +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── ruby/ # Ruby (rbenv) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── php/ # PHP 8.3 (Homebrew) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── perl/ # Perl (Perlbrew) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── swift/ # Swift 6.x +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── lean/ # Lean (elan) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── rocq/ # Rocq/Coq (Opam) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── cpp/ # C/C++ (CMake, Clang, LLVM) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── assembly/ # Assembly (NASM, FASM) +│ │ ├── install.sh +│ │ └── Dockerfile +│ ├── essentials-sandbox/ # Minimal sandbox (git identity tools) +│ │ ├── install.sh +│ │ └── Dockerfile +│ └── full-sandbox/ # Complete sandbox (all languages) +│ ├── install.sh +│ └── Dockerfile ├── scripts/ -│ └── ... # Build scripts -├── data/ -│ └── ... # Data files -├── experiments/ -│ └── ... # Experimental scripts -├── Dockerfile # Main container definition -├── README.md # Project overview -├── ARCHITECTURE.md # This file -├── REQUIREMENTS.md # Project requirements -├── LICENSE # MIT License -└── package.json # Node.js metadata +│ ├── ubuntu-24-server-install.sh # Legacy full installation script +│ ├── entrypoint.sh # Container entrypoint +│ ├── measure-disk-space.sh # Disk space measurement +│ └── ... # Other scripts +├── docs/ +│ └── case-studies/ # Case studies +├── data/ # Data files +├── experiments/ # Experimental scripts +├── Dockerfile # Root Dockerfile (full sandbox) +├── README.md # Project overview +├── ARCHITECTURE.md # This file +├── REQUIREMENTS.md # Project requirements +└── LICENSE # MIT License ``` +## Modular Design + +The sandbox follows a layered modular architecture: + +``` +┌─────────────────────────────────────────────┐ +│ full-sandbox │ +│ (konard/sandbox or konard/sandbox-full) │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ essentials-sandbox │ │ +│ │ (konard/sandbox-essentials) │ │ +│ │ │ │ +│ │ git, gh, glab, Node.js, Bun, │ │ +│ │ Deno, gh-setup-git-identity, │ │ +│ │ glab-setup-git-identity │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ + Python, Go, Rust, Java, Kotlin, .NET, │ +│ R, Ruby, PHP, Perl, Swift, Lean, Rocq, │ +│ C/C++, Assembly │ +└─────────────────────────────────────────────┘ + +Each language also available as standalone: +┌────┐ ┌────────┐ ┌────┐ ┌──────┐ ┌──────┐ +│ JS │ │ Python │ │ Go │ │ Rust │ │ ... │ +└────┘ └────────┘ └────┘ └──────┘ └──────┘ +``` + +### Benefits + +1. **Configurable disk usage**: Users can choose only the languages they need +2. **Parallel CI/CD**: Each language image can be built and tested independently +3. **Faster iteration**: Changes to one language only rebuild that image +4. **Standalone scripts**: Each `install.sh` works directly on Ubuntu 24.04 via `curl | bash` + ## Design Decisions ### 1. Non-Root User diff --git a/README.md b/README.md index 1fe8f11..e4f05dc 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,59 @@ This sandbox provides a pre-configured development environment with common langu - **GitHub CLI (gh)** - **Homebrew** +## Modular Architecture + +The sandbox is split into modular components, allowing you to use only what you need: + +| Image | Description | Contents | +|-------|-------------|----------| +| `konard/sandbox` | Full sandbox (all languages) | Everything below | +| `konard/sandbox-essentials` | Essentials only | git, gh, glab, Node.js, Bun, Deno, git identity tools | + +### Per-Language Install Scripts & Dockerfiles + +Each language has its own standalone `install.sh` and `Dockerfile` under `ubuntu/24.04//`: + +| Language | Directory | Key Tools | +|----------|-----------|-----------| +| JavaScript/TypeScript | `ubuntu/24.04/js/` | NVM, Node.js, Bun, Deno, npm | +| Python | `ubuntu/24.04/python/` | Pyenv, latest stable Python | +| Go | `ubuntu/24.04/go/` | Latest stable Go | +| Rust | `ubuntu/24.04/rust/` | rustup, Cargo | +| Java | `ubuntu/24.04/java/` | SDKMAN, Eclipse Temurin 21 | +| Kotlin | `ubuntu/24.04/kotlin/` | SDKMAN, Kotlin | +| .NET | `ubuntu/24.04/dotnet/` | .NET SDK 8.0 | +| R | `ubuntu/24.04/r/` | R base | +| Ruby | `ubuntu/24.04/ruby/` | rbenv, latest Ruby 3.x | +| PHP | `ubuntu/24.04/php/` | Homebrew, PHP 8.3 | +| Perl | `ubuntu/24.04/perl/` | Perlbrew, latest Perl | +| Swift | `ubuntu/24.04/swift/` | Swift 6.x | +| Lean | `ubuntu/24.04/lean/` | elan, Lean prover | +| Rocq/Coq | `ubuntu/24.04/rocq/` | Opam, Rocq prover | +| C/C++ | `ubuntu/24.04/cpp/` | CMake, Clang, LLVM, LLD | +| Assembly | `ubuntu/24.04/assembly/` | NASM, FASM (x86_64) | + +Each install script can be run standalone on Ubuntu 24.04: + +```bash +# Install just Go on your Ubuntu 24.04 system +curl -fsSL https://raw.githubusercontent.com/link-foundation/sandbox/main/ubuntu/24.04/go/install.sh | bash +``` + ## Usage -### Pull the image +### Pull the full image ```bash docker pull ghcr.io/link-foundation/sandbox:latest ``` +### Pull the essentials image + +```bash +docker pull ghcr.io/link-foundation/sandbox-essentials:latest +``` + ### Run interactively ```bash diff --git a/ubuntu/24.04/essentials-sandbox/Dockerfile b/ubuntu/24.04/essentials-sandbox/Dockerfile index 031f866..2ebdabe 100644 --- a/ubuntu/24.04/essentials-sandbox/Dockerfile +++ b/ubuntu/24.04/essentials-sandbox/Dockerfile @@ -4,22 +4,22 @@ FROM ubuntu:24.04 # Contains minimal tooling for gh-setup-git-identity and glab-setup-git-identity # Includes: git, gh, glab, bun, deno, nvm/node, npm # Published as: konard/sandbox-essentials +# +# Build from repository root: +# docker build -f ubuntu/24.04/essentials-sandbox/Dockerfile -t sandbox-essentials . ENV DEBIAN_FRONTEND=noninteractive WORKDIR /workspace -# Copy and run the essentials installation script -COPY install.sh /tmp/install.sh -COPY ../common.sh /tmp/common.sh +# Copy install scripts from repo root context +COPY ubuntu/24.04/common.sh /tmp/common.sh +COPY ubuntu/24.04/essentials-sandbox/install.sh /tmp/install.sh +COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /tmp/install.sh && \ +RUN chmod +x /tmp/install.sh /tmp/common.sh /usr/local/bin/entrypoint.sh && \ DOCKER_BUILD=1 bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh -# Copy entrypoint script -COPY ../../scripts/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh - USER sandbox WORKDIR /home/sandbox diff --git a/ubuntu/24.04/full-sandbox/Dockerfile b/ubuntu/24.04/full-sandbox/Dockerfile index aca36de..03d346a 100644 --- a/ubuntu/24.04/full-sandbox/Dockerfile +++ b/ubuntu/24.04/full-sandbox/Dockerfile @@ -4,15 +4,18 @@ FROM ubuntu:24.04 # Contains all language runtimes and development tools # Built on top of essentials-sandbox install script # Published as: konard/sandbox (or konard/sandbox-full) +# +# Build from repository root: +# docker build -f ubuntu/24.04/full-sandbox/Dockerfile -t sandbox-full . ENV DEBIAN_FRONTEND=noninteractive WORKDIR /workspace -# Copy the entire ubuntu/24.04 directory for access to all install scripts -COPY . /tmp/sandbox-scripts/ - -# Copy entrypoint script -COPY ../../scripts/entrypoint.sh /usr/local/bin/entrypoint.sh +# Copy all install scripts from repo root context +COPY ubuntu/24.04/common.sh /tmp/sandbox-scripts/common.sh +COPY ubuntu/24.04/essentials-sandbox/install.sh /tmp/sandbox-scripts/essentials-sandbox/install.sh +COPY ubuntu/24.04/full-sandbox/install.sh /tmp/sandbox-scripts/full-sandbox/install.sh +COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh # Make scripts executable and run the full installation RUN chmod +x /tmp/sandbox-scripts/full-sandbox/install.sh && \ From 33c2a3251c64dbf07863ee96bd57ab8fe30611a8 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 17:51:08 +0100 Subject: [PATCH 05/13] Revert "Initial commit with task details" This reverts commit 7fb3fbace764d2a1825c3c4d05702745a038e728. --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 9152c89..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/link-foundation/sandbox/issues/37 -Your prepared branch: issue-37-06e880b28d2a -Your prepared working directory: /tmp/gh-issue-solver-1769963677082 - -Proceed. From 99e54e752cb7fe674a1fed0cddcc8c387aee467f Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 19:46:35 +0100 Subject: [PATCH 06/13] =?UTF-8?q?Layered=20image=20architecture:=20JS=20?= =?UTF-8?q?=E2=86=92=20essentials=20=E2=86=92=20full=20sandbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the modular sandbox to use a proper layered Docker image hierarchy where each layer builds on top of the previous: - JS sandbox (konard/sandbox-js): standalone Node.js, Bun, Deno - Essentials sandbox (konard/sandbox-essentials): builds on JS, adds git, gh, glab, gh-setup-git-identity, glab-setup-git-identity - Full sandbox (konard/sandbox): builds on essentials, adds all remaining languages CI/CD workflow updated with per-image change detection: - Only rebuild images whose scripts/Dockerfiles actually changed - Reuse latest published images for unchanged base layers - JS sandbox amd64/arm64 built in parallel - Essentials waits for JS, then builds in parallel per arch - Full sandbox waits for essentials, then builds per arch - Each image gets its own multi-arch manifest Co-Authored-By: Claude Opus 4.5 --- .github/workflows/release.yml | 594 +++++++++++++++++++-- ARCHITECTURE.md | 68 ++- README.md | 17 +- ubuntu/24.04/essentials-sandbox/Dockerfile | 22 +- ubuntu/24.04/essentials-sandbox/install.sh | 74 +-- ubuntu/24.04/full-sandbox/Dockerfile | 14 +- ubuntu/24.04/full-sandbox/install.sh | 34 +- ubuntu/24.04/js/Dockerfile | 11 +- 8 files changed, 682 insertions(+), 152 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index beb0894..b552426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -202,13 +202,14 @@ jobs: echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "bumped=true" >> $GITHUB_OUTPUT - # === DETECT CHANGES === + # === DETECT CHANGES (per-image granularity) === detect-changes: runs-on: ubuntu-24.04 needs: [apply-changesets, version-bump] # Always run, but wait for version jobs if they're running if: always() && (needs.apply-changesets.result == 'success' || needs.apply-changesets.result == 'skipped') && (needs.version-bump.result == 'success' || needs.version-bump.result == 'skipped') outputs: + # Legacy change detection docker-changed: ${{ steps.changes.outputs.docker }} scripts-changed: ${{ steps.changes.outputs.scripts }} ubuntu-changed: ${{ steps.changes.outputs.ubuntu }} @@ -216,6 +217,11 @@ jobs: version-changed: ${{ steps.changes.outputs.version }} should-build: ${{ steps.should-build.outputs.result }} version: ${{ steps.version.outputs.version }} + # Per-image change detection + js-changed: ${{ steps.image-changes.outputs.js }} + essentials-changed: ${{ steps.image-changes.outputs.essentials }} + full-changed: ${{ steps.image-changes.outputs.full }} + common-changed: ${{ steps.image-changes.outputs.common }} steps: - uses: actions/checkout@v4 @@ -251,6 +257,8 @@ jobs: # Get changed files CHANGED_FILES=$(git diff --name-only "$BASE_SHA" HEAD 2>/dev/null || git diff --name-only HEAD~1 HEAD) + echo "Changed files:" + echo "$CHANGED_FILES" # Check for Docker-related changes if echo "$CHANGED_FILES" | grep -qE '^Dockerfile$'; then @@ -287,6 +295,42 @@ jobs: echo "version=false" >> $GITHUB_OUTPUT fi + # Save changed files for per-image detection + echo "$CHANGED_FILES" > /tmp/changed-files.txt + + - name: Detect per-image changes + id: image-changes + run: | + CHANGED_FILES=$(cat /tmp/changed-files.txt) + + # JS sandbox changes + if echo "$CHANGED_FILES" | grep -qE '^ubuntu/24\.04/js/'; then + echo "js=true" >> $GITHUB_OUTPUT + else + echo "js=false" >> $GITHUB_OUTPUT + fi + + # Essentials sandbox changes + if echo "$CHANGED_FILES" | grep -qE '^ubuntu/24\.04/essentials-sandbox/'; then + echo "essentials=true" >> $GITHUB_OUTPUT + else + echo "essentials=false" >> $GITHUB_OUTPUT + fi + + # Full sandbox changes (full-sandbox dir, root Dockerfile, or scripts) + if echo "$CHANGED_FILES" | grep -qE '^(ubuntu/24\.04/full-sandbox/|Dockerfile$|scripts/)'; then + echo "full=true" >> $GITHUB_OUTPUT + else + echo "full=false" >> $GITHUB_OUTPUT + fi + + # Common.sh changes (affects all images) + if echo "$CHANGED_FILES" | grep -qE '^ubuntu/24\.04/common\.sh$'; then + echo "common=true" >> $GITHUB_OUTPUT + else + echo "common=false" >> $GITHUB_OUTPUT + fi + - name: Determine if build is needed id: should-build run: | @@ -422,16 +466,476 @@ jobs: echo "" echo "=== All tests completed ===" - # === BUILD AND PUSH DOCKER IMAGE (MAIN) === - docker-build-push: + # === BUILD JS SANDBOX (amd64) === + # JS sandbox is the base layer - built first, other images depend on it + build-js-amd64: runs-on: ubuntu-24.04 needs: [detect-changes] + if: | + always() && + needs.detect-changes.result == 'success' && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) && + ( + needs.detect-changes.outputs.js-changed == 'true' || + needs.detect-changes.outputs.common-changed == 'true' || + needs.detect-changes.outputs.version-changed == 'true' || + github.event_name == 'workflow_dispatch' + ) + permissions: + contents: read + packages: write + outputs: + built: ${{ steps.result.outputs.built }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push JS sandbox (amd64) + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/js/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:latest-amd64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:${{ steps.version.outputs.version }}-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-js:${{ steps.version.outputs.version }}-amd64 + provenance: false + cache-from: type=gha,scope=js-amd64 + cache-to: type=gha,scope=js-amd64,mode=max + + - name: Mark as built + id: result + run: echo "built=true" >> $GITHUB_OUTPUT + + # === BUILD JS SANDBOX (arm64) === + build-js-arm64: + runs-on: ubuntu-24.04-arm + timeout-minutes: 120 + needs: [detect-changes] + if: | + always() && + needs.detect-changes.result == 'success' && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) && + ( + needs.detect-changes.outputs.js-changed == 'true' || + needs.detect-changes.outputs.common-changed == 'true' || + needs.detect-changes.outputs.version-changed == 'true' || + github.event_name == 'workflow_dispatch' + ) + permissions: + contents: read + packages: write + outputs: + built: ${{ steps.result.outputs.built }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push JS sandbox (arm64) + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/js/Dockerfile + platforms: linux/arm64 + push: true + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:latest-arm64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:${{ steps.version.outputs.version }}-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-js:${{ steps.version.outputs.version }}-arm64 + provenance: false + cache-from: type=gha,scope=js-arm64 + cache-to: type=gha,scope=js-arm64,mode=max + + - name: Mark as built + id: result + run: echo "built=true" >> $GITHUB_OUTPUT + + # === CREATE JS MULTI-ARCH MANIFEST === + js-manifest: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-js-amd64, build-js-arm64] + if: | + always() && + needs.detect-changes.result == 'success' && + needs.build-js-amd64.result == 'success' && + needs.build-js-arm64.result == 'success' + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create and push JS multi-arch manifests + run: | + VERSION="${{ steps.version.outputs.version }}" + + # GHCR + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:latest \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:latest-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:latest-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:latest + + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:${VERSION} \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:${VERSION}-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:${VERSION}-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-js:${VERSION} + + # Docker Hub + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest + + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}-js:${VERSION} \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-js:${VERSION}-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-js:${VERSION}-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}-js:${VERSION} + + echo "JS sandbox multi-arch manifests pushed for latest and ${VERSION}" + + # === BUILD ESSENTIALS SANDBOX (amd64) === + # Built on top of JS sandbox - waits for JS to complete (if JS was rebuilt) + build-essentials-amd64: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-js-amd64] + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.build-js-amd64.result == 'success' || needs.build-js-amd64.result == 'skipped') && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) && + ( + needs.detect-changes.outputs.js-changed == 'true' || + needs.detect-changes.outputs.essentials-changed == 'true' || + needs.detect-changes.outputs.common-changed == 'true' || + needs.detect-changes.outputs.scripts-changed == 'true' || + needs.detect-changes.outputs.version-changed == 'true' || + github.event_name == 'workflow_dispatch' + ) + permissions: + contents: read + packages: write + outputs: + built: ${{ steps.result.outputs.built }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine JS base image + id: js-base + run: | + # Use freshly built JS image if JS was rebuilt, otherwise use latest + if [ "${{ needs.build-js-amd64.outputs.built }}" = "true" ]; then + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-js:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT + else + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest-amd64" >> $GITHUB_OUTPUT + fi + + - name: Build and push essentials sandbox (amd64) + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/essentials-sandbox/Dockerfile + platforms: linux/amd64 + push: true + build-args: | + JS_IMAGE=${{ steps.js-base.outputs.image }} + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:latest-amd64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-amd64 + provenance: false + cache-from: type=gha,scope=essentials-amd64 + cache-to: type=gha,scope=essentials-amd64,mode=max + + - name: Mark as built + id: result + run: echo "built=true" >> $GITHUB_OUTPUT + + # === BUILD ESSENTIALS SANDBOX (arm64) === + build-essentials-arm64: + runs-on: ubuntu-24.04-arm + timeout-minutes: 120 + needs: [detect-changes, build-js-arm64] + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.build-js-arm64.result == 'success' || needs.build-js-arm64.result == 'skipped') && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) && + ( + needs.detect-changes.outputs.js-changed == 'true' || + needs.detect-changes.outputs.essentials-changed == 'true' || + needs.detect-changes.outputs.common-changed == 'true' || + needs.detect-changes.outputs.scripts-changed == 'true' || + needs.detect-changes.outputs.version-changed == 'true' || + github.event_name == 'workflow_dispatch' + ) + permissions: + contents: read + packages: write + outputs: + built: ${{ steps.result.outputs.built }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine JS base image + id: js-base + run: | + if [ "${{ needs.build-js-arm64.outputs.built }}" = "true" ]; then + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-js:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + else + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-js:latest-arm64" >> $GITHUB_OUTPUT + fi + + - name: Build and push essentials sandbox (arm64) + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/essentials-sandbox/Dockerfile + platforms: linux/arm64 + push: true + build-args: | + JS_IMAGE=${{ steps.js-base.outputs.image }} + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:latest-arm64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-arm64 + provenance: false + cache-from: type=gha,scope=essentials-arm64 + cache-to: type=gha,scope=essentials-arm64,mode=max + + - name: Mark as built + id: result + run: echo "built=true" >> $GITHUB_OUTPUT + + # === CREATE ESSENTIALS MULTI-ARCH MANIFEST === + essentials-manifest: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-essentials-amd64, build-essentials-arm64] + if: | + always() && + needs.detect-changes.result == 'success' && + needs.build-essentials-amd64.result == 'success' && + needs.build-essentials-arm64.result == 'success' + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create and push essentials multi-arch manifests + run: | + VERSION="${{ steps.version.outputs.version }}" + + # GHCR + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:latest \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:latest-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:latest-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:latest + + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:${VERSION} \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:${VERSION}-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:${VERSION}-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-essentials:${VERSION} + + # Docker Hub + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest + + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${VERSION} \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${VERSION}-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${VERSION}-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${VERSION} + + echo "Essentials sandbox multi-arch manifests pushed for latest and ${VERSION}" + + # === BUILD AND PUSH FULL SANDBOX (MAIN - amd64) === + docker-build-push: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-essentials-amd64] # Run on push to main with changes, OR on workflow_dispatch - # Use always() to prevent implicit success() check from skipping this job - # when upstream jobs are skipped (see docs/case-studies/issue-23) if: | always() && needs.detect-changes.result == 'success' && + (needs.build-essentials-amd64.result == 'success' || needs.build-essentials-amd64.result == 'skipped') && ( (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || (github.event_name == 'workflow_dispatch') @@ -475,6 +979,15 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Determine essentials base image + id: essentials-base + run: | + if [ "${{ needs.build-essentials-amd64.outputs.built }}" = "true" ]; then + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT + else + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-amd64" >> $GITHUB_OUTPUT + fi + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -503,13 +1016,16 @@ jobs: type=sha,prefix= type=raw,value={{date 'YYYYMMDD'}} - - name: Build and push Docker image (amd64) + - name: Build and push full sandbox (amd64) id: build uses: docker/build-push-action@v5 with: context: . + file: ubuntu/24.04/full-sandbox/Dockerfile platforms: linux/amd64 push: true + build-args: | + ESSENTIALS_IMAGE=${{ steps.essentials-base.outputs.image }} tags: | ${{ steps.meta.outputs.tags }} ${{ steps.meta-amd64.outputs.tags }} @@ -520,18 +1036,14 @@ jobs: # === BUILD AND PUSH ARM64 IMAGE === # Using native ARM64 runner for optimal build performance - # See: docs/case-studies/issue-7/README.md for analysis of the previous timeout - # Reference: https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/ docker-build-push-arm64: runs-on: ubuntu-24.04-arm # Native ARM64 runner (free for public repos since Jan 2025) timeout-minutes: 120 # Safety timeout to prevent runaway builds - needs: [detect-changes, docker-build-push] - # Run on push to main with changes, OR on workflow_dispatch - # Use always() to prevent implicit success() check from skipping this job - # when upstream jobs are skipped (see docs/case-studies/issue-23) + needs: [detect-changes, build-essentials-arm64, docker-build-push] if: | always() && needs.detect-changes.result == 'success' && + (needs.build-essentials-arm64.result == 'success' || needs.build-essentials-arm64.result == 'skipped') && needs.docker-build-push.result == 'success' && ( (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || @@ -575,6 +1087,15 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Determine essentials base image + id: essentials-base + run: | + if [ "${{ needs.build-essentials-arm64.outputs.built }}" = "true" ]; then + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + else + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-arm64" >> $GITHUB_OUTPUT + fi + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -590,13 +1111,16 @@ jobs: type=sha,prefix= type=raw,value={{date 'YYYYMMDD'}} - - name: Build and push Docker image (arm64) + - name: Build and push full sandbox (arm64) id: build uses: docker/build-push-action@v5 with: context: . + file: ubuntu/24.04/full-sandbox/Dockerfile platforms: linux/arm64 push: true + build-args: | + ESSENTIALS_IMAGE=${{ steps.essentials-base.outputs.image }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} provenance: false # Prevents unknown/unknown platform in registry @@ -607,8 +1131,6 @@ jobs: docker-manifest: runs-on: ubuntu-24.04 needs: [detect-changes, docker-build-push, docker-build-push-arm64] - # Use always() to prevent implicit success() check from skipping this job - # when upstream jobs are skipped (see docs/case-studies/issue-23) if: | always() && needs.detect-changes.result == 'success' && @@ -693,9 +1215,7 @@ jobs: # === CREATE GITHUB RELEASE === create-release: runs-on: ubuntu-24.04 - needs: [detect-changes, docker-manifest] - # Use always() to prevent implicit success() check from skipping this job - # when upstream jobs are skipped (see docs/case-studies/issue-23) + needs: [detect-changes, docker-manifest, js-manifest, essentials-manifest] if: | always() && needs.detect-changes.result == 'success' && @@ -735,33 +1255,45 @@ jobs: cat > /tmp/release-notes.md << ENDOFNOTES ## Docker Images - ### GitHub Container Registry (GHCR) - - [\`${GHCR_IMAGE}:${VERSION}\`](https://github.com/${REPO}/pkgs/container/sandbox) (multi-arch) - - [\`${GHCR_IMAGE}:${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/sandbox) (AMD64) - - [\`${GHCR_IMAGE}:${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/sandbox) (ARM64) - - [\`${GHCR_IMAGE}:latest\`](https://github.com/${REPO}/pkgs/container/sandbox) (multi-arch) - - ### Docker Hub + ### Full Sandbox (konard/sandbox) - [\`${DOCKERHUB_IMAGE}:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}) (multi-arch) - [\`${DOCKERHUB_IMAGE}:${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}) (AMD64) - [\`${DOCKERHUB_IMAGE}:${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}) (ARM64) - - [\`${DOCKERHUB_IMAGE}:latest\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}) (multi-arch) - ## Quick Start + ### Essentials Sandbox (konard/sandbox-essentials) + - [\`${DOCKERHUB_IMAGE}-essentials:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials) (multi-arch) + + ### JS Sandbox (konard/sandbox-js) + - [\`${DOCKERHUB_IMAGE}-js:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js) (multi-arch) + + ### GitHub Container Registry (GHCR) + - [\`${GHCR_IMAGE}:${VERSION}\`](https://github.com/${REPO}/pkgs/container/sandbox) (multi-arch) + - [\`${GHCR_IMAGE}-essentials:${VERSION}\`](https://github.com/${REPO}/pkgs/container/sandbox-essentials) (multi-arch) + - [\`${GHCR_IMAGE}-js:${VERSION}\`](https://github.com/${REPO}/pkgs/container/sandbox-js) (multi-arch) + + ## Architecture - Pull from GHCR: - \`\`\`sh - docker pull ${GHCR_IMAGE}:${VERSION} \`\`\` + JS sandbox (konard/sandbox-js) + → Essentials sandbox (konard/sandbox-essentials) + → Full sandbox (konard/sandbox) + \`\`\` + + ## Quick Start Pull from Docker Hub: \`\`\`sh docker pull ${DOCKERHUB_IMAGE}:${VERSION} \`\`\` + Pull from GHCR: + \`\`\`sh + docker pull ${GHCR_IMAGE}:${VERSION} + \`\`\` + ## Links - - [GHCR Package](https://github.com/${REPO}/pkgs/container/sandbox) - [Docker Hub](https://hub.docker.com/r/${DOCKERHUB_IMAGE}) + - [GHCR Package](https://github.com/${REPO}/pkgs/container/sandbox) Released on ${DATE} ENDOFNOTES diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index f9958e0..769145b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -77,31 +77,44 @@ See [Case Study: Docker ARM64 Build Timeout](docs/case-studies/issue-7/README.md ## Build Pipeline +The CI/CD pipeline uses per-image change detection for efficiency. Only images whose +scripts or Dockerfiles changed are rebuilt. Unchanged images reuse the latest published version. + ``` -┌─────────────────┐ -│ detect-changes │ -│ (ubuntu-latest) │ -└────────┬────────┘ +┌──────────────────┐ +│ detect-changes │ (per-image granularity) +│ (ubuntu-latest) │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ ← built first (base layer) +│ build-js │ +│ (amd64 + arm64) │ (parallel per arch) +└────────┬─────────┘ + │ + ▼ +┌────────────────────────┐ +│ build-essentials │ ← built on JS sandbox +│ (amd64 + arm64) │ (parallel per arch) +└────────┬───────────────┘ │ - ├─────────────────────────┐ - │ │ - ▼ ▼ -┌─────────────────┐ ┌─────────────────────┐ -│docker-build-push│ │docker-build-push- │ -│ (amd64) │ │ arm64 │ -│ ubuntu-latest │ │ ubuntu-24.04-arm │ -└────────┬────────┘ │ (NATIVE - NO EMU) │ - │ └──────────┬──────────┘ - │ │ - └──────────┬──────────────┘ - │ - ▼ - ┌─────────────────┐ - │ docker-manifest │ - │ (multi-arch) │ - └─────────────────┘ + ▼ +┌────────────────────────┐ +│ docker-build-push │ ← full sandbox built on essentials +│ (amd64 + arm64) │ (sequential: amd64 first, then arm64) +└────────┬───────────────┘ + │ + ▼ +┌──────────────────┐ +│ docker-manifest │ ← multi-arch manifests for js, essentials, full +│ (multi-arch) │ +└──────────────────┘ ``` +Each layer only rebuilds if its own scripts/Dockerfiles changed, or if a dependency +(common.sh, base image) changed. When JS sandbox hasn't changed, essentials and full +sandbox reuse the latest published JS image. + ## File Structure ``` @@ -195,9 +208,16 @@ The sandbox follows a layered modular architecture: │ │ essentials-sandbox │ │ │ │ (konard/sandbox-essentials) │ │ │ │ │ │ -│ │ git, gh, glab, Node.js, Bun, │ │ -│ │ Deno, gh-setup-git-identity, │ │ -│ │ glab-setup-git-identity │ │ +│ │ ┌───────────────────────────┐ │ │ +│ │ │ JS sandbox │ │ │ +│ │ │ (konard/sandbox-js) │ │ │ +│ │ │ │ │ │ +│ │ │ Node.js, Bun, Deno, npm │ │ │ +│ │ └───────────────────────────┘ │ │ +│ │ │ │ +│ │ + git, gh, glab, │ │ +│ │ gh-setup-git-identity, │ │ +│ │ glab-setup-git-identity │ │ │ └─────────────────────────────────────┘ │ │ │ │ + Python, Go, Rust, Java, Kotlin, .NET, │ diff --git a/README.md b/README.md index e4f05dc..817b10c 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,19 @@ This sandbox provides a pre-configured development environment with common langu ## Modular Architecture -The sandbox is split into modular components, allowing you to use only what you need: +The sandbox is split into layered modular components, allowing you to use only what you need: + +``` +JS sandbox (konard/sandbox-js) + → Essentials sandbox (konard/sandbox-essentials) + → Full sandbox (konard/sandbox) +``` | Image | Description | Contents | |-------|-------------|----------| | `konard/sandbox` | Full sandbox (all languages) | Everything below | -| `konard/sandbox-essentials` | Essentials only | git, gh, glab, Node.js, Bun, Deno, git identity tools | +| `konard/sandbox-essentials` | Essentials (git identity tools) | JS sandbox + git, gh, glab, gh-setup-git-identity, glab-setup-git-identity | +| `konard/sandbox-js` | JavaScript only | Node.js, Bun, Deno, npm | ### Per-Language Install Scripts & Dockerfiles @@ -87,6 +94,12 @@ curl -fsSL https://raw.githubusercontent.com/link-foundation/sandbox/main/ubuntu docker pull ghcr.io/link-foundation/sandbox:latest ``` +### Pull the JS image + +```bash +docker pull ghcr.io/link-foundation/sandbox-js:latest +``` + ### Pull the essentials image ```bash diff --git a/ubuntu/24.04/essentials-sandbox/Dockerfile b/ubuntu/24.04/essentials-sandbox/Dockerfile index 2ebdabe..45fc851 100644 --- a/ubuntu/24.04/essentials-sandbox/Dockerfile +++ b/ubuntu/24.04/essentials-sandbox/Dockerfile @@ -1,17 +1,22 @@ -FROM ubuntu:24.04 +ARG JS_IMAGE=konard/sandbox-js:latest +FROM ${JS_IMAGE} # Essentials Sandbox Docker image -# Contains minimal tooling for gh-setup-git-identity and glab-setup-git-identity -# Includes: git, gh, glab, bun, deno, nvm/node, npm +# Built on top of the JS sandbox, adds git identity tools (gh, glab) # Published as: konard/sandbox-essentials # # Build from repository root: # docker build -f ubuntu/24.04/essentials-sandbox/Dockerfile -t sandbox-essentials . +# +# Build with specific JS image: +# docker build -f ubuntu/24.04/essentials-sandbox/Dockerfile \ +# --build-arg JS_IMAGE=konard/sandbox-js:latest -t sandbox-essentials . + +USER root ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace -# Copy install scripts from repo root context +# Copy install script COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/essentials-sandbox/install.sh /tmp/install.sh COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh @@ -23,11 +28,8 @@ RUN chmod +x /tmp/install.sh /tmp/common.sh /usr/local/bin/entrypoint.sh && \ USER sandbox WORKDIR /home/sandbox -# Environment variables for JS runtimes -ENV NVM_DIR="/home/sandbox/.nvm" -ENV BUN_INSTALL="/home/sandbox/.bun" -ENV DENO_INSTALL="/home/sandbox/.deno" -ENV PATH="/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:${PATH}" +# Environment variables inherited from JS sandbox base image +# Additional ones for essentials are not needed (gh, glab are system-wide) SHELL ["/bin/bash", "-c"] diff --git a/ubuntu/24.04/essentials-sandbox/install.sh b/ubuntu/24.04/essentials-sandbox/install.sh index 71a467d..6c59be7 100644 --- a/ubuntu/24.04/essentials-sandbox/install.sh +++ b/ubuntu/24.04/essentials-sandbox/install.sh @@ -2,10 +2,12 @@ set -euo pipefail # Essentials Sandbox Installation Script -# Installs minimal tooling required for gh-setup-git-identity and glab-setup-git-identity -# Components: system essentials, GitHub CLI, GitLab CLI, JavaScript (Bun, NVM/Node, Deno) +# Installs tooling required for gh-setup-git-identity and glab-setup-git-identity +# on top of an existing JS sandbox (Node.js, Bun, Deno already available). # -# This is the base image that full-sandbox builds upon. +# Components added: system essentials, GitHub CLI, GitLab CLI, git identity tools +# +# This is the layer between JS sandbox and full-sandbox. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then @@ -22,7 +24,7 @@ else maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } fi -log_step "Installing Essentials Sandbox" +log_step "Installing Essentials Sandbox (on top of JS sandbox)" # --- Pre-flight Checks --- log_step "Running pre-flight checks" @@ -43,7 +45,7 @@ fi log_success "Pre-flight checks passed" -# --- Create sandbox user --- +# --- Ensure sandbox user exists --- log_step "Setting up sandbox user" if id "sandbox" &>/dev/null; then log_info "sandbox user already exists." @@ -101,10 +103,10 @@ else log_success "GitLab CLI already installed" fi -# --- JavaScript runtimes (as sandbox user) --- -log_step "Installing JavaScript runtimes" +# --- Install git identity tools as sandbox user (using Bun from JS sandbox) --- +log_step "Installing git identity tools" -cat > /tmp/essentials-js-setup.sh <<'EOF_JS' +cat > /tmp/essentials-identity-setup.sh <<'EOF_IDENTITY' #!/usr/bin/env bash set -euo pipefail @@ -112,11 +114,7 @@ log_info() { echo "[*] $1"; } log_success() { echo "[✓] $1"; } command_exists() { command -v "$1" &>/dev/null; } -# Bun -if ! command_exists bun; then - log_info "Installing Bun..." - curl -fsSL https://bun.sh/install | bash -fi +# Load Bun from JS sandbox export BUN_INSTALL="$HOME/.bun" export PATH="$BUN_INSTALL/bin:$PATH" @@ -136,41 +134,7 @@ if command_exists bun; then fi fi -# Deno -if ! command_exists deno; then - log_info "Installing Deno..." - curl -fsSL https://deno.land/install.sh | sh -s -- -y - export DENO_INSTALL="$HOME/.deno" - export PATH="$DENO_INSTALL/bin:$PATH" - if ! grep -q 'DENO_INSTALL' "$HOME/.bashrc" 2>/dev/null; then - { - echo '' - echo '# Deno configuration' - echo 'export DENO_INSTALL="$HOME/.deno"' - echo 'export PATH="$DENO_INSTALL/bin:$PATH"' - } >> "$HOME/.bashrc" - fi -fi - -# NVM + Node.js -if [ ! -d "$HOME/.nvm" ]; then - log_info "Installing NVM..." - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash -fi - -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - -if ! nvm ls 20 2>/dev/null | grep -q 'v20'; then - log_info "Installing Node.js 20..." - nvm install 20 -fi -nvm use 20 - -log_info "Updating npm..." -npm install -g npm@latest --no-fund --silent - -# Git setup +# Git setup if gh is authenticated if gh auth status &>/dev/null; then log_info "Configuring Git with GitHub identity..." git config --global user.name "$(gh api user --jq .login)" @@ -178,16 +142,16 @@ if gh auth status &>/dev/null; then gh auth setup-git fi -log_success "Essentials sandbox user setup complete" -EOF_JS +log_success "Essentials identity tools setup complete" +EOF_IDENTITY -chmod +x /tmp/essentials-js-setup.sh +chmod +x /tmp/essentials-identity-setup.sh if [ "$EUID" -eq 0 ]; then - su - sandbox -c "bash /tmp/essentials-js-setup.sh" + su - sandbox -c "bash /tmp/essentials-identity-setup.sh" else - sudo -i -u sandbox bash /tmp/essentials-js-setup.sh + sudo -i -u sandbox bash /tmp/essentials-identity-setup.sh fi -rm -f /tmp/essentials-js-setup.sh +rm -f /tmp/essentials-identity-setup.sh # --- Cleanup --- log_step "Cleaning up" @@ -197,4 +161,4 @@ maybe_sudo apt-get autoremove -y maybe_sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* log_step "Essentials Sandbox setup complete!" -log_success "Installed: git, gh, glab, bun, deno, nvm/node, gh-setup-git-identity, glab-setup-git-identity" +log_success "Added on top of JS sandbox: git, gh, glab, gh-setup-git-identity, glab-setup-git-identity" diff --git a/ubuntu/24.04/full-sandbox/Dockerfile b/ubuntu/24.04/full-sandbox/Dockerfile index 03d346a..7430bfa 100644 --- a/ubuntu/24.04/full-sandbox/Dockerfile +++ b/ubuntu/24.04/full-sandbox/Dockerfile @@ -1,26 +1,30 @@ -FROM ubuntu:24.04 +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} # Full Sandbox Docker image # Contains all language runtimes and development tools -# Built on top of essentials-sandbox install script +# Built on top of essentials-sandbox (which is built on top of JS sandbox) # Published as: konard/sandbox (or konard/sandbox-full) # # Build from repository root: # docker build -f ubuntu/24.04/full-sandbox/Dockerfile -t sandbox-full . +# +# Build with specific essentials image: +# docker build -f ubuntu/24.04/full-sandbox/Dockerfile \ +# --build-arg ESSENTIALS_IMAGE=konard/sandbox-essentials:latest -t sandbox-full . + +USER root ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace # Copy all install scripts from repo root context COPY ubuntu/24.04/common.sh /tmp/sandbox-scripts/common.sh -COPY ubuntu/24.04/essentials-sandbox/install.sh /tmp/sandbox-scripts/essentials-sandbox/install.sh COPY ubuntu/24.04/full-sandbox/install.sh /tmp/sandbox-scripts/full-sandbox/install.sh COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh # Make scripts executable and run the full installation RUN chmod +x /tmp/sandbox-scripts/full-sandbox/install.sh && \ chmod +x /tmp/sandbox-scripts/common.sh && \ - chmod +x /tmp/sandbox-scripts/essentials-sandbox/install.sh && \ chmod +x /usr/local/bin/entrypoint.sh && \ DOCKER_BUILD=1 bash /tmp/sandbox-scripts/full-sandbox/install.sh && \ rm -rf /tmp/sandbox-scripts diff --git a/ubuntu/24.04/full-sandbox/install.sh b/ubuntu/24.04/full-sandbox/install.sh index 3926503..54e774e 100644 --- a/ubuntu/24.04/full-sandbox/install.sh +++ b/ubuntu/24.04/full-sandbox/install.sh @@ -2,10 +2,12 @@ set -euo pipefail # Full Sandbox Installation Script -# Installs all supported language runtimes and development tools. -# This is the complete sandbox - equivalent to the original ubuntu-24-server-install.sh +# Installs all additional language runtimes and development tools +# on top of the essentials-sandbox (which already includes JS + git identity tools). +# +# Architecture: +# JS sandbox → essentials-sandbox → full-sandbox (this script) # -# Architecture: Runs the essentials-sandbox install first, then adds all languages. # Each language installer is a standalone script under ubuntu/24.04//install.sh. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -23,21 +25,11 @@ else maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } fi -log_step "Installing Full Sandbox" - -# --- Step 1: Run essentials-sandbox install --- -log_step "Phase 1: Installing essentials sandbox" -if [ -f "$SCRIPT_DIR/../essentials-sandbox/install.sh" ]; then - bash "$SCRIPT_DIR/../essentials-sandbox/install.sh" -else - log_error "essentials-sandbox/install.sh not found at $SCRIPT_DIR/../essentials-sandbox/install.sh" - exit 1 -fi +log_step "Installing Full Sandbox (on top of essentials)" -# --- Step 2: Install additional system packages --- -log_step "Phase 2: Installing additional system packages" +# --- Install additional system packages --- +log_step "Installing additional system packages" -# Re-source common.sh after essentials may have modified apt maybe_sudo apt update -y || true # .NET SDK @@ -79,8 +71,8 @@ maybe_sudo apt install -y \ libffi-dev liblzma-dev log_success "Python build dependencies installed" -# --- Step 3: Prepare Homebrew directory --- -log_step "Phase 3: Preparing Homebrew directory" +# --- Prepare Homebrew directory --- +log_step "Preparing Homebrew directory" if [ ! -d /home/linuxbrew/.linuxbrew ]; then maybe_sudo mkdir -p /home/linuxbrew/.linuxbrew if id "sandbox" &>/dev/null; then @@ -92,8 +84,8 @@ else fi fi -# --- Step 4: Install all language runtimes as sandbox user --- -log_step "Phase 4: Installing language runtimes as sandbox user" +# --- Install all language runtimes as sandbox user --- +log_step "Installing language runtimes as sandbox user" cat > /tmp/full-sandbox-user-setup.sh <<'EOF_FULL_SETUP' #!/usr/bin/env bash @@ -106,7 +98,7 @@ log_note() { echo "[i] $1"; } log_step() { echo "==> $1"; } command_exists() { command -v "$1" &>/dev/null; } -# Ensure JS tools are available (installed by essentials) +# Ensure JS tools are available (installed by essentials/JS sandbox) export BUN_INSTALL="$HOME/.bun" export DENO_INSTALL="$HOME/.deno" export NVM_DIR="$HOME/.nvm" diff --git a/ubuntu/24.04/js/Dockerfile b/ubuntu/24.04/js/Dockerfile index b52e405..035acf0 100644 --- a/ubuntu/24.04/js/Dockerfile +++ b/ubuntu/24.04/js/Dockerfile @@ -1,8 +1,11 @@ FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/js/Dockerfile -t sandbox-js . # JavaScript/TypeScript sandbox: Node.js (NVM), Bun, Deno # Includes: gh-setup-git-identity, glab-setup-git-identity +# Published as: konard/sandbox-js +# +# Build from repository root: +# docker build -f ubuntu/24.04/js/Dockerfile -t sandbox-js . ENV DEBIAN_FRONTEND=noninteractive WORKDIR /workspace @@ -18,11 +21,11 @@ RUN useradd -m -s /bin/bash sandbox && \ usermod -aG sudo sandbox # Copy and run install script +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/js/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh + rm -f /tmp/install.sh /tmp/common.sh USER sandbox WORKDIR /home/sandbox From 34fe68789ccfb5c6447a349f960ed732dd0804ee Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 19:47:58 +0100 Subject: [PATCH 07/13] Update root Dockerfile to use modular architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root Dockerfile now builds on top of essentials-sandbox image using the full-sandbox/install.sh script, matching the layered architecture (JS → essentials → full). Removes dependency on legacy ubuntu-24-server-install.sh script. Co-Authored-By: Claude Opus 4.5 --- Dockerfile | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index f35d065..9fe9a99 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,23 @@ -FROM ubuntu:24.04 +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} # Full Sandbox environment Docker image -# Contains common language runtimes without any AI-specific tools -# This image is meant to be used as a base for other projects that need language runtimes. -# +# Contains all language runtimes and development tools. # This is the "full-sandbox" image (konard/sandbox or konard/sandbox-full). +# +# Architecture: JS sandbox → essentials-sandbox → full-sandbox (this image) +# # For a lighter image with just essentials, see ubuntu/24.04/essentials-sandbox/Dockerfile. +# For just JavaScript, see ubuntu/24.04/js/Dockerfile. # For individual language images, see ubuntu/24.04//Dockerfile. +# +# Build from repository root: +# docker build -t sandbox . +# +# Build with specific essentials image: +# docker build --build-arg ESSENTIALS_IMAGE=konard/sandbox-essentials:latest -t sandbox . + +USER root # Set non-interactive frontend for apt ENV DEBIAN_FRONTEND=noninteractive @@ -16,23 +27,19 @@ WORKDIR /workspace # Copy the modular installation scripts COPY ubuntu/24.04/common.sh /tmp/sandbox-scripts/common.sh -COPY ubuntu/24.04/essentials-sandbox/install.sh /tmp/sandbox-scripts/essentials-sandbox/install.sh COPY ubuntu/24.04/full-sandbox/install.sh /tmp/sandbox-scripts/full-sandbox/install.sh -# Copy the legacy installation script (used by full-sandbox/install.sh) -COPY scripts/ubuntu-24-server-install.sh /tmp/ubuntu-24-server-install.sh +# Copy entrypoint script for proper environment initialization +COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh # Make scripts executable and run the full installation # Pass DOCKER_BUILD=1 environment variable to indicate Docker build environment -RUN chmod +x /tmp/ubuntu-24-server-install.sh && \ - DOCKER_BUILD=1 bash /tmp/ubuntu-24-server-install.sh && \ - rm -f /tmp/ubuntu-24-server-install.sh && \ +RUN chmod +x /tmp/sandbox-scripts/full-sandbox/install.sh && \ + chmod +x /tmp/sandbox-scripts/common.sh && \ + chmod +x /usr/local/bin/entrypoint.sh && \ + DOCKER_BUILD=1 bash /tmp/sandbox-scripts/full-sandbox/install.sh && \ rm -rf /tmp/sandbox-scripts -# Copy entrypoint script for proper environment initialization -COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh - # Switch to sandbox user USER sandbox From 0cc493667d4c8df310dc07ca2332039880ead112 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 19:53:08 +0100 Subject: [PATCH 08/13] Fix PR test build to use layered build chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docker-build-test job now builds the full chain locally: JS → essentials → full sandbox, since the base images may not exist on registries yet during PR testing. Also adds separate test steps for JS and essentials sandboxes to verify each layer independently. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/release.yml | 85 +++++++++++++---------------------- 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b552426..ab62c8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -389,78 +389,55 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Build Docker image (test) - uses: docker/build-push-action@v5 - with: - context: . - push: false - tags: ${{ env.GHCR_IMAGE_NAME }}:test - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Test Docker image + - name: Build layered images (JS → essentials → full) run: | - echo "Building image for testing..." - docker build -t sandbox-test . + echo "=== Building JS sandbox ===" + docker build -f ubuntu/24.04/js/Dockerfile -t sandbox-js . echo "" - echo "=== Testing installed tools ===" - echo "Note: Using entrypoint script which initializes all environments" + echo "=== Building essentials sandbox (on JS) ===" + docker build -f ubuntu/24.04/essentials-sandbox/Dockerfile \ + --build-arg JS_IMAGE=sandbox-js -t sandbox-essentials . echo "" - echo "Testing Node.js..." - docker run --rm sandbox-test node --version || echo "Node.js test failed" + echo "=== Building full sandbox (on essentials) ===" + docker build --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-test . - echo "" - echo "Testing Python..." - docker run --rm sandbox-test python --version || echo "Python test failed" + - name: Test JS sandbox + run: | + echo "=== Testing JS sandbox ===" + docker run --rm sandbox-js bash -c '. $HOME/.nvm/nvm.sh && node --version' || echo "Node.js test failed" + docker run --rm sandbox-js bash -c 'export PATH=$HOME/.bun/bin:$PATH && bun --version' || echo "Bun test failed" + docker run --rm sandbox-js bash -c 'export PATH=$HOME/.deno/bin:$PATH && deno --version' || echo "Deno test failed" + echo "=== JS sandbox tests completed ===" - echo "" - echo "Testing Go..." - docker run --rm sandbox-test go version || echo "Go test failed" + - name: Test essentials sandbox + run: | + echo "=== Testing essentials sandbox ===" + docker run --rm sandbox-essentials gh --version || echo "GitHub CLI test failed" + docker run --rm sandbox-essentials glab --version || echo "GitLab CLI test failed" + docker run --rm sandbox-essentials gh-setup-git-identity --version || echo "gh-setup-git-identity test failed" + docker run --rm sandbox-essentials glab-setup-git-identity --version || echo "glab-setup-git-identity test failed" + echo "=== Essentials sandbox tests completed ===" + + - name: Test full sandbox + run: | + echo "=== Testing full sandbox ===" + echo "Note: Using entrypoint script which initializes all environments" - echo "" - echo "Testing Rust..." + docker run --rm sandbox-test node --version || echo "Node.js test failed" + docker run --rm sandbox-test python --version || echo "Python test failed" + docker run --rm sandbox-test go version || echo "Go test failed" docker run --rm sandbox-test rustc --version || echo "Rust test failed" - - echo "" - echo "Testing Java..." docker run --rm sandbox-test java -version || echo "Java test failed" - - echo "" - echo "Testing Bun..." docker run --rm sandbox-test bun --version || echo "Bun test failed" - - echo "" - echo "Testing Deno..." docker run --rm sandbox-test deno --version || echo "Deno test failed" - - echo "" - echo "Testing GitHub CLI..." docker run --rm sandbox-test gh --version || echo "GitHub CLI test failed" - - echo "" - echo "Testing GitLab CLI..." docker run --rm sandbox-test glab --version || echo "GitLab CLI test failed" - - echo "" - echo "Testing gh-setup-git-identity..." docker run --rm sandbox-test gh-setup-git-identity --version || echo "gh-setup-git-identity test failed" - - echo "" - echo "Testing glab-setup-git-identity..." docker run --rm sandbox-test glab-setup-git-identity --version || echo "glab-setup-git-identity test failed" - - echo "" - echo "Testing Lean..." docker run --rm sandbox-test lean --version || echo "Lean test failed" - - echo "" - echo "Testing Perl..." docker run --rm sandbox-test perl --version || echo "Perl test failed" - - echo "" - echo "Testing PHP..." docker run --rm sandbox-test php --version || echo "PHP test failed" echo "" From 697bbec3195e01c39eaa95e218cb949eee06b1bc Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 21:03:47 +0100 Subject: [PATCH 09/13] Deduplicate dependencies and fix JS sandbox Bun install - Add unzip to JS Dockerfile (required by Bun installer) - Move common dev libraries (libssl-dev, zlib1g-dev, libyaml-dev, etc.) from full-sandbox to essentials-sandbox to avoid duplication - Remove gh/glab-setup-git-identity from JS install.sh (belongs in essentials only, JS should be standalone without essentials overhead) - Move bubblewrap install from user-level heredoc to system-level in full-sandbox Co-Authored-By: Claude Opus 4.5 --- ubuntu/24.04/essentials-sandbox/install.sh | 14 +++++++++++++- ubuntu/24.04/full-sandbox/install.sh | 20 +++++++------------- ubuntu/24.04/js/Dockerfile | 4 ++-- ubuntu/24.04/js/install.sh | 22 ---------------------- 4 files changed, 22 insertions(+), 38 deletions(-) diff --git a/ubuntu/24.04/essentials-sandbox/install.sh b/ubuntu/24.04/essentials-sandbox/install.sh index 6c59be7..22d93d5 100644 --- a/ubuntu/24.04/essentials-sandbox/install.sh +++ b/ubuntu/24.04/essentials-sandbox/install.sh @@ -70,7 +70,19 @@ for pair in "microsoft-edge:microsoft-edge-stable" "google-chrome:google-chrome- done maybe_sudo apt update -y || true -maybe_sudo apt install -y wget curl unzip zip git sudo ca-certificates gnupg build-essential expect screen + +# Core system tools +maybe_sudo apt install -y \ + wget curl unzip zip git sudo ca-certificates gnupg \ + build-essential expect screen + +# Common development libraries used by multiple language runtimes +# (Python, Ruby, Rust, Go, etc. all benefit from these) +maybe_sudo apt install -y \ + libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ + libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \ + libffi-dev liblzma-dev libyaml-dev + log_success "System prerequisites installed" # --- GitHub CLI --- diff --git a/ubuntu/24.04/full-sandbox/install.sh b/ubuntu/24.04/full-sandbox/install.sh index 54e774e..b8e71ca 100644 --- a/ubuntu/24.04/full-sandbox/install.sh +++ b/ubuntu/24.04/full-sandbox/install.sh @@ -58,18 +58,13 @@ log_info "Installing R statistical language..." maybe_sudo apt install -y r-base log_success "R language installed" -# Ruby build dependencies -log_info "Installing Ruby build dependencies..." -maybe_sudo apt install -y libyaml-dev -log_success "Ruby build dependencies installed" - -# Python build dependencies -log_info "Installing Python build dependencies..." -maybe_sudo apt install -y \ - libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ - libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \ - libffi-dev liblzma-dev -log_success "Python build dependencies installed" +# Note: Common build dependencies (build-essential, libssl-dev, zlib1g-dev, +# libyaml-dev, etc.) are already installed in the essentials-sandbox layer. + +# Bubblewrap (needed by Rocq/Opam) +log_info "Installing bubblewrap..." +maybe_sudo apt install -y bubblewrap +log_success "Bubblewrap installed" # --- Prepare Homebrew directory --- log_step "Preparing Homebrew directory" @@ -234,7 +229,6 @@ fi # --- Rocq/Coq (Opam) --- log_step "Installing Rocq/Coq" if ! command_exists opam; then - sudo apt install -y bubblewrap || true bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --no-backup" <<< "y" || { sudo apt install -y opam || true } diff --git a/ubuntu/24.04/js/Dockerfile b/ubuntu/24.04/js/Dockerfile index 035acf0..c89e974 100644 --- a/ubuntu/24.04/js/Dockerfile +++ b/ubuntu/24.04/js/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:24.04 # JavaScript/TypeScript sandbox: Node.js (NVM), Bun, Deno -# Includes: gh-setup-git-identity, glab-setup-git-identity +# Standalone JS image without essentials overhead # Published as: konard/sandbox-js # # Build from repository root: @@ -12,7 +12,7 @@ WORKDIR /workspace # Install minimal system prerequisites RUN apt update -y && \ - apt install -y curl git sudo ca-certificates && \ + apt install -y curl git sudo ca-certificates unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* # Create sandbox user diff --git a/ubuntu/24.04/js/install.sh b/ubuntu/24.04/js/install.sh index 94c3c44..e556509 100644 --- a/ubuntu/24.04/js/install.sh +++ b/ubuntu/24.04/js/install.sh @@ -76,26 +76,4 @@ log_info "Updating npm to latest version..." npm install -g npm@latest --no-fund --silent log_success "npm updated to latest version" -# --- gh-setup-git-identity --- -if command_exists bun; then - if ! command_exists gh-setup-git-identity; then - log_info "Installing gh-setup-git-identity..." - bun install -g gh-setup-git-identity - log_success "gh-setup-git-identity installed" - else - log_info "gh-setup-git-identity already installed." - fi -fi - -# --- glab-setup-git-identity --- -if command_exists bun; then - if ! command_exists glab-setup-git-identity; then - log_info "Installing glab-setup-git-identity..." - bun install -g glab-setup-git-identity - log_success "glab-setup-git-identity installed" - else - log_info "glab-setup-git-identity already installed." - fi -fi - log_success "JavaScript/TypeScript runtimes installation complete" From c768d159488ca90f9c2bac3303e0983157dc2e4d Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 22:06:46 +0100 Subject: [PATCH 10/13] All languages depend on essentials, full sandbox assembles via COPY --from Redesign architecture so every language image builds on top of konard/sandbox-essentials (not ubuntu:24.04), and the full sandbox (konard/sandbox) merges all language images via multi-stage COPY --from. Key changes: - All 15 language Dockerfiles now use FROM konard/sandbox-essentials - Language install.sh scripts no longer install duplicate system deps - Full sandbox Dockerfile uses COPY --from for 11 user-home languages (python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq) - System packages (.NET, R, C/C++, Assembly) still installed via apt - CI/CD uses matrix strategy for parallel language builds (amd64+arm64) - Full sandbox waits for all language images before assembly - bashrc configs merged from all language stages - README and ARCHITECTURE updated with new diagrams Co-Authored-By: Claude Opus 4.5 --- .github/workflows/release.yml | 440 +++++++++++++++++++++++++-- ARCHITECTURE.md | 90 +++--- Dockerfile | 161 +++++++--- README.md | 38 ++- ubuntu/24.04/assembly/Dockerfile | 23 +- ubuntu/24.04/assembly/install.sh | 2 + ubuntu/24.04/cpp/Dockerfile | 23 +- ubuntu/24.04/cpp/install.sh | 2 + ubuntu/24.04/dotnet/Dockerfile | 24 +- ubuntu/24.04/dotnet/install.sh | 2 + ubuntu/24.04/full-sandbox/Dockerfile | 157 ++++++++-- ubuntu/24.04/go/Dockerfile | 26 +- ubuntu/24.04/go/install.sh | 2 + ubuntu/24.04/java/Dockerfile | 28 +- ubuntu/24.04/java/install.sh | 2 + ubuntu/24.04/kotlin/Dockerfile | 26 +- ubuntu/24.04/kotlin/install.sh | 2 + ubuntu/24.04/lean/Dockerfile | 26 +- ubuntu/24.04/lean/install.sh | 2 + ubuntu/24.04/perl/Dockerfile | 26 +- ubuntu/24.04/perl/install.sh | 2 + ubuntu/24.04/php/Dockerfile | 30 +- ubuntu/24.04/php/install.sh | 2 + ubuntu/24.04/python/Dockerfile | 32 +- ubuntu/24.04/python/install.sh | 23 +- ubuntu/24.04/r/Dockerfile | 24 +- ubuntu/24.04/r/install.sh | 2 + ubuntu/24.04/rocq/Dockerfile | 26 +- ubuntu/24.04/rocq/install.sh | 5 +- ubuntu/24.04/ruby/Dockerfile | 26 +- ubuntu/24.04/ruby/install.sh | 7 +- ubuntu/24.04/rust/Dockerfile | 26 +- ubuntu/24.04/rust/install.sh | 2 + ubuntu/24.04/swift/Dockerfile | 28 +- ubuntu/24.04/swift/install.sh | 2 + 35 files changed, 942 insertions(+), 397 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab62c8d..3942067 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -222,6 +222,20 @@ jobs: essentials-changed: ${{ steps.image-changes.outputs.essentials }} full-changed: ${{ steps.image-changes.outputs.full }} common-changed: ${{ steps.image-changes.outputs.common }} + # Per-language change detection + python-changed: ${{ steps.language-changes.outputs.python }} + go-changed: ${{ steps.language-changes.outputs.go }} + rust-changed: ${{ steps.language-changes.outputs.rust }} + java-changed: ${{ steps.language-changes.outputs.java }} + kotlin-changed: ${{ steps.language-changes.outputs.kotlin }} + ruby-changed: ${{ steps.language-changes.outputs.ruby }} + php-changed: ${{ steps.language-changes.outputs.php }} + perl-changed: ${{ steps.language-changes.outputs.perl }} + swift-changed: ${{ steps.language-changes.outputs.swift }} + lean-changed: ${{ steps.language-changes.outputs.lean }} + rocq-changed: ${{ steps.language-changes.outputs.rocq }} + cpp-changed: ${{ steps.language-changes.outputs.cpp }} + assembly-changed: ${{ steps.language-changes.outputs.assembly }} steps: - uses: actions/checkout@v4 @@ -331,6 +345,19 @@ jobs: echo "common=false" >> $GITHUB_OUTPUT fi + - name: Detect per-language changes + id: language-changes + run: | + CHANGED_FILES=$(cat /tmp/changed-files.txt) + + for lang in python go rust java kotlin ruby php perl swift lean rocq cpp assembly; do + if echo "$CHANGED_FILES" | grep -qE "^ubuntu/24\.04/${lang}/"; then + echo "${lang}=true" >> $GITHUB_OUTPUT + else + echo "${lang}=false" >> $GITHUB_OUTPUT + fi + done + - name: Determine if build is needed id: should-build run: | @@ -389,7 +416,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Build layered images (JS → essentials → full) + - name: Build layered images (JS -> essentials -> languages -> full) run: | echo "=== Building JS sandbox ===" docker build -f ubuntu/24.04/js/Dockerfile -t sandbox-js . @@ -400,8 +427,31 @@ jobs: --build-arg JS_IMAGE=sandbox-js -t sandbox-essentials . echo "" - echo "=== Building full sandbox (on essentials) ===" - docker build --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-test . + echo "=== Building language images (on essentials) ===" + for lang in python go rust java kotlin ruby php perl swift lean rocq; do + echo "" + echo "--- Building ${lang} sandbox ---" + docker build -f "ubuntu/24.04/${lang}/Dockerfile" \ + --build-arg ESSENTIALS_IMAGE=sandbox-essentials \ + -t "sandbox-${lang}" . + done + + echo "" + echo "=== Building full sandbox (multi-stage from all language images) ===" + docker build -f ubuntu/24.04/full-sandbox/Dockerfile \ + --build-arg ESSENTIALS_IMAGE=sandbox-essentials \ + --build-arg PYTHON_IMAGE=sandbox-python \ + --build-arg GO_IMAGE=sandbox-go \ + --build-arg RUST_IMAGE=sandbox-rust \ + --build-arg JAVA_IMAGE=sandbox-java \ + --build-arg KOTLIN_IMAGE=sandbox-kotlin \ + --build-arg RUBY_IMAGE=sandbox-ruby \ + --build-arg PHP_IMAGE=sandbox-php \ + --build-arg PERL_IMAGE=sandbox-perl \ + --build-arg SWIFT_IMAGE=sandbox-swift \ + --build-arg LEAN_IMAGE=sandbox-lean \ + --build-arg ROCQ_IMAGE=sandbox-rocq \ + -t sandbox-test . - name: Test JS sandbox run: | @@ -904,15 +954,305 @@ jobs: echo "Essentials sandbox multi-arch manifests pushed for latest and ${VERSION}" + # === BUILD LANGUAGE IMAGES (amd64) === + # All language images are built in parallel on top of essentials sandbox + build-languages-amd64: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-essentials-amd64] + strategy: + fail-fast: false + matrix: + language: [python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq] + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.build-essentials-amd64.result == 'success' || needs.build-essentials-amd64.result == 'skipped') && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) + permissions: + contents: read + packages: write + + steps: + - name: Check if this language needs building + id: check-lang + run: | + LANG="${{ matrix.language }}" + LANG_CHANGED="${{ needs.detect-changes.outputs[format('{0}-changed', matrix.language)] }}" + ESSENTIALS_CHANGED="${{ needs.detect-changes.outputs.essentials-changed }}" + COMMON_CHANGED="${{ needs.detect-changes.outputs.common-changed }}" + VERSION_CHANGED="${{ needs.detect-changes.outputs.version-changed }}" + JS_CHANGED="${{ needs.detect-changes.outputs.js-changed }}" + + if [ "$LANG_CHANGED" = "true" ] || \ + [ "$ESSENTIALS_CHANGED" = "true" ] || \ + [ "$COMMON_CHANGED" = "true" ] || \ + [ "$JS_CHANGED" = "true" ] || \ + [ "$VERSION_CHANGED" = "true" ] || \ + [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "should_build=true" >> $GITHUB_OUTPUT + echo "Building ${LANG}: change detected or workflow_dispatch" + else + echo "should_build=false" >> $GITHUB_OUTPUT + echo "Skipping ${LANG}: no relevant changes" + fi + + - name: Checkout repository + if: steps.check-lang.outputs.should_build == 'true' + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + if: steps.check-lang.outputs.should_build == 'true' + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine essentials base image + if: steps.check-lang.outputs.should_build == 'true' + id: essentials-base + run: | + if [ "${{ needs.build-essentials-amd64.outputs.built }}" = "true" ]; then + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT + else + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-amd64" >> $GITHUB_OUTPUT + fi + + - name: Build and push ${{ matrix.language }} sandbox (amd64) + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/${{ matrix.language }}/Dockerfile + platforms: linux/amd64 + push: true + build-args: | + ESSENTIALS_IMAGE=${{ steps.essentials-base.outputs.image }} + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${{ matrix.language }}:latest-amd64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${{ matrix.language }}:${{ steps.version.outputs.version }}-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-${{ matrix.language }}:latest-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-${{ matrix.language }}:${{ steps.version.outputs.version }}-amd64 + provenance: false + cache-from: type=gha,scope=${{ matrix.language }}-amd64 + cache-to: type=gha,scope=${{ matrix.language }}-amd64,mode=max + + # === BUILD LANGUAGE IMAGES (arm64) === + build-languages-arm64: + runs-on: ubuntu-24.04-arm + timeout-minutes: 120 + needs: [detect-changes, build-essentials-arm64] + strategy: + fail-fast: false + matrix: + language: [python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq] + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.build-essentials-arm64.result == 'success' || needs.build-essentials-arm64.result == 'skipped') && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) + permissions: + contents: read + packages: write + + steps: + - name: Check if this language needs building + id: check-lang + run: | + LANG="${{ matrix.language }}" + LANG_CHANGED="${{ needs.detect-changes.outputs[format('{0}-changed', matrix.language)] }}" + ESSENTIALS_CHANGED="${{ needs.detect-changes.outputs.essentials-changed }}" + COMMON_CHANGED="${{ needs.detect-changes.outputs.common-changed }}" + VERSION_CHANGED="${{ needs.detect-changes.outputs.version-changed }}" + JS_CHANGED="${{ needs.detect-changes.outputs.js-changed }}" + + if [ "$LANG_CHANGED" = "true" ] || \ + [ "$ESSENTIALS_CHANGED" = "true" ] || \ + [ "$COMMON_CHANGED" = "true" ] || \ + [ "$JS_CHANGED" = "true" ] || \ + [ "$VERSION_CHANGED" = "true" ] || \ + [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "should_build=true" >> $GITHUB_OUTPUT + echo "Building ${LANG}: change detected or workflow_dispatch" + else + echo "should_build=false" >> $GITHUB_OUTPUT + echo "Skipping ${LANG}: no relevant changes" + fi + + - name: Checkout repository + if: steps.check-lang.outputs.should_build == 'true' + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + if: steps.check-lang.outputs.should_build == 'true' + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine essentials base image + if: steps.check-lang.outputs.should_build == 'true' + id: essentials-base + run: | + if [ "${{ needs.build-essentials-arm64.outputs.built }}" = "true" ]; then + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + else + echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-arm64" >> $GITHUB_OUTPUT + fi + + - name: Build and push ${{ matrix.language }} sandbox (arm64) + if: steps.check-lang.outputs.should_build == 'true' + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/${{ matrix.language }}/Dockerfile + platforms: linux/arm64 + push: true + build-args: | + ESSENTIALS_IMAGE=${{ steps.essentials-base.outputs.image }} + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${{ matrix.language }}:latest-arm64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${{ matrix.language }}:${{ steps.version.outputs.version }}-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-${{ matrix.language }}:latest-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}-${{ matrix.language }}:${{ steps.version.outputs.version }}-arm64 + provenance: false + cache-from: type=gha,scope=${{ matrix.language }}-arm64 + cache-to: type=gha,scope=${{ matrix.language }}-arm64,mode=max + + # === CREATE LANGUAGE MULTI-ARCH MANIFESTS === + languages-manifest: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-languages-amd64, build-languages-arm64] + strategy: + fail-fast: false + matrix: + language: [python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq] + if: | + always() && + needs.detect-changes.result == 'success' && + needs.build-languages-amd64.result == 'success' && + needs.build-languages-arm64.result == 'success' + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create and push ${{ matrix.language }} multi-arch manifests + run: | + VERSION="${{ steps.version.outputs.version }}" + LANG="${{ matrix.language }}" + + # GHCR + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:latest \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:latest-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:latest-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:latest + + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:${VERSION} \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:${VERSION}-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:${VERSION}-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}-${LANG}:${VERSION} + + # Docker Hub + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:latest \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:latest-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:latest-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:latest + + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:${VERSION} \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:${VERSION}-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:${VERSION}-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}-${LANG}:${VERSION} + + echo "${LANG} sandbox multi-arch manifests pushed for latest and ${VERSION}" + # === BUILD AND PUSH FULL SANDBOX (MAIN - amd64) === docker-build-push: runs-on: ubuntu-24.04 - needs: [detect-changes, build-essentials-amd64] + needs: [detect-changes, build-languages-amd64, build-essentials-amd64] # Run on push to main with changes, OR on workflow_dispatch if: | always() && needs.detect-changes.result == 'success' && (needs.build-essentials-amd64.result == 'success' || needs.build-essentials-amd64.result == 'skipped') && + (needs.build-languages-amd64.result == 'success' || needs.build-languages-amd64.result == 'skipped') && ( (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || (github.event_name == 'workflow_dispatch') @@ -956,15 +1296,29 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Determine essentials base image - id: essentials-base + - name: Determine base images + id: base-images run: | + VERSION="${{ steps.version.outputs.version }}" + + # Essentials base image if [ "${{ needs.build-essentials-amd64.outputs.built }}" = "true" ]; then - echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT + echo "essentials=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${VERSION}-amd64" >> $GITHUB_OUTPUT else - echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-amd64" >> $GITHUB_OUTPUT + echo "essentials=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-amd64" >> $GITHUB_OUTPUT fi + # Language images - use version tag if languages were built, otherwise latest + for lang in python go rust java kotlin ruby php perl swift lean rocq; do + LANG_UPPER=$(echo "$lang" | tr '[:lower:]' '[:upper:]') + # If the language matrix ran successfully, use versioned tag + if [ "${{ needs.build-languages-amd64.result }}" = "success" ]; then + echo "${lang}=${{ env.DOCKERHUB_IMAGE_NAME }}-${lang}:${VERSION}-amd64" >> $GITHUB_OUTPUT + else + echo "${lang}=${{ env.DOCKERHUB_IMAGE_NAME }}-${lang}:latest-amd64" >> $GITHUB_OUTPUT + fi + done + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -1002,7 +1356,18 @@ jobs: platforms: linux/amd64 push: true build-args: | - ESSENTIALS_IMAGE=${{ steps.essentials-base.outputs.image }} + ESSENTIALS_IMAGE=${{ steps.base-images.outputs.essentials }} + PYTHON_IMAGE=${{ steps.base-images.outputs.python }} + GO_IMAGE=${{ steps.base-images.outputs.go }} + RUST_IMAGE=${{ steps.base-images.outputs.rust }} + JAVA_IMAGE=${{ steps.base-images.outputs.java }} + KOTLIN_IMAGE=${{ steps.base-images.outputs.kotlin }} + RUBY_IMAGE=${{ steps.base-images.outputs.ruby }} + PHP_IMAGE=${{ steps.base-images.outputs.php }} + PERL_IMAGE=${{ steps.base-images.outputs.perl }} + SWIFT_IMAGE=${{ steps.base-images.outputs.swift }} + LEAN_IMAGE=${{ steps.base-images.outputs.lean }} + ROCQ_IMAGE=${{ steps.base-images.outputs.rocq }} tags: | ${{ steps.meta.outputs.tags }} ${{ steps.meta-amd64.outputs.tags }} @@ -1016,11 +1381,12 @@ jobs: docker-build-push-arm64: runs-on: ubuntu-24.04-arm # Native ARM64 runner (free for public repos since Jan 2025) timeout-minutes: 120 # Safety timeout to prevent runaway builds - needs: [detect-changes, build-essentials-arm64, docker-build-push] + needs: [detect-changes, build-languages-arm64, build-essentials-arm64, docker-build-push] if: | always() && needs.detect-changes.result == 'success' && (needs.build-essentials-arm64.result == 'success' || needs.build-essentials-arm64.result == 'skipped') && + (needs.build-languages-arm64.result == 'success' || needs.build-languages-arm64.result == 'skipped') && needs.docker-build-push.result == 'success' && ( (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || @@ -1064,15 +1430,27 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Determine essentials base image - id: essentials-base + - name: Determine base images + id: base-images run: | + VERSION="${{ steps.version.outputs.version }}" + + # Essentials base image if [ "${{ needs.build-essentials-arm64.outputs.built }}" = "true" ]; then - echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + echo "essentials=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:${VERSION}-arm64" >> $GITHUB_OUTPUT else - echo "image=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-arm64" >> $GITHUB_OUTPUT + echo "essentials=${{ env.DOCKERHUB_IMAGE_NAME }}-essentials:latest-arm64" >> $GITHUB_OUTPUT fi + # Language images + for lang in python go rust java kotlin ruby php perl swift lean rocq; do + if [ "${{ needs.build-languages-arm64.result }}" = "success" ]; then + echo "${lang}=${{ env.DOCKERHUB_IMAGE_NAME }}-${lang}:${VERSION}-arm64" >> $GITHUB_OUTPUT + else + echo "${lang}=${{ env.DOCKERHUB_IMAGE_NAME }}-${lang}:latest-arm64" >> $GITHUB_OUTPUT + fi + done + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -1097,7 +1475,18 @@ jobs: platforms: linux/arm64 push: true build-args: | - ESSENTIALS_IMAGE=${{ steps.essentials-base.outputs.image }} + ESSENTIALS_IMAGE=${{ steps.base-images.outputs.essentials }} + PYTHON_IMAGE=${{ steps.base-images.outputs.python }} + GO_IMAGE=${{ steps.base-images.outputs.go }} + RUST_IMAGE=${{ steps.base-images.outputs.rust }} + JAVA_IMAGE=${{ steps.base-images.outputs.java }} + KOTLIN_IMAGE=${{ steps.base-images.outputs.kotlin }} + RUBY_IMAGE=${{ steps.base-images.outputs.ruby }} + PHP_IMAGE=${{ steps.base-images.outputs.php }} + PERL_IMAGE=${{ steps.base-images.outputs.perl }} + SWIFT_IMAGE=${{ steps.base-images.outputs.swift }} + LEAN_IMAGE=${{ steps.base-images.outputs.lean }} + ROCQ_IMAGE=${{ steps.base-images.outputs.rocq }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} provenance: false # Prevents unknown/unknown platform in registry @@ -1192,7 +1581,7 @@ jobs: # === CREATE GITHUB RELEASE === create-release: runs-on: ubuntu-24.04 - needs: [detect-changes, docker-manifest, js-manifest, essentials-manifest] + needs: [detect-changes, docker-manifest, js-manifest, essentials-manifest, languages-manifest] if: | always() && needs.detect-changes.result == 'success' && @@ -1243,6 +1632,19 @@ jobs: ### JS Sandbox (konard/sandbox-js) - [\`${DOCKERHUB_IMAGE}-js:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js) (multi-arch) + ### Language Sandboxes + - [\`${DOCKERHUB_IMAGE}-python:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-go:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-rust:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-java:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-kotlin:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-ruby:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-php:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-perl:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-swift:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-lean:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean) (multi-arch) + - [\`${DOCKERHUB_IMAGE}-rocq:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq) (multi-arch) + ### GitHub Container Registry (GHCR) - [\`${GHCR_IMAGE}:${VERSION}\`](https://github.com/${REPO}/pkgs/container/sandbox) (multi-arch) - [\`${GHCR_IMAGE}-essentials:${VERSION}\`](https://github.com/${REPO}/pkgs/container/sandbox-essentials) (multi-arch) @@ -1253,7 +1655,11 @@ jobs: \`\`\` JS sandbox (konard/sandbox-js) → Essentials sandbox (konard/sandbox-essentials) - → Full sandbox (konard/sandbox) + ├─ sandbox-python ├─ sandbox-go ├─ sandbox-rust + ├─ sandbox-java ├─ sandbox-kotlin ├─ sandbox-ruby + ├─ sandbox-php ├─ sandbox-perl ├─ sandbox-swift + ├─ sandbox-lean └─ sandbox-rocq + → Full sandbox (konard/sandbox) [merges all language images] \`\`\` ## Quick Start diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 769145b..12ddfd2 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -80,9 +80,12 @@ See [Case Study: Docker ARM64 Build Timeout](docs/case-studies/issue-7/README.md The CI/CD pipeline uses per-image change detection for efficiency. Only images whose scripts or Dockerfiles changed are rebuilt. Unchanged images reuse the latest published version. +All language images are built in parallel, and the full sandbox assembles them via +multi-stage `COPY --from` once all are ready. + ``` ┌──────────────────┐ -│ detect-changes │ (per-image granularity) +│ detect-changes │ (per-image + per-language granularity) │ (ubuntu-latest) │ └────────┬─────────┘ │ @@ -99,21 +102,30 @@ scripts or Dockerfiles changed are rebuilt. Unchanged images reuse the latest pu └────────┬───────────────┘ │ ▼ +┌────────────────────────────────────────────────────┐ +│ build-languages (matrix: 11 languages) │ ← ALL in parallel +│ python, go, rust, java, kotlin, ruby, php, perl, │ +│ swift, lean, rocq │ +│ (amd64 + arm64 per language) │ +└────────────────────┬───────────────────────────────┘ + │ + ▼ ┌────────────────────────┐ -│ docker-build-push │ ← full sandbox built on essentials -│ (amd64 + arm64) │ (sequential: amd64 first, then arm64) +│ docker-build-push │ ← full sandbox: COPY --from all language images +│ (amd64 + arm64) │ (multi-stage assembly, waits for all languages) └────────┬───────────────┘ │ ▼ ┌──────────────────┐ -│ docker-manifest │ ← multi-arch manifests for js, essentials, full +│ manifests │ ← multi-arch manifests for js, essentials, languages, full │ (multi-arch) │ └──────────────────┘ ``` -Each layer only rebuilds if its own scripts/Dockerfiles changed, or if a dependency -(common.sh, base image) changed. When JS sandbox hasn't changed, essentials and full -sandbox reuse the latest published JS image. +Each image only rebuilds if its own scripts/Dockerfiles changed, or if a dependency +(common.sh, essentials) changed. The full sandbox uses `COPY --from` to merge +pre-built language runtimes from all language images, plus `apt install` for +system-level packages (.NET, R, C/C++, Assembly). ## File Structure @@ -197,46 +209,46 @@ sandbox/ ## Modular Design -The sandbox follows a layered modular architecture: +The sandbox follows a modular architecture where all language images depend on +`essentials-sandbox`, and the full sandbox assembles them via multi-stage `COPY --from`: ``` -┌─────────────────────────────────────────────┐ -│ full-sandbox │ -│ (konard/sandbox or konard/sandbox-full) │ -│ │ -│ ┌─────────────────────────────────────┐ │ -│ │ essentials-sandbox │ │ -│ │ (konard/sandbox-essentials) │ │ -│ │ │ │ -│ │ ┌───────────────────────────┐ │ │ -│ │ │ JS sandbox │ │ │ -│ │ │ (konard/sandbox-js) │ │ │ -│ │ │ │ │ │ -│ │ │ Node.js, Bun, Deno, npm │ │ │ -│ │ └───────────────────────────┘ │ │ -│ │ │ │ -│ │ + git, gh, glab, │ │ -│ │ gh-setup-git-identity, │ │ -│ │ glab-setup-git-identity │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ + Python, Go, Rust, Java, Kotlin, .NET, │ -│ R, Ruby, PHP, Perl, Swift, Lean, Rocq, │ -│ C/C++, Assembly │ -└─────────────────────────────────────────────┘ - -Each language also available as standalone: -┌────┐ ┌────────┐ ┌────┐ ┌──────┐ ┌──────┐ -│ JS │ │ Python │ │ Go │ │ Rust │ │ ... │ -└────┘ └────────┘ └────┘ └──────┘ └──────┘ +┌─────────────────────────────────────────────────────────────┐ +│ JS sandbox (konard/sandbox-js) │ +│ └─ Node.js, Bun, Deno, npm │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Essentials sandbox (konard/sandbox-essentials) │ +│ └─ + git, gh, glab, identity tools, dev libraries │ +└──┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬────┬──┬─┘ + │ │ │ │ │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ + Python Go Rust Java Kotlin Ruby PHP Perl Swift Lean Rocq + │ │ │ │ │ │ │ │ │ │ + └──────┴──────┴──────┴──────┴──────┴──────┴──────┴────┴──┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Full sandbox (konard/sandbox) │ +│ └─ COPY --from all language images │ +│ └─ + apt: .NET, R, C/C++, Assembly (system packages) │ +└─────────────────────────────────────────────────────────────┘ ``` +Each language image is also available as a standalone Docker image +(e.g., `konard/sandbox-python`, `konard/sandbox-go`, etc.), each with +essentials pre-installed (JS, git, gh, glab, dev libraries). + ### Benefits 1. **Configurable disk usage**: Users can choose only the languages they need -2. **Parallel CI/CD**: Each language image can be built and tested independently +2. **Parallel CI/CD**: All language images are built in parallel 3. **Faster iteration**: Changes to one language only rebuild that image -4. **Standalone scripts**: Each `install.sh` works directly on Ubuntu 24.04 via `curl | bash` +4. **Efficient assembly**: Full sandbox uses `COPY --from` to merge pre-built files +5. **No dependency conflicts**: Each language builds in isolation on essentials +6. **Standalone scripts**: Each `install.sh` works directly on Ubuntu 24.04 via `curl | bash` ## Design Decisions diff --git a/Dockerfile b/Dockerfile index 9fe9a99..0148129 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,52 +1,147 @@ -ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest -FROM ${ESSENTIALS_IMAGE} - # Full Sandbox environment Docker image # Contains all language runtimes and development tools. # This is the "full-sandbox" image (konard/sandbox or konard/sandbox-full). # -# Architecture: JS sandbox → essentials-sandbox → full-sandbox (this image) -# -# For a lighter image with just essentials, see ubuntu/24.04/essentials-sandbox/Dockerfile. -# For just JavaScript, see ubuntu/24.04/js/Dockerfile. -# For individual language images, see ubuntu/24.04//Dockerfile. +# Architecture: +# essentials-sandbox (base for all language images) +# ├─ sandbox-python, sandbox-go, sandbox-rust, ... (built in parallel) +# └─ full-sandbox (merges all language images via COPY --from) # # Build from repository root: # docker build -t sandbox . # -# Build with specific essentials image: +# Build with specific images: # docker build --build-arg ESSENTIALS_IMAGE=konard/sandbox-essentials:latest -t sandbox . +# +# For a lighter image with just essentials, see ubuntu/24.04/essentials-sandbox/Dockerfile. +# For just JavaScript, see ubuntu/24.04/js/Dockerfile. +# For individual language images, see ubuntu/24.04//Dockerfile. -USER root +# === Language image stages (for COPY --from) === +ARG PYTHON_IMAGE=konard/sandbox-python:latest +ARG GO_IMAGE=konard/sandbox-go:latest +ARG RUST_IMAGE=konard/sandbox-rust:latest +ARG JAVA_IMAGE=konard/sandbox-java:latest +ARG KOTLIN_IMAGE=konard/sandbox-kotlin:latest +ARG RUBY_IMAGE=konard/sandbox-ruby:latest +ARG PHP_IMAGE=konard/sandbox-php:latest +ARG PERL_IMAGE=konard/sandbox-perl:latest +ARG SWIFT_IMAGE=konard/sandbox-swift:latest +ARG LEAN_IMAGE=konard/sandbox-lean:latest +ARG ROCQ_IMAGE=konard/sandbox-rocq:latest -# Set non-interactive frontend for apt -ENV DEBIAN_FRONTEND=noninteractive +FROM ${PYTHON_IMAGE} AS python-stage +FROM ${GO_IMAGE} AS go-stage +FROM ${RUST_IMAGE} AS rust-stage +FROM ${JAVA_IMAGE} AS java-stage +FROM ${KOTLIN_IMAGE} AS kotlin-stage +FROM ${RUBY_IMAGE} AS ruby-stage +FROM ${PHP_IMAGE} AS php-stage +FROM ${PERL_IMAGE} AS perl-stage +FROM ${SWIFT_IMAGE} AS swift-stage +FROM ${LEAN_IMAGE} AS lean-stage +FROM ${ROCQ_IMAGE} AS rocq-stage -# Set working directory -WORKDIR /workspace +# === Final assembly image === +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} -# Copy the modular installation scripts -COPY ubuntu/24.04/common.sh /tmp/sandbox-scripts/common.sh -COPY ubuntu/24.04/full-sandbox/install.sh /tmp/sandbox-scripts/full-sandbox/install.sh +USER root +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /workspace -# Copy entrypoint script for proper environment initialization +# Copy entrypoint script COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh -# Make scripts executable and run the full installation -# Pass DOCKER_BUILD=1 environment variable to indicate Docker build environment -RUN chmod +x /tmp/sandbox-scripts/full-sandbox/install.sh && \ - chmod +x /tmp/sandbox-scripts/common.sh && \ - chmod +x /usr/local/bin/entrypoint.sh && \ - DOCKER_BUILD=1 bash /tmp/sandbox-scripts/full-sandbox/install.sh && \ - rm -rf /tmp/sandbox-scripts +# --- Install system-level packages (cannot be COPY'd from images) --- +RUN apt-get update -y && \ + apt-get install -y \ + dotnet-sdk-8.0 \ + r-base \ + cmake clang llvm lld \ + nasm \ + bubblewrap && \ + # FASM only available on x86_64 + if [ "$(uname -m)" = "x86_64" ]; then apt-get install -y fasm; fi && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# --- Prepare directories for COPY --from --- +RUN mkdir -p /home/linuxbrew/.linuxbrew && \ + chown -R sandbox:sandbox /home/linuxbrew + +# --- Copy user-home language runtimes from pre-built images --- + +# Python (pyenv) +COPY --from=python-stage --chown=sandbox:sandbox /home/sandbox/.pyenv /home/sandbox/.pyenv + +# Go +COPY --from=go-stage --chown=sandbox:sandbox /home/sandbox/.go /home/sandbox/.go + +# Rust (cargo + rustup) +COPY --from=rust-stage --chown=sandbox:sandbox /home/sandbox/.cargo /home/sandbox/.cargo +COPY --from=rust-stage --chown=sandbox:sandbox /home/sandbox/.rustup /home/sandbox/.rustup + +# Java (SDKMAN) +COPY --from=java-stage --chown=sandbox:sandbox /home/sandbox/.sdkman /home/sandbox/.sdkman + +# Kotlin (SDKMAN - merge Kotlin candidate into Java's SDKMAN) +COPY --from=kotlin-stage --chown=sandbox:sandbox /home/sandbox/.sdkman/candidates/kotlin /home/sandbox/.sdkman/candidates/kotlin + +# Ruby (rbenv) +COPY --from=ruby-stage --chown=sandbox:sandbox /home/sandbox/.rbenv /home/sandbox/.rbenv + +# PHP (Homebrew) +COPY --from=php-stage --chown=sandbox:sandbox /home/linuxbrew/.linuxbrew /home/linuxbrew/.linuxbrew + +# Perl (Perlbrew) +COPY --from=perl-stage --chown=sandbox:sandbox /home/sandbox/.perl5 /home/sandbox/.perl5 + +# Swift +COPY --from=swift-stage --chown=sandbox:sandbox /home/sandbox/.swift /home/sandbox/.swift + +# Lean (elan) +COPY --from=lean-stage --chown=sandbox:sandbox /home/sandbox/.elan /home/sandbox/.elan + +# Rocq/Coq (Opam) +COPY --from=rocq-stage --chown=sandbox:sandbox /home/sandbox/.opam /home/sandbox/.opam + +# --- Copy bashrc configurations from language stages --- +COPY --from=python-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-python +COPY --from=go-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-go +COPY --from=rust-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-rust +COPY --from=java-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-java +COPY --from=kotlin-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-kotlin +COPY --from=ruby-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-ruby +COPY --from=php-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-php +COPY --from=perl-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-perl +COPY --from=swift-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-swift +COPY --from=lean-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-lean +COPY --from=rocq-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-rocq + +# Merge bashrc configurations: take the essentials bashrc as base, +# then append unique lines from each language stage +RUN cp /home/sandbox/.bashrc /tmp/.bashrc-base && \ + for lang_bashrc in /tmp/.bashrc-python /tmp/.bashrc-go /tmp/.bashrc-rust \ + /tmp/.bashrc-java /tmp/.bashrc-kotlin /tmp/.bashrc-ruby /tmp/.bashrc-php \ + /tmp/.bashrc-perl /tmp/.bashrc-swift /tmp/.bashrc-lean /tmp/.bashrc-rocq; do \ + if [ -f "$lang_bashrc" ]; then \ + while IFS= read -r line; do \ + if [ -n "$line" ] && ! grep -qxF "$line" /tmp/.bashrc-base 2>/dev/null; then \ + echo "$line" >> /tmp/.bashrc-base; \ + fi; \ + done < "$lang_bashrc"; \ + fi; \ + done && \ + cp /tmp/.bashrc-base /home/sandbox/.bashrc && \ + chown sandbox:sandbox /home/sandbox/.bashrc && \ + rm -f /tmp/.bashrc-* # Switch to sandbox user USER sandbox - -# Set home directory WORKDIR /home/sandbox -# Set up basic environment variables (tools will be fully loaded by entrypoint) +# Environment variables for all tools ENV NVM_DIR="/home/sandbox/.nvm" ENV PYENV_ROOT="/home/sandbox/.pyenv" ENV BUN_INSTALL="/home/sandbox/.bun" @@ -58,20 +153,14 @@ ENV SDKMAN_DIR="/home/sandbox/.sdkman" ENV PERLBREW_ROOT="/home/sandbox/.perl5" ENV RBENV_ROOT="/home/sandbox/.rbenv" -# Set up PATH for tools that don't need special initialization -# Bun, Deno, Cargo, elan, rbenv, Swift, Homebrew work with just PATH -ENV PATH="/home/sandbox/.rbenv/bin:/home/sandbox/.rbenv/shims:/home/sandbox/.swift/usr/bin:/home/sandbox/.elan/bin:/home/sandbox/.opam/default/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/sandbox/.cargo/bin:/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:/home/sandbox/.go/bin:/home/sandbox/.go/path/bin:/home/linuxbrew/.linuxbrew/bin:${PATH}" +# PATH for tools that don't need special initialization +ENV PATH="/home/sandbox/.pyenv/bin:/home/sandbox/.pyenv/shims:/home/sandbox/.rbenv/bin:/home/sandbox/.rbenv/shims:/home/sandbox/.swift/usr/bin:/home/sandbox/.elan/bin:/home/sandbox/.opam/default/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/sandbox/.cargo/bin:/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:/home/sandbox/.go/bin:/home/sandbox/.go/path/bin:/home/linuxbrew/.linuxbrew/bin:${PATH}" # Opam environment variables for Rocq/Coq theorem prover ENV OPAM_SWITCH_PREFIX="/home/sandbox/.opam/default" ENV CAML_LD_LIBRARY_PATH="/home/sandbox/.opam/default/lib/stublibs:/home/sandbox/.opam/default/lib/ocaml/stublibs:/home/sandbox/.opam/default/lib/ocaml" ENV OCAML_TOPLEVEL_PATH="/home/sandbox/.opam/default/lib/toplevel" -# Use bash as default shell SHELL ["/bin/bash", "-c"] - -# Use entrypoint to initialize environment ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] - -# Set default command to bash CMD ["/bin/bash"] diff --git a/README.md b/README.md index 817b10c..ad8d769 100644 --- a/README.md +++ b/README.md @@ -42,19 +42,41 @@ This sandbox provides a pre-configured development environment with common langu ## Modular Architecture -The sandbox is split into layered modular components, allowing you to use only what you need: +The sandbox is split into modular components, allowing you to use only what you need: ``` JS sandbox (konard/sandbox-js) - → Essentials sandbox (konard/sandbox-essentials) - → Full sandbox (konard/sandbox) + └─ Essentials sandbox (konard/sandbox-essentials) + ├─ sandbox-python (built in parallel) + ├─ sandbox-go (built in parallel) + ├─ sandbox-rust (built in parallel) + ├─ sandbox-java (built in parallel) + ├─ sandbox-kotlin (built in parallel) + ├─ sandbox-ruby (built in parallel) + ├─ sandbox-php (built in parallel) + ├─ sandbox-perl (built in parallel) + ├─ sandbox-swift (built in parallel) + ├─ sandbox-lean (built in parallel) + └─ sandbox-rocq (built in parallel) + └─ Full sandbox (konard/sandbox) ← merges all via COPY --from ``` -| Image | Description | Contents | -|-------|-------------|----------| -| `konard/sandbox` | Full sandbox (all languages) | Everything below | -| `konard/sandbox-essentials` | Essentials (git identity tools) | JS sandbox + git, gh, glab, gh-setup-git-identity, glab-setup-git-identity | -| `konard/sandbox-js` | JavaScript only | Node.js, Bun, Deno, npm | +| Image | Description | Base Image | +|-------|-------------|------------| +| `konard/sandbox` | Full sandbox (all languages) | Assembled from all language images | +| `konard/sandbox-essentials` | Essentials (git identity tools) | Built on JS sandbox | +| `konard/sandbox-js` | JavaScript only | Ubuntu 24.04 | +| `konard/sandbox-python` | Python (pyenv) | Built on essentials | +| `konard/sandbox-go` | Go (latest stable) | Built on essentials | +| `konard/sandbox-rust` | Rust (rustup + cargo) | Built on essentials | +| `konard/sandbox-java` | Java 21 (SDKMAN + Temurin) | Built on essentials | +| `konard/sandbox-kotlin` | Kotlin (SDKMAN) | Built on essentials | +| `konard/sandbox-ruby` | Ruby (rbenv) | Built on essentials | +| `konard/sandbox-php` | PHP 8.3 (Homebrew) | Built on essentials | +| `konard/sandbox-perl` | Perl (Perlbrew) | Built on essentials | +| `konard/sandbox-swift` | Swift 6.x | Built on essentials | +| `konard/sandbox-lean` | Lean (elan) | Built on essentials | +| `konard/sandbox-rocq` | Rocq/Coq (Opam) | Built on essentials | ### Per-Language Install Scripts & Dockerfiles diff --git a/ubuntu/24.04/assembly/Dockerfile b/ubuntu/24.04/assembly/Dockerfile index 9014868..2fb235c 100644 --- a/ubuntu/24.04/assembly/Dockerfile +++ b/ubuntu/24.04/assembly/Dockerfile @@ -1,24 +1,17 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/assembly/Dockerfile -t sandbox-assembly . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/assembly/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-assembly . # Assembly sandbox: NASM, FASM (x86_64 only), GNU Assembler, LLVM MC +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER root +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/assembly/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ - rm -f /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh && \ apt-get clean && rm -rf /var/lib/apt/lists/* USER sandbox diff --git a/ubuntu/24.04/assembly/install.sh b/ubuntu/24.04/assembly/install.sh index 1b4c250..d904b1c 100644 --- a/ubuntu/24.04/assembly/install.sh +++ b/ubuntu/24.04/assembly/install.sh @@ -6,6 +6,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/cpp/Dockerfile b/ubuntu/24.04/cpp/Dockerfile index 6e51892..c0deace 100644 --- a/ubuntu/24.04/cpp/Dockerfile +++ b/ubuntu/24.04/cpp/Dockerfile @@ -1,24 +1,17 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/cpp/Dockerfile -t sandbox-cpp . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/cpp/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-cpp . # C/C++ sandbox: build-essential, CMake, Clang, LLVM, LLD +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER root +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/cpp/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ - rm -f /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh && \ apt-get clean && rm -rf /var/lib/apt/lists/* USER sandbox diff --git a/ubuntu/24.04/cpp/install.sh b/ubuntu/24.04/cpp/install.sh index 4a75d58..4d1ae61 100644 --- a/ubuntu/24.04/cpp/install.sh +++ b/ubuntu/24.04/cpp/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/dotnet/Dockerfile b/ubuntu/24.04/dotnet/Dockerfile index c119996..c32185f 100644 --- a/ubuntu/24.04/dotnet/Dockerfile +++ b/ubuntu/24.04/dotnet/Dockerfile @@ -1,24 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/dotnet/Dockerfile -t sandbox-dotnet . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/dotnet/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-dotnet . # .NET sandbox: .NET SDK 8.0 +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER root +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/dotnet/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ - rm -f /tmp/install.sh + rm -f /tmp/install.sh /tmp/common.sh && \ + apt-get clean && rm -rf /var/lib/apt/lists/* USER sandbox WORKDIR /home/sandbox diff --git a/ubuntu/24.04/dotnet/install.sh b/ubuntu/24.04/dotnet/install.sh index 02a4b66..3c0c224 100644 --- a/ubuntu/24.04/dotnet/install.sh +++ b/ubuntu/24.04/dotnet/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/full-sandbox/Dockerfile b/ubuntu/24.04/full-sandbox/Dockerfile index 7430bfa..f969b1a 100644 --- a/ubuntu/24.04/full-sandbox/Dockerfile +++ b/ubuntu/24.04/full-sandbox/Dockerfile @@ -1,33 +1,146 @@ -ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest -FROM ${ESSENTIALS_IMAGE} - -# Full Sandbox Docker image -# Contains all language runtimes and development tools -# Built on top of essentials-sandbox (which is built on top of JS sandbox) +# Full Sandbox Docker image - Multi-stage assembly +# Copies pre-built language runtimes from individual language images # Published as: konard/sandbox (or konard/sandbox-full) # +# Architecture: +# essentials-sandbox (base for all language images) +# ├─ sandbox-python (built in parallel) +# ├─ sandbox-go (built in parallel) +# ├─ sandbox-rust (built in parallel) +# ├─ sandbox-java (built in parallel) +# ├─ sandbox-kotlin (built in parallel) +# ├─ sandbox-ruby (built in parallel) +# ├─ sandbox-php (built in parallel) +# ├─ sandbox-perl (built in parallel) +# ├─ sandbox-swift (built in parallel) +# ├─ sandbox-lean (built in parallel) +# ├─ sandbox-rocq (built in parallel) +# └─ full-sandbox (merges all + adds dotnet, r, cpp, assembly via apt) +# # Build from repository root: # docker build -f ubuntu/24.04/full-sandbox/Dockerfile -t sandbox-full . -# -# Build with specific essentials image: -# docker build -f ubuntu/24.04/full-sandbox/Dockerfile \ -# --build-arg ESSENTIALS_IMAGE=konard/sandbox-essentials:latest -t sandbox-full . -USER root +# === Language image stages (for COPY --from) === +ARG PYTHON_IMAGE=konard/sandbox-python:latest +ARG GO_IMAGE=konard/sandbox-go:latest +ARG RUST_IMAGE=konard/sandbox-rust:latest +ARG JAVA_IMAGE=konard/sandbox-java:latest +ARG KOTLIN_IMAGE=konard/sandbox-kotlin:latest +ARG RUBY_IMAGE=konard/sandbox-ruby:latest +ARG PHP_IMAGE=konard/sandbox-php:latest +ARG PERL_IMAGE=konard/sandbox-perl:latest +ARG SWIFT_IMAGE=konard/sandbox-swift:latest +ARG LEAN_IMAGE=konard/sandbox-lean:latest +ARG ROCQ_IMAGE=konard/sandbox-rocq:latest + +FROM ${PYTHON_IMAGE} AS python-stage +FROM ${GO_IMAGE} AS go-stage +FROM ${RUST_IMAGE} AS rust-stage +FROM ${JAVA_IMAGE} AS java-stage +FROM ${KOTLIN_IMAGE} AS kotlin-stage +FROM ${RUBY_IMAGE} AS ruby-stage +FROM ${PHP_IMAGE} AS php-stage +FROM ${PERL_IMAGE} AS perl-stage +FROM ${SWIFT_IMAGE} AS swift-stage +FROM ${LEAN_IMAGE} AS lean-stage +FROM ${ROCQ_IMAGE} AS rocq-stage +# === Final assembly image === +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} + +USER root ENV DEBIAN_FRONTEND=noninteractive -# Copy all install scripts from repo root context -COPY ubuntu/24.04/common.sh /tmp/sandbox-scripts/common.sh -COPY ubuntu/24.04/full-sandbox/install.sh /tmp/sandbox-scripts/full-sandbox/install.sh +# Copy entrypoint script COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# --- Install system-level packages (cannot be COPY'd from images) --- +RUN apt-get update -y && \ + apt-get install -y \ + dotnet-sdk-8.0 \ + r-base \ + cmake clang llvm lld \ + nasm \ + bubblewrap && \ + # FASM only available on x86_64 + if [ "$(uname -m)" = "x86_64" ]; then apt-get install -y fasm; fi && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# --- Prepare directories for COPY --from --- +RUN mkdir -p /home/linuxbrew/.linuxbrew && \ + chown -R sandbox:sandbox /home/linuxbrew + +# --- Copy user-home language runtimes from pre-built images --- + +# Python (pyenv) +COPY --from=python-stage --chown=sandbox:sandbox /home/sandbox/.pyenv /home/sandbox/.pyenv + +# Go +COPY --from=go-stage --chown=sandbox:sandbox /home/sandbox/.go /home/sandbox/.go + +# Rust (cargo + rustup) +COPY --from=rust-stage --chown=sandbox:sandbox /home/sandbox/.cargo /home/sandbox/.cargo +COPY --from=rust-stage --chown=sandbox:sandbox /home/sandbox/.rustup /home/sandbox/.rustup + +# Java (SDKMAN) +COPY --from=java-stage --chown=sandbox:sandbox /home/sandbox/.sdkman /home/sandbox/.sdkman + +# Kotlin (SDKMAN - merge with Java's SDKMAN) +# Kotlin stage has its own SDKMAN with Kotlin installed +# We copy only the Kotlin candidate from the kotlin stage +COPY --from=kotlin-stage --chown=sandbox:sandbox /home/sandbox/.sdkman/candidates/kotlin /home/sandbox/.sdkman/candidates/kotlin + +# Ruby (rbenv) +COPY --from=ruby-stage --chown=sandbox:sandbox /home/sandbox/.rbenv /home/sandbox/.rbenv + +# PHP (Homebrew) +COPY --from=php-stage --chown=sandbox:sandbox /home/linuxbrew/.linuxbrew /home/linuxbrew/.linuxbrew + +# Perl (Perlbrew) +COPY --from=perl-stage --chown=sandbox:sandbox /home/sandbox/.perl5 /home/sandbox/.perl5 + +# Swift +COPY --from=swift-stage --chown=sandbox:sandbox /home/sandbox/.swift /home/sandbox/.swift + +# Lean (elan) +COPY --from=lean-stage --chown=sandbox:sandbox /home/sandbox/.elan /home/sandbox/.elan + +# Rocq/Coq (Opam) +COPY --from=rocq-stage --chown=sandbox:sandbox /home/sandbox/.opam /home/sandbox/.opam + +# --- Copy bashrc configurations from language stages --- +# We need the bashrc entries for environment initialization +COPY --from=python-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-python +COPY --from=go-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-go +COPY --from=rust-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-rust +COPY --from=java-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-java +COPY --from=kotlin-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-kotlin +COPY --from=ruby-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-ruby +COPY --from=php-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-php +COPY --from=perl-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-perl +COPY --from=swift-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-swift +COPY --from=lean-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-lean +COPY --from=rocq-stage --chown=sandbox:sandbox /home/sandbox/.bashrc /tmp/.bashrc-rocq -# Make scripts executable and run the full installation -RUN chmod +x /tmp/sandbox-scripts/full-sandbox/install.sh && \ - chmod +x /tmp/sandbox-scripts/common.sh && \ - chmod +x /usr/local/bin/entrypoint.sh && \ - DOCKER_BUILD=1 bash /tmp/sandbox-scripts/full-sandbox/install.sh && \ - rm -rf /tmp/sandbox-scripts +# Merge bashrc configurations: take the essentials bashrc as base, +# then append unique lines from each language stage +RUN cp /home/sandbox/.bashrc /tmp/.bashrc-base && \ + for lang_bashrc in /tmp/.bashrc-python /tmp/.bashrc-go /tmp/.bashrc-rust \ + /tmp/.bashrc-java /tmp/.bashrc-kotlin /tmp/.bashrc-ruby /tmp/.bashrc-php \ + /tmp/.bashrc-perl /tmp/.bashrc-swift /tmp/.bashrc-lean /tmp/.bashrc-rocq; do \ + if [ -f "$lang_bashrc" ]; then \ + while IFS= read -r line; do \ + if [ -n "$line" ] && ! grep -qxF "$line" /tmp/.bashrc-base 2>/dev/null; then \ + echo "$line" >> /tmp/.bashrc-base; \ + fi; \ + done < "$lang_bashrc"; \ + fi; \ + done && \ + cp /tmp/.bashrc-base /home/sandbox/.bashrc && \ + chown sandbox:sandbox /home/sandbox/.bashrc && \ + rm -f /tmp/.bashrc-* USER sandbox WORKDIR /home/sandbox @@ -45,9 +158,9 @@ ENV PERLBREW_ROOT="/home/sandbox/.perl5" ENV RBENV_ROOT="/home/sandbox/.rbenv" # PATH for tools that don't need special initialization -ENV PATH="/home/sandbox/.rbenv/bin:/home/sandbox/.rbenv/shims:/home/sandbox/.swift/usr/bin:/home/sandbox/.elan/bin:/home/sandbox/.opam/default/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/sandbox/.cargo/bin:/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:/home/sandbox/.go/bin:/home/sandbox/.go/path/bin:/home/linuxbrew/.linuxbrew/bin:${PATH}" +ENV PATH="/home/sandbox/.pyenv/bin:/home/sandbox/.pyenv/shims:/home/sandbox/.rbenv/bin:/home/sandbox/.rbenv/shims:/home/sandbox/.swift/usr/bin:/home/sandbox/.elan/bin:/home/sandbox/.opam/default/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/sandbox/.cargo/bin:/home/sandbox/.deno/bin:/home/sandbox/.bun/bin:/home/sandbox/.go/bin:/home/sandbox/.go/path/bin:/home/linuxbrew/.linuxbrew/bin:${PATH}" -# Opam environment variables +# Opam environment variables for Rocq/Coq theorem prover ENV OPAM_SWITCH_PREFIX="/home/sandbox/.opam/default" ENV CAML_LD_LIBRARY_PATH="/home/sandbox/.opam/default/lib/stublibs:/home/sandbox/.opam/default/lib/ocaml/stublibs:/home/sandbox/.opam/default/lib/ocaml" ENV OCAML_TOPLEVEL_PATH="/home/sandbox/.opam/default/lib/toplevel" diff --git a/ubuntu/24.04/go/Dockerfile b/ubuntu/24.04/go/Dockerfile index 3ed5a9f..7f53e9d 100644 --- a/ubuntu/24.04/go/Dockerfile +++ b/ubuntu/24.04/go/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/go/Dockerfile -t sandbox-go . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/go/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-go . # Go sandbox: latest stable Go +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/go/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV GOROOT="/home/sandbox/.go" diff --git a/ubuntu/24.04/go/install.sh b/ubuntu/24.04/go/install.sh index 1e081c2..002949c 100644 --- a/ubuntu/24.04/go/install.sh +++ b/ubuntu/24.04/go/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/java/Dockerfile b/ubuntu/24.04/java/Dockerfile index 97b9bb8..dbb419f 100644 --- a/ubuntu/24.04/java/Dockerfile +++ b/ubuntu/24.04/java/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/java/Dockerfile -t sandbox-java . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/java/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-java . -# Java sandbox: SDKMAN + Eclipse Temurin 21 LTS +# Java sandbox: SDKMAN + Eclipse Temurin 21 +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates zip unzip && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/java/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV SDKMAN_DIR="/home/sandbox/.sdkman" diff --git a/ubuntu/24.04/java/install.sh b/ubuntu/24.04/java/install.sh index a1fb0cb..89616ae 100644 --- a/ubuntu/24.04/java/install.sh +++ b/ubuntu/24.04/java/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/kotlin/Dockerfile b/ubuntu/24.04/kotlin/Dockerfile index 4d000ed..0c32a4a 100644 --- a/ubuntu/24.04/kotlin/Dockerfile +++ b/ubuntu/24.04/kotlin/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/kotlin/Dockerfile -t sandbox-kotlin . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/kotlin/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-kotlin . # Kotlin sandbox: SDKMAN + Kotlin +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates zip unzip && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/kotlin/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV SDKMAN_DIR="/home/sandbox/.sdkman" diff --git a/ubuntu/24.04/kotlin/install.sh b/ubuntu/24.04/kotlin/install.sh index 60ba266..6c0acb6 100644 --- a/ubuntu/24.04/kotlin/install.sh +++ b/ubuntu/24.04/kotlin/install.sh @@ -6,6 +6,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/lean/Dockerfile b/ubuntu/24.04/lean/Dockerfile index 3fe141a..d65fa76 100644 --- a/ubuntu/24.04/lean/Dockerfile +++ b/ubuntu/24.04/lean/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/lean/Dockerfile -t sandbox-lean . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/lean/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-lean . # Lean sandbox: Lean theorem prover via elan +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/lean/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV PATH="/home/sandbox/.elan/bin:${PATH}" diff --git a/ubuntu/24.04/lean/install.sh b/ubuntu/24.04/lean/install.sh index 0af4d14..07769ec 100644 --- a/ubuntu/24.04/lean/install.sh +++ b/ubuntu/24.04/lean/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/perl/Dockerfile b/ubuntu/24.04/perl/Dockerfile index 0873922..89ced06 100644 --- a/ubuntu/24.04/perl/Dockerfile +++ b/ubuntu/24.04/perl/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/perl/Dockerfile -t sandbox-perl . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/perl/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-perl . # Perl sandbox: Perlbrew + latest stable Perl +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/perl/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV PERLBREW_ROOT="/home/sandbox/.perl5" diff --git a/ubuntu/24.04/perl/install.sh b/ubuntu/24.04/perl/install.sh index 61d8d0e..903fb4c 100644 --- a/ubuntu/24.04/perl/install.sh +++ b/ubuntu/24.04/perl/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/php/Dockerfile b/ubuntu/24.04/php/Dockerfile index b9abf25..2af036e 100644 --- a/ubuntu/24.04/php/Dockerfile +++ b/ubuntu/24.04/php/Dockerfile @@ -1,28 +1,22 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/php/Dockerfile -t sandbox-php . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/php/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-php . # PHP sandbox: PHP 8.3 via Homebrew +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox && \ - mkdir -p /home/linuxbrew/.linuxbrew && \ +USER root +RUN mkdir -p /home/linuxbrew/.linuxbrew && \ chown -R sandbox:sandbox /home/linuxbrew +USER sandbox + +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/php/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV PATH="/home/linuxbrew/.linuxbrew/opt/php@8.3/bin:/home/linuxbrew/.linuxbrew/opt/php@8.3/sbin:/home/linuxbrew/.linuxbrew/bin:${PATH}" diff --git a/ubuntu/24.04/php/install.sh b/ubuntu/24.04/php/install.sh index 53cf83f..94e986e 100644 --- a/ubuntu/24.04/php/install.sh +++ b/ubuntu/24.04/php/install.sh @@ -6,6 +6,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/python/Dockerfile b/ubuntu/24.04/python/Dockerfile index 27725fb..e9f3fc3 100644 --- a/ubuntu/24.04/python/Dockerfile +++ b/ubuntu/24.04/python/Dockerfile @@ -1,34 +1,22 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/python/Dockerfile -t sandbox-python . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/python/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-python . # Python sandbox: Pyenv + latest stable Python +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -# Install system prerequisites and Python build dependencies -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential \ - libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ - libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \ - libffi-dev liblzma-dev && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/python/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV PYENV_ROOT="/home/sandbox/.pyenv" -ENV PATH="/home/sandbox/.pyenv/bin:${PATH}" +ENV PATH="/home/sandbox/.pyenv/bin:/home/sandbox/.pyenv/shims:${PATH}" SHELL ["/bin/bash", "-c"] CMD ["/bin/bash"] diff --git a/ubuntu/24.04/python/install.sh b/ubuntu/24.04/python/install.sh index 231db59..6797fd6 100644 --- a/ubuntu/24.04/python/install.sh +++ b/ubuntu/24.04/python/install.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash # Python installation via Pyenv # Usage: curl -fsSL | bash OR bash install.sh -# Requires: build dependencies (libssl-dev, zlib1g-dev, etc.) +# Requires: essentials-sandbox (provides build dependencies: libssl-dev, zlib1g-dev, etc.) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } @@ -18,22 +20,9 @@ fi log_step "Installing Python via Pyenv" -# Install build dependencies (requires root/sudo) -log_info "Installing Python build dependencies..." -maybe_sudo apt install -y \ - libssl-dev \ - zlib1g-dev \ - libbz2-dev \ - libreadline-dev \ - libsqlite3-dev \ - libncursesw5-dev \ - xz-utils \ - tk-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libffi-dev \ - liblzma-dev -log_success "Python build dependencies installed" +# Note: Build dependencies (libssl-dev, zlib1g-dev, libbz2-dev, libreadline-dev, +# libsqlite3-dev, libncursesw5-dev, xz-utils, tk-dev, libxml2-dev, libxmlsec1-dev, +# libffi-dev, liblzma-dev) are provided by essentials-sandbox. # --- Pyenv --- if [ ! -d "$HOME/.pyenv" ]; then diff --git a/ubuntu/24.04/r/Dockerfile b/ubuntu/24.04/r/Dockerfile index a94f7fe..dc8827f 100644 --- a/ubuntu/24.04/r/Dockerfile +++ b/ubuntu/24.04/r/Dockerfile @@ -1,24 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/r/Dockerfile -t sandbox-r . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/r/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-r . # R sandbox: R statistical language +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER root +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/r/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ - rm -f /tmp/install.sh + rm -f /tmp/install.sh /tmp/common.sh && \ + apt-get clean && rm -rf /var/lib/apt/lists/* USER sandbox WORKDIR /home/sandbox diff --git a/ubuntu/24.04/r/install.sh b/ubuntu/24.04/r/install.sh index c2290bb..c9645b4 100644 --- a/ubuntu/24.04/r/install.sh +++ b/ubuntu/24.04/r/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/rocq/Dockerfile b/ubuntu/24.04/rocq/Dockerfile index 4a84116..53463f1 100644 --- a/ubuntu/24.04/rocq/Dockerfile +++ b/ubuntu/24.04/rocq/Dockerfile @@ -1,26 +1,22 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/rocq/Dockerfile -t sandbox-rocq . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/rocq/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-rocq . # Rocq/Coq sandbox: Opam + Rocq theorem prover +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential bubblewrap && \ +USER root +RUN apt-get update -y && apt-get install -y bubblewrap && \ apt-get clean && rm -rf /var/lib/apt/lists/* -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/rocq/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV OPAM_SWITCH_PREFIX="/home/sandbox/.opam/default" diff --git a/ubuntu/24.04/rocq/install.sh b/ubuntu/24.04/rocq/install.sh index 442644b..e4dfeb8 100644 --- a/ubuntu/24.04/rocq/install.sh +++ b/ubuntu/24.04/rocq/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } @@ -17,10 +19,11 @@ fi log_step "Installing Rocq/Coq via Opam" +# Note: bubblewrap is provided by essentials-sandbox or the Dockerfile. + # --- Opam --- if ! command_exists opam; then log_info "Installing Opam (OCaml package manager)..." - maybe_sudo apt install -y bubblewrap || true bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --no-backup" <<< "y" || { maybe_sudo apt install -y opam || true diff --git a/ubuntu/24.04/ruby/Dockerfile b/ubuntu/24.04/ruby/Dockerfile index aaacfac..5be69f6 100644 --- a/ubuntu/24.04/ruby/Dockerfile +++ b/ubuntu/24.04/ruby/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/ruby/Dockerfile -t sandbox-ruby . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/ruby/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-ruby . # Ruby sandbox: rbenv + latest stable Ruby 3.x +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries including libyaml-dev) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential libyaml-dev && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/ruby/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV RBENV_ROOT="/home/sandbox/.rbenv" diff --git a/ubuntu/24.04/ruby/install.sh b/ubuntu/24.04/ruby/install.sh index b7bd621..8111b73 100644 --- a/ubuntu/24.04/ruby/install.sh +++ b/ubuntu/24.04/ruby/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } @@ -16,10 +18,7 @@ fi log_step "Installing Ruby via rbenv" -# Install build dependencies -log_info "Installing Ruby build dependencies..." -maybe_sudo apt install -y libyaml-dev -log_success "Ruby build dependencies installed" +# Note: Build dependencies (libyaml-dev, libssl-dev, etc.) are provided by essentials-sandbox. if [ ! -d "$HOME/.rbenv" ]; then log_info "Installing rbenv (Ruby version manager)..." diff --git a/ubuntu/24.04/rust/Dockerfile b/ubuntu/24.04/rust/Dockerfile index 538e90a..d12b8cc 100644 --- a/ubuntu/24.04/rust/Dockerfile +++ b/ubuntu/24.04/rust/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/rust/Dockerfile -t sandbox-rust . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/rust/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-rust . # Rust sandbox: rustup + latest stable Rust +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/rust/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV CARGO_HOME="/home/sandbox/.cargo" diff --git a/ubuntu/24.04/rust/install.sh b/ubuntu/24.04/rust/install.sh index 300c934..7da84a5 100644 --- a/ubuntu/24.04/rust/install.sh +++ b/ubuntu/24.04/rust/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } diff --git a/ubuntu/24.04/swift/Dockerfile b/ubuntu/24.04/swift/Dockerfile index caf1695..39f2356 100644 --- a/ubuntu/24.04/swift/Dockerfile +++ b/ubuntu/24.04/swift/Dockerfile @@ -1,26 +1,18 @@ -FROM ubuntu:24.04 -# Build: docker build -f ubuntu/24.04/swift/Dockerfile -t sandbox-swift . +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest +FROM ${ESSENTIALS_IMAGE} +# Build: docker build -f ubuntu/24.04/swift/Dockerfile --build-arg ESSENTIALS_IMAGE=sandbox-essentials -t sandbox-swift . -# Swift sandbox: Swift 6.x +# Swift sandbox: Swift 6.0.3 +# Built on top of essentials-sandbox (inherits JS, git, gh, glab, dev libraries) -ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /workspace - -RUN apt update -y && \ - apt install -y curl git sudo ca-certificates build-essential && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd -m -s /bin/bash sandbox && \ - passwd -d sandbox && \ - usermod -aG sudo sandbox +USER sandbox +COPY ubuntu/24.04/common.sh /tmp/common.sh COPY ubuntu/24.04/swift/install.sh /tmp/install.sh -COPY ubuntu/24.04/common.sh /common.sh -RUN chmod +x /tmp/install.sh && \ - su - sandbox -c "bash /tmp/install.sh" && \ - rm -f /tmp/install.sh +RUN chmod +x /tmp/install.sh /tmp/common.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh -USER sandbox WORKDIR /home/sandbox ENV PATH="/home/sandbox/.swift/usr/bin:${PATH}" diff --git a/ubuntu/24.04/swift/install.sh b/ubuntu/24.04/swift/install.sh index 8438cac..b64aa17 100644 --- a/ubuntu/24.04/swift/install.sh +++ b/ubuntu/24.04/swift/install.sh @@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/../common.sh" ]; then source "$SCRIPT_DIR/../common.sh" +elif [ -f "/tmp/common.sh" ]; then + source "/tmp/common.sh" else set -euo pipefail log_info() { echo "[*] $1"; } From ac9f84d479d441eba404c4e0aaaeb81220715263 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 22:10:06 +0100 Subject: [PATCH 11/13] Fix COPY permission errors in language Dockerfiles Add --chown=sandbox:sandbox to COPY directives in all language Dockerfiles that run as USER sandbox. The essentials base image ends with USER sandbox, so COPY creates root-owned files that the sandbox user cannot chmod. Co-Authored-By: Claude Opus 4.5 --- ubuntu/24.04/go/Dockerfile | 4 ++-- ubuntu/24.04/java/Dockerfile | 4 ++-- ubuntu/24.04/kotlin/Dockerfile | 4 ++-- ubuntu/24.04/lean/Dockerfile | 4 ++-- ubuntu/24.04/perl/Dockerfile | 4 ++-- ubuntu/24.04/php/Dockerfile | 4 ++-- ubuntu/24.04/python/Dockerfile | 4 ++-- ubuntu/24.04/rocq/Dockerfile | 4 ++-- ubuntu/24.04/ruby/Dockerfile | 4 ++-- ubuntu/24.04/rust/Dockerfile | 4 ++-- ubuntu/24.04/swift/Dockerfile | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/ubuntu/24.04/go/Dockerfile b/ubuntu/24.04/go/Dockerfile index 7f53e9d..728aa6d 100644 --- a/ubuntu/24.04/go/Dockerfile +++ b/ubuntu/24.04/go/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/go/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/go/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/java/Dockerfile b/ubuntu/24.04/java/Dockerfile index dbb419f..f22d4d3 100644 --- a/ubuntu/24.04/java/Dockerfile +++ b/ubuntu/24.04/java/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/java/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/java/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/kotlin/Dockerfile b/ubuntu/24.04/kotlin/Dockerfile index 0c32a4a..704c97d 100644 --- a/ubuntu/24.04/kotlin/Dockerfile +++ b/ubuntu/24.04/kotlin/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/kotlin/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/kotlin/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/lean/Dockerfile b/ubuntu/24.04/lean/Dockerfile index d65fa76..9d54e04 100644 --- a/ubuntu/24.04/lean/Dockerfile +++ b/ubuntu/24.04/lean/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/lean/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/lean/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/perl/Dockerfile b/ubuntu/24.04/perl/Dockerfile index 89ced06..a2b8a4f 100644 --- a/ubuntu/24.04/perl/Dockerfile +++ b/ubuntu/24.04/perl/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/perl/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/perl/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/php/Dockerfile b/ubuntu/24.04/php/Dockerfile index 2af036e..704e30b 100644 --- a/ubuntu/24.04/php/Dockerfile +++ b/ubuntu/24.04/php/Dockerfile @@ -11,8 +11,8 @@ RUN mkdir -p /home/linuxbrew/.linuxbrew && \ USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/php/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/php/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/python/Dockerfile b/ubuntu/24.04/python/Dockerfile index e9f3fc3..885280d 100644 --- a/ubuntu/24.04/python/Dockerfile +++ b/ubuntu/24.04/python/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/python/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/python/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/rocq/Dockerfile b/ubuntu/24.04/rocq/Dockerfile index 53463f1..f00eec2 100644 --- a/ubuntu/24.04/rocq/Dockerfile +++ b/ubuntu/24.04/rocq/Dockerfile @@ -11,8 +11,8 @@ RUN apt-get update -y && apt-get install -y bubblewrap && \ USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/rocq/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/rocq/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/ruby/Dockerfile b/ubuntu/24.04/ruby/Dockerfile index 5be69f6..0a1b0ea 100644 --- a/ubuntu/24.04/ruby/Dockerfile +++ b/ubuntu/24.04/ruby/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/ruby/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/ruby/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/rust/Dockerfile b/ubuntu/24.04/rust/Dockerfile index d12b8cc..8bfe2b0 100644 --- a/ubuntu/24.04/rust/Dockerfile +++ b/ubuntu/24.04/rust/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/rust/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/rust/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh diff --git a/ubuntu/24.04/swift/Dockerfile b/ubuntu/24.04/swift/Dockerfile index 39f2356..fcfafd3 100644 --- a/ubuntu/24.04/swift/Dockerfile +++ b/ubuntu/24.04/swift/Dockerfile @@ -7,8 +7,8 @@ FROM ${ESSENTIALS_IMAGE} USER sandbox -COPY ubuntu/24.04/common.sh /tmp/common.sh -COPY ubuntu/24.04/swift/install.sh /tmp/install.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/common.sh /tmp/common.sh +COPY --chown=sandbox:sandbox ubuntu/24.04/swift/install.sh /tmp/install.sh RUN chmod +x /tmp/install.sh /tmp/common.sh && \ bash /tmp/install.sh && \ rm -f /tmp/install.sh /tmp/common.sh From c8e031617f068ee3b9cd2572775358f3fe749705 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 22:30:00 +0100 Subject: [PATCH 12/13] Fix Docker ARG scoping: declare all ARGs before first FROM Move ESSENTIALS_IMAGE ARG declaration before all FROM statements in both root Dockerfile and full-sandbox Dockerfile. Docker ARGs declared after FROM are stage-scoped and lose their values for subsequent stages, causing "base name should not be blank" errors. Co-Authored-By: Claude Opus 4.5 --- Dockerfile | 4 ++-- ubuntu/24.04/full-sandbox/Dockerfile | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0148129..c9e6cb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,8 @@ # For just JavaScript, see ubuntu/24.04/js/Dockerfile. # For individual language images, see ubuntu/24.04//Dockerfile. -# === Language image stages (for COPY --from) === +# === Build arguments (all declared before first FROM for global scope) === +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest ARG PYTHON_IMAGE=konard/sandbox-python:latest ARG GO_IMAGE=konard/sandbox-go:latest ARG RUST_IMAGE=konard/sandbox-rust:latest @@ -43,7 +44,6 @@ FROM ${LEAN_IMAGE} AS lean-stage FROM ${ROCQ_IMAGE} AS rocq-stage # === Final assembly image === -ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest FROM ${ESSENTIALS_IMAGE} USER root diff --git a/ubuntu/24.04/full-sandbox/Dockerfile b/ubuntu/24.04/full-sandbox/Dockerfile index f969b1a..ad55af4 100644 --- a/ubuntu/24.04/full-sandbox/Dockerfile +++ b/ubuntu/24.04/full-sandbox/Dockerfile @@ -20,7 +20,8 @@ # Build from repository root: # docker build -f ubuntu/24.04/full-sandbox/Dockerfile -t sandbox-full . -# === Language image stages (for COPY --from) === +# === Build arguments (all declared before first FROM for global scope) === +ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest ARG PYTHON_IMAGE=konard/sandbox-python:latest ARG GO_IMAGE=konard/sandbox-go:latest ARG RUST_IMAGE=konard/sandbox-rust:latest @@ -33,6 +34,7 @@ ARG SWIFT_IMAGE=konard/sandbox-swift:latest ARG LEAN_IMAGE=konard/sandbox-lean:latest ARG ROCQ_IMAGE=konard/sandbox-rocq:latest +# === Language image stages (for COPY --from) === FROM ${PYTHON_IMAGE} AS python-stage FROM ${GO_IMAGE} AS go-stage FROM ${RUST_IMAGE} AS rust-stage @@ -46,7 +48,6 @@ FROM ${LEAN_IMAGE} AS lean-stage FROM ${ROCQ_IMAGE} AS rocq-stage # === Final assembly image === -ARG ESSENTIALS_IMAGE=konard/sandbox-essentials:latest FROM ${ESSENTIALS_IMAGE} USER root From e2d0dbc2432141239eb9366a1b773807b2061a47 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 1 Feb 2026 22:48:10 +0100 Subject: [PATCH 13/13] Fix Rocq/Opam installation for non-root user context The opam install script was receiving "y" as installation path instead of accepting the default. Fix by providing the correct path ($HOME/.local/bin) as first input. Also add fallback direct binary download and ensure .opam directory always exists for COPY --from. Co-Authored-By: Claude Opus 4.5 --- ubuntu/24.04/rocq/install.sh | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ubuntu/24.04/rocq/install.sh b/ubuntu/24.04/rocq/install.sh index e4dfeb8..8f717e8 100644 --- a/ubuntu/24.04/rocq/install.sh +++ b/ubuntu/24.04/rocq/install.sh @@ -25,8 +25,21 @@ log_step "Installing Rocq/Coq via Opam" if ! command_exists opam; then log_info "Installing Opam (OCaml package manager)..." - bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --no-backup" <<< "y" || { - maybe_sudo apt install -y opam || true + # Install opam binary to user-writable location + OPAM_BIN_DIR="$HOME/.local/bin" + mkdir -p "$OPAM_BIN_DIR" + export PATH="$OPAM_BIN_DIR:$PATH" + # The install script asks: 1) path [/usr/local/bin], 2) create dir? [Y/n] + printf '%s\n%s\n' "$OPAM_BIN_DIR" "Y" | bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --no-backup" || { + # Fallback: download opam binary directly + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) OPAM_ARCH="x86_64" ;; + aarch64) OPAM_ARCH="arm64" ;; + *) OPAM_ARCH="$ARCH" ;; + esac + curl -fsSL "https://github.com/ocaml/opam/releases/latest/download/opam-2.3.0-${OPAM_ARCH}-linux" -o "$OPAM_BIN_DIR/opam" && \ + chmod +x "$OPAM_BIN_DIR/opam" || true } if command_exists opam; then @@ -82,4 +95,10 @@ if command_exists opam; then fi fi +# Ensure .opam directory exists (required for COPY --from in full-sandbox) +if [ ! -d "$HOME/.opam" ]; then + log_warning "Opam directory not found - creating minimal structure" + mkdir -p "$HOME/.opam" +fi + log_success "Rocq/Coq installation complete"