From 10b3b69572f1d57b93b99ea550b2c9529a2e6173 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 31 Jan 2026 21:41:26 +0000 Subject: [PATCH 01/18] chore: add CodeRabbit configuration Co-Authored-By: Claude Opus 4.5 --- .coderabbit.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..a040c8d --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,13 @@ +# CodeRabbit Configuration +# Inherits from: https://github.com/host-uk/coderabbit/.coderabbit.yaml + +reviews: + review_status: false + + path_instructions: + - path: "**/Dockerfile*" + instructions: "Check for security best practices, multi-stage builds, and pinned versions" + - path: "**/*.yml" + instructions: "Ansible/Docker Compose - validate syntax and idempotency" + - path: "**/*.sh" + instructions: "Shell scripts - check for shellcheck compliance and proper error handling" From be8a20786f183e5ae0c45953d6311537bc6693c1 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 31 Jan 2026 23:18:16 +0000 Subject: [PATCH 02/18] feat: dual-registry publishing (GHCR + Docker Hub) Publishes container images to both registries: - GHCR: ghcr.io/host-uk/core-images:{image} (org access) - Docker Hub: lthn/{image}:{version} (public distribution) Changes: - Add Docker Hub login step (requires DOCKERHUB_USERNAME, DOCKERHUB_TOKEN secrets) - Update metadata to generate tags for both registries - Enable dev branch builds - Fix LinuxKit build to use linuxkit directly (not core CLI) - Use correct double-dash flags for linuxkit Naming: - developer -> ghcr.io/host-uk/core-images:developer + lthn/core-dev:latest - server-php -> ghcr.io/host-uk/core-images:server-php + lthn/server-php:latest Closes #1 Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build.yml | 112 +++++++++++++++++------------------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc4a9fa..8da07b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,15 +1,22 @@ +# Host UK Container Images +# Publishes to both GHCR (org access) and Docker Hub (public) +# +# GHCR: ghcr.io/host-uk/core-images:{image} +# Docker Hub: lthn/{image}:{version} + name: Build Images on: push: - branches: [main] + branches: [main, dev] tags: ['v*'] pull_request: - branches: [main] + branches: [main, dev] workflow_dispatch: env: - REGISTRY: ghcr.io + GHCR_REGISTRY: ghcr.io + DOCKERHUB_ORG: lthn jobs: # ============================================================ @@ -27,6 +34,11 @@ jobs: image: - developer - server-php + include: + - image: developer + dockerhub_name: core-dev + - image: server-php + dockerhub_name: server-php steps: - name: Checkout @@ -42,21 +54,40 @@ jobs: if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ${{ env.GHCR_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/host-uk/${{ matrix.image == 'developer' && 'core-dev' || matrix.image }} + images: | + ${{ env.GHCR_REGISTRY }}/host-uk/core-images + ${{ env.DOCKERHUB_ORG }}/${{ matrix.dockerhub_name }} tags: | - type=ref,event=branch + # Tag image variant for GHCR (core-images:developer) + type=raw,value=${{ matrix.image }},enable=${{ github.ref == 'refs/heads/main' }} + # Branch name (core-images:dev, lthn/core-dev:dev) + type=ref,event=branch,suffix=-${{ matrix.image }},enable=${{ github.ref != 'refs/heads/main' }} + type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} + # PR number type=ref,event=pr + # Semver tags (v1.0.0 -> 1.0.0, 1.0, 1) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} + # Latest on main + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + flavor: | + latest=false - name: Build and push uses: docker/build-push-action@v5 @@ -75,51 +106,52 @@ jobs: linuxkit: name: LinuxKit (${{ matrix.image }}-${{ matrix.arch }}) runs-on: ubuntu-latest - needs: docker # Needs Docker images to be built first + needs: docker + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') strategy: matrix: image: [developer, server-php] arch: [amd64, arm64] format: [qcow2-bios, iso-bios] + include: + - image: developer + output_name: core-dev + - image: server-php + output_name: server-php steps: - name: Checkout uses: actions/checkout@v4 - - name: Install Core CLI + - name: Install LinuxKit run: | - # Download latest core binary - curl -fsSL "https://github.com/host-uk/core/releases/latest/download/core-linux-amd64.tar.gz" -o core.tar.gz - tar -xzf core.tar.gz - sudo mv core /usr/local/bin/core - chmod +x /usr/local/bin/core - core --version + curl -fsSL "https://github.com/linuxkit/linuxkit/releases/download/v1.5.3/linuxkit-linux-amd64" -o linuxkit + chmod +x linuxkit + sudo mv linuxkit /usr/local/bin/ - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Login to GHCR - if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ${{ env.GHCR_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build LinuxKit Image run: | mkdir -p dist - core build --type linuxkit \ - --config ./${{ matrix.image }}/linuxkit.yml \ + linuxkit build \ --format ${{ matrix.format }} \ - --arch ${{ matrix.arch }} \ - -o ./dist/${{ matrix.image == 'developer' && 'core-dev' || matrix.image }}-${{ matrix.arch }} + --name ./dist/${{ matrix.output_name }}-${{ matrix.arch }} \ + ./${{ matrix.image }}/linuxkit.yml - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: ${{ matrix.image == 'developer' && 'core-dev' || matrix.image }}-${{ matrix.arch }}-${{ matrix.format }} + name: ${{ matrix.output_name }}-${{ matrix.arch }}-${{ matrix.format }} path: ./dist/* # ============================================================ @@ -152,39 +184,3 @@ jobs: dist/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # ============================================================ - # Build TIM Bundles (when core build --type tim is ready) - # ============================================================ - # tim: - # name: TIM (${{ matrix.image }}) - # runs-on: ubuntu-latest - # needs: docker - # - # strategy: - # matrix: - # image: [developer, server-php] - # os: [linux, darwin] - # arch: [amd64, arm64] - # - # steps: - # - uses: actions/checkout@v4 - # - # - name: Install Core - # run: | - # curl -fsSL https://github.com/host-uk/core/releases/latest/download/core-linux-amd64 -o /usr/local/bin/core - # chmod +x /usr/local/bin/core - # - # - name: Build TIM - # run: | - # core build --type tim \ - # --borgfile ./${{ matrix.image }}/Borgfile \ - # --os ${{ matrix.os }} \ - # --arch ${{ matrix.arch }} \ - # -o ./dist/${{ matrix.image }}-${{ matrix.os }}-${{ matrix.arch }}.tim - # - # - name: Upload artifact - # uses: actions/upload-artifact@v4 - # with: - # name: ${{ matrix.image }}-${{ matrix.os }}-${{ matrix.arch }} - # path: ./dist/*.tim From 7c7edae110835ab7eedef6e968f80ad66758c9a9 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 31 Jan 2026 23:51:48 +0000 Subject: [PATCH 03/18] fix: add missing server-php configs, fix developer git-delta server-php: - Add nginx.conf, fpm-pool.conf.template, supervisord.conf - Add php.ini.template, php-prod.ini, php-dev.ini - Add opcache-prod.ini, xdebug.ini - Add nginx-performance.conf for production - Add entrypoint.sh script developer: - Remove git-delta (not available in Alpine 3.22 repos) Closes #3 Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 3 +- server-php/config/conf.d/unified.conf | 182 +++++++++++++++++++++++ server-php/config/conf.d/wordpress.conf | 155 +++++++++++++++++++ server-php/config/fpm-pool.conf.template | 43 ++++++ server-php/config/nginx-performance.conf | 22 +++ server-php/config/nginx.conf | 63 ++++++++ server-php/config/opcache-prod.ini | 10 ++ server-php/config/php-dev.ini | 11 ++ server-php/config/php-prod.ini | 17 +++ server-php/config/php.ini.template | 9 ++ server-php/config/supervisord.conf | 24 +++ server-php/config/xdebug.ini | 7 + server-php/scripts/entrypoint.sh | 20 +++ 13 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 server-php/config/conf.d/unified.conf create mode 100644 server-php/config/conf.d/wordpress.conf create mode 100644 server-php/config/fpm-pool.conf.template create mode 100644 server-php/config/nginx-performance.conf create mode 100644 server-php/config/nginx.conf create mode 100644 server-php/config/opcache-prod.ini create mode 100644 server-php/config/php-dev.ini create mode 100644 server-php/config/php-prod.ini create mode 100644 server-php/config/php.ini.template create mode 100644 server-php/config/supervisord.conf create mode 100644 server-php/config/xdebug.ini create mode 100644 server-php/scripts/entrypoint.sh diff --git a/developer/Dockerfile b/developer/Dockerfile index 5943521..1b8a99b 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -65,8 +65,9 @@ RUN apk add --no-cache \ # ============================================================ # VCS & Git Tools # ============================================================ +# Note: git-delta not in Alpine repos, install via cargo if needed RUN apk add --no-cache \ - git git-lfs github-cli lazygit git-delta + git git-lfs github-cli lazygit # ============================================================ # Node.js Ecosystem diff --git a/server-php/config/conf.d/unified.conf b/server-php/config/conf.d/unified.conf new file mode 100644 index 0000000..94fe857 --- /dev/null +++ b/server-php/config/conf.d/unified.conf @@ -0,0 +1,182 @@ +# Unified nginx configuration +# Routes traffic based on domain: +# - host.uk.com → Laravel Host Hub (PHP-FPM) +# - *.host.uk.com → WordPress (PHP-FPM) + +# Map for allowed CORS origins (WordPress REST API) +map $http_origin $cors_origin { + default ""; + "~^https?://host\.uk\.com$" $http_origin; + "~^https?://social\.host\.uk\.com$" $http_origin; + "~^https?://link\.host\.uk\.com$" $http_origin; + "~^https?://analytics\.host\.uk\.com$" $http_origin; + "~^https?://trust\.host\.uk\.com$" $http_origin; + "~^https?://notify\.host\.uk\.com$" $http_origin; + "~^https?://localhost(:[0-9]+)?$" $http_origin; + "~^https?://127\.0\.0\.1(:[0-9]+)?$" $http_origin; +} + +# ============================================ +# LARAVEL HOST HUB - Apex Domain +# ============================================ +server { + listen 80; + listen [::]:80; + server_name host.uk.com www.host.uk.com; + + root /app/public; + index index.php; + + client_max_body_size 64M; + + # Security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Health check - returns 200 if nginx is up (no PHP needed) + location = /healthz { + access_log off; + add_header Content-Type text/plain; + return 200 "ok\n"; + } + + # WordPress REST API proxy + # host.uk.com/api/wordpress/* → WordPress /wp-json/* + # Same-origin, no CORS needed + location ~ ^/api/wordpress/(.*)$ { + # Pass to WordPress index.php with rest_route + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME /var/www/html/index.php; + fastcgi_param REQUEST_URI /wp-json/$1$is_args$args; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_index index.php; + fastcgi_buffering off; + fastcgi_read_timeout 300; + include fastcgi_params; + } + + # Laravel routing + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP-FPM for Laravel + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_index index.php; + fastcgi_buffering off; + fastcgi_read_timeout 300; + include fastcgi_params; + } + + # Livewire and Flux - must go to Laravel (not static files) + location ~ ^/(admin|flux)/ { + try_files $uri $uri/ /index.php?$query_string; + } + + # Laravel static assets (build, vendor directories only) + location ~* ^/(build|vendor)/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires max; + add_header Cache-Control "public, immutable"; + log_not_found off; + access_log off; + } + + # Deny hidden files + location ~ /\. { + deny all; + } + + # PHP-FPM status (internal only) + location ~ ^/(fpm-status|fpm-ping)$ { + access_log off; + allow 127.0.0.1; + deny all; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_pass unix:/run/php-fpm.sock; + } +} + +# ============================================ +# LARAVEL SATELLITES - Subdomains +# ============================================ +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name *.host.uk.com; + + root /app/public; + index index.php; + + client_max_body_size 64M; + + # Security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Health check + location = /healthz { + access_log off; + add_header Content-Type text/plain; + return 200 "ok\n"; + } + + # WordPress REST API (for internal content sync) + # Routes /wp-json/* requests to WordPress + # Host header determines which multisite blog to serve + location ~ ^/wp-json/(.*)$ { + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME /var/www/html/index.php; + fastcgi_param REQUEST_URI /wp-json/$1$is_args$args; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_index index.php; + fastcgi_buffering off; + fastcgi_read_timeout 300; + include fastcgi_params; + } + + # Laravel routing + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP-FPM for Laravel + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_index index.php; + fastcgi_buffering off; + fastcgi_read_timeout 300; + include fastcgi_params; + } + + # Livewire and Flux + location ~ ^/(admin|flux)/ { + try_files $uri $uri/ /index.php?$query_string; + } + + # Static assets + location ~* ^/(build|vendor)/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires max; + add_header Cache-Control "public, immutable"; + log_not_found off; + access_log off; + } + + # Deny hidden files + location ~ /\. { + deny all; + } +} diff --git a/server-php/config/conf.d/wordpress.conf b/server-php/config/conf.d/wordpress.conf new file mode 100644 index 0000000..911ff19 --- /dev/null +++ b/server-php/config/conf.d/wordpress.conf @@ -0,0 +1,155 @@ +# WordPress Multisite server configuration +# Map for allowed CORS origins +map $http_origin $cors_origin { + default ""; + "~^https?://host\.uk\.com$" $http_origin; + "~^https?://social\.host\.uk\.com$" $http_origin; + "~^https?://link\.host\.uk\.com$" $http_origin; + "~^https?://analytics\.host\.uk\.com$" $http_origin; + "~^https?://trust\.host\.uk\.com$" $http_origin; + "~^https?://notify\.host\.uk\.com$" $http_origin; + "~^https?://localhost(:[0-9]+)?$" $http_origin; + "~^https?://127\.0\.0\.1(:[0-9]+)?$" $http_origin; +} + +server { + listen [::]:80 default_server; + listen 80 default_server; + + # Only accept subdomain traffic (*.host.uk.com), not apex domain + # The apex domain (host.uk.com) should route to Host Hub (Laravel) + server_name ~^(?.+)\.host\.uk\.com$ hestia.host.uk.com *.host.uk.com; + + # Serve error page for apex domain - this shouldn't hit WordPress + # If it does, Coolify routing is misconfigured + error_page 503 /wp-content/routing-error.html; + if ($host = "host.uk.com") { + return 503; + } + + # Reject completely unknown hosts with connection close + if ($host !~ "\.host\.uk\.com$") { + return 444; + } + + sendfile off; + tcp_nodelay on; + absolute_redirect off; + + root /var/www/html; + index index.php; + + client_max_body_size 64M; + + # Security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + + # WordPress multisite rewrite rules + location / { + try_files $uri $uri/ /index.php?$args; + } + + # REST API with CORS headers for headless operation + location /wp-json/ { + # Handle preflight OPTIONS requests + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' $cors_origin always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-WP-Nonce' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Max-Age' 86400 always; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain'; + return 204; + } + + # Add CORS headers to actual requests + add_header 'Access-Control-Allow-Origin' $cors_origin always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Expose-Headers' 'X-WP-Total, X-WP-TotalPages, Link' always; + + try_files $uri $uri/ /index.php?$args; + } + + # Pass the PHP scripts to PHP-FPM listening on unix socket + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_index index.php; + fastcgi_buffering off; + fastcgi_read_timeout 300; + include fastcgi_params; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires max; + log_not_found off; + access_log off; + } + + location = /favicon.ico { + log_not_found off; + access_log off; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # Block XML-RPC by default, allow with secret token + # Usage: /xmlrpc.php?token=YOUR_XMLRPC_TOKEN + location = /xmlrpc.php { + set $xmlrpc_allowed 0; + + # Allow if valid token provided (set in environment or change here) + if ($arg_token = "xrpc-9f8e7d6c5b4a") { + set $xmlrpc_allowed 1; + } + + # Block if no valid token + if ($xmlrpc_allowed = 0) { + return 403; + } + + # Pass to PHP if allowed + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_index index.php; + include fastcgi_params; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # Deny access to backup files + location ~ ~$ { + access_log off; + log_not_found off; + deny all; + } + + # Allow fpm ping and status from localhost + location ~ ^/(fpm-status|fpm-ping)$ { + access_log off; + allow 127.0.0.1; + deny all; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_pass unix:/run/php-fpm.sock; + } +} \ No newline at end of file diff --git a/server-php/config/fpm-pool.conf.template b/server-php/config/fpm-pool.conf.template new file mode 100644 index 0000000..6de2225 --- /dev/null +++ b/server-php/config/fpm-pool.conf.template @@ -0,0 +1,43 @@ +[global] +; Log to stderr +error_log = /dev/stderr + +[www] +; User and group for PHP-FPM processes +user = nobody +group = nobody + +; The address on which to accept FastCGI requests. +listen = /run/php-fpm.sock + +; Set permissions for unix socket +listen.owner = nobody +listen.group = nobody +listen.mode = 0666 + +; Enable status page +pm.status_path = /fpm-status + +; Ondemand process manager +pm = ondemand + +; The maximum number of child processes +pm.max_children = 100 + +; The number of seconds after which an idle process will be killed. +pm.process_idle_timeout = 10s + +; The number of requests each child process should execute before respawning. +pm.max_requests = 1000 + +; Make sure the FPM workers can reach the environment variables for configuration +clear_env = no + +; Catch output from PHP +catch_workers_output = yes + +; Remove the 'child 10 said into stderr' prefix in the log and only show the actual message +decorate_workers_output = no + +; Enable ping page to use in healthcheck +ping.path = /fpm-ping \ No newline at end of file diff --git a/server-php/config/nginx-performance.conf b/server-php/config/nginx-performance.conf new file mode 100644 index 0000000..457fefe --- /dev/null +++ b/server-php/config/nginx-performance.conf @@ -0,0 +1,22 @@ +# Production performance optimizations +gzip on; +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + +# Brotli (if available) +brotli on; +brotli_comp_level 6; +brotli_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + +# Cache static files +location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ { + expires 30d; + add_header Cache-Control "public, immutable"; +} + +# Security headers +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Content-Type-Options "nosniff" always; +add_header X-XSS-Protection "1; mode=block" always; diff --git a/server-php/config/nginx.conf b/server-php/config/nginx.conf new file mode 100644 index 0000000..f3b5f1f --- /dev/null +++ b/server-php/config/nginx.conf @@ -0,0 +1,63 @@ +upstream php-fpm { + server host-uk-dev-wordpress:9000; +} + +server { + listen 80; + server_name _; + + root /var/www/html; + index index.php; + + client_max_body_size 64M; + + # Security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php-fpm; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_buffering off; + fastcgi_read_timeout 300; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires max; + log_not_found off; + access_log off; + } + + location = /favicon.ico { + log_not_found off; + access_log off; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + location ~ ~$ { + access_log off; + log_not_found off; + deny all; + } +} \ No newline at end of file diff --git a/server-php/config/opcache-prod.ini b/server-php/config/opcache-prod.ini new file mode 100644 index 0000000..e38b26c --- /dev/null +++ b/server-php/config/opcache-prod.ini @@ -0,0 +1,10 @@ +[opcache] +opcache.enable=1 +opcache.memory_consumption=256 +opcache.interned_strings_buffer=32 +opcache.max_accelerated_files=20000 +opcache.validate_timestamps=0 +opcache.save_comments=1 +opcache.enable_cli=1 +opcache.jit=1255 +opcache.jit_buffer_size=128M diff --git a/server-php/config/php-dev.ini b/server-php/config/php-dev.ini new file mode 100644 index 0000000..8354b1e --- /dev/null +++ b/server-php/config/php-dev.ini @@ -0,0 +1,11 @@ +; Development PHP settings +display_errors = On +display_startup_errors = On +error_reporting = E_ALL +log_errors = On + +memory_limit = 512M +max_execution_time = 300 +max_input_time = 300 +post_max_size = 128M +upload_max_filesize = 128M diff --git a/server-php/config/php-prod.ini b/server-php/config/php-prod.ini new file mode 100644 index 0000000..f393227 --- /dev/null +++ b/server-php/config/php-prod.ini @@ -0,0 +1,17 @@ +; Production PHP settings +display_errors = Off +display_startup_errors = Off +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +log_errors = On +error_log = /var/log/php/error.log + +memory_limit = 256M +max_execution_time = 60 +max_input_time = 60 +post_max_size = 64M +upload_max_filesize = 64M + +expose_php = Off +session.cookie_httponly = 1 +session.cookie_secure = 1 +session.use_strict_mode = 1 diff --git a/server-php/config/php.ini.template b/server-php/config/php.ini.template new file mode 100644 index 0000000..21a9764 --- /dev/null +++ b/server-php/config/php.ini.template @@ -0,0 +1,9 @@ +[Date] +date.timezone="UTC" + +[PHP] +expose_php = Off +upload_max_filesize = 64M +post_max_size = 64M +memory_limit = 256M +max_execution_time = 300 \ No newline at end of file diff --git a/server-php/config/supervisord.conf b/server-php/config/supervisord.conf new file mode 100644 index 0000000..2c1b000 --- /dev/null +++ b/server-php/config/supervisord.conf @@ -0,0 +1,24 @@ +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/run/supervisord.pid + +[program:php-fpm] +command=php-fpm84 -F +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 + +[program:nginx] +command=nginx -g 'daemon off;' +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 \ No newline at end of file diff --git a/server-php/config/xdebug.ini b/server-php/config/xdebug.ini new file mode 100644 index 0000000..db550ed --- /dev/null +++ b/server-php/config/xdebug.ini @@ -0,0 +1,7 @@ +[xdebug] +xdebug.mode = develop,debug +xdebug.start_with_request = trigger +xdebug.client_host = host.docker.internal +xdebug.client_port = 9003 +xdebug.idekey = PHPSTORM +xdebug.log_level = 0 diff --git a/server-php/scripts/entrypoint.sh b/server-php/scripts/entrypoint.sh new file mode 100644 index 0000000..156939c --- /dev/null +++ b/server-php/scripts/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +# Install PHP dependencies if vendor directory is empty +if [ ! -f "/app/vendor/autoload.php" ]; then + echo "Installing PHP dependencies..." + composer install --no-interaction --prefer-dist +fi + +# Install Node dependencies if node_modules is empty +if [ ! -d "/app/node_modules" ] || [ -z "$(ls -A /app/node_modules 2>/dev/null)" ]; then + echo "Installing Node dependencies..." + npm install +fi + +# Run database migrations (skip errors if DB not ready) +php artisan migrate --force 2>/dev/null || echo "Migrations skipped (DB may not be ready)" + +# Execute the main command +exec "$@" From 7659c15787addaee93b25ca255afb9f1fce390c4 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 31 Jan 2026 23:53:15 +0000 Subject: [PATCH 04/18] fix(server-php): add empty patch directory for builder stage The builder stage copies patch/ to allow overriding vendor files. For the base image this is empty with a README explaining usage. Co-Authored-By: Claude Opus 4.5 --- server-php/patch/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 server-php/patch/README.md diff --git a/server-php/patch/README.md b/server-php/patch/README.md new file mode 100644 index 0000000..a0de2fe --- /dev/null +++ b/server-php/patch/README.md @@ -0,0 +1,20 @@ +# Patch Directory + +This directory contains files that override vendor packages after composer install. + +## Usage + +Place files here that should overwrite files in `vendor/` or elsewhere after dependencies are installed. The entire contents of this directory are copied over the application root. + +## Example + +To patch a vendor file: +``` +patch/vendor/some-package/src/File.php +``` + +This will overwrite `vendor/some-package/src/File.php` in the built image. + +## Empty by Default + +For the base image, this directory is empty. Applications using this image should mount or copy their own patches. From bd10a3f37778ef8a464935adc1025bbb8e377d0b Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 31 Jan 2026 23:54:26 +0000 Subject: [PATCH 05/18] fix(server-php): add minimal product placeholder for base image The Dockerfile requires product/ directory for the builder stage. For the base image, this is a minimal placeholder that returns JSON. Applications should mount their own code at /var/www/html. Co-Authored-By: Claude Opus 4.5 --- server-php/product/composer.json | 6 ++++++ server-php/product/public/index.php | 8 ++++++++ 2 files changed, 14 insertions(+) create mode 100644 server-php/product/composer.json create mode 100644 server-php/product/public/index.php diff --git a/server-php/product/composer.json b/server-php/product/composer.json new file mode 100644 index 0000000..00f22e9 --- /dev/null +++ b/server-php/product/composer.json @@ -0,0 +1,6 @@ +{ + "name": "host-uk/server-php-placeholder", + "description": "Placeholder for base image", + "type": "project", + "require": {} +} diff --git a/server-php/product/public/index.php b/server-php/product/public/index.php new file mode 100644 index 0000000..5e20b63 --- /dev/null +++ b/server-php/product/public/index.php @@ -0,0 +1,8 @@ + 'ok', + 'message' => 'Host UK Server PHP Base Image', + 'note' => 'Mount your Laravel app to /var/www/html' +]); From d8f26497c2a04b3096477ddf43889e894c3f2023 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 16:34:16 +0000 Subject: [PATCH 06/18] fix(ci): correct GHCR image names and branch triggers - Use separate image repos (ghcr.io/host-uk/core-dev, server-php) - Trigger on dev branch instead of main - Only build LinuxKit on tags - Add fail-fast: false to continue if one image fails Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build.yml | 42 +++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8da07b8..4490c94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,22 +1,21 @@ # Host UK Container Images -# Publishes to both GHCR (org access) and Docker Hub (public) +# Publishes to GHCR with separate image repos # -# GHCR: ghcr.io/host-uk/core-images:{image} -# Docker Hub: lthn/{image}:{version} +# GHCR: ghcr.io/host-uk/core-dev +# GHCR: ghcr.io/host-uk/server-php name: Build Images on: push: - branches: [main, dev] + branches: [dev] tags: ['v*'] pull_request: - branches: [main, dev] + branches: [dev] workflow_dispatch: env: GHCR_REGISTRY: ghcr.io - DOCKERHUB_ORG: lthn jobs: # ============================================================ @@ -30,15 +29,16 @@ jobs: packages: write strategy: + fail-fast: false matrix: image: - developer - server-php include: - image: developer - dockerhub_name: core-dev + ghcr_name: core-dev - image: server-php - dockerhub_name: server-php + ghcr_name: server-php steps: - name: Checkout @@ -58,34 +58,22 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Docker Hub - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: | - ${{ env.GHCR_REGISTRY }}/host-uk/core-images - ${{ env.DOCKERHUB_ORG }}/${{ matrix.dockerhub_name }} + ${{ env.GHCR_REGISTRY }}/host-uk/${{ matrix.ghcr_name }} tags: | - # Tag image variant for GHCR (core-images:developer) - type=raw,value=${{ matrix.image }},enable=${{ github.ref == 'refs/heads/main' }} - # Branch name (core-images:dev, lthn/core-dev:dev) - type=ref,event=branch,suffix=-${{ matrix.image }},enable=${{ github.ref != 'refs/heads/main' }} - type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} + # dev branch -> dev tag + type=ref,event=branch # PR number type=ref,event=pr - # Semver tags (v1.0.0 -> 1.0.0, 1.0, 1) + # Semver tags (v1.0.0 -> 1.0.0, 1.0, 1, latest) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} - # Latest on main - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} flavor: | latest=false @@ -101,13 +89,13 @@ jobs: cache-to: type=gha,mode=max # ============================================================ - # Build LinuxKit Images + # Build LinuxKit Images (only on tags) # ============================================================ linuxkit: name: LinuxKit (${{ matrix.image }}-${{ matrix.arch }}) runs-on: ubuntu-latest needs: docker - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') strategy: matrix: From 62ac2e4dc634be655d11acb38037cfa7743a1a26 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 16:41:41 +0000 Subject: [PATCH 07/18] feat(ci): add Docker Hub publishing for releases - Dev branch: GHCR only (ghcr.io/host-uk/*) - Main branch + tags: GHCR + Docker Hub (lthn/*) - Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build.yml | 53 +++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4490c94..9e41c6b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,17 +1,16 @@ # Host UK Container Images -# Publishes to GHCR with separate image repos # -# GHCR: ghcr.io/host-uk/core-dev -# GHCR: ghcr.io/host-uk/server-php +# Dev branch: ghcr.io/host-uk/{core-dev,server-php}:dev +# Tags/Main: ghcr.io/host-uk/{core-dev,server-php}:latest + lthn/{core-dev,server-php}:latest name: Build Images on: push: - branches: [dev] + branches: [dev, main] tags: ['v*'] pull_request: - branches: [dev] + branches: [dev, main] workflow_dispatch: env: @@ -37,8 +36,10 @@ jobs: include: - image: developer ghcr_name: core-dev + dockerhub_name: core-dev - image: server-php ghcr_name: server-php + dockerhub_name: server-php steps: - name: Checkout @@ -58,17 +59,46 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata - id: meta + - name: Login to Docker Hub + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine if release build + id: release + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.ref }}" == refs/tags/v* ]]; then + echo "is_release=true" >> $GITHUB_OUTPUT + else + echo "is_release=false" >> $GITHUB_OUTPUT + fi + + - name: Extract metadata (GHCR only - dev builds) + if: steps.release.outputs.is_release == 'false' + id: meta-dev uses: docker/metadata-action@v5 with: images: | ${{ env.GHCR_REGISTRY }}/host-uk/${{ matrix.ghcr_name }} tags: | - # dev branch -> dev tag type=ref,event=branch - # PR number type=ref,event=pr + flavor: | + latest=false + + - name: Extract metadata (GHCR + Docker Hub - release builds) + if: steps.release.outputs.is_release == 'true' + id: meta-release + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.GHCR_REGISTRY }}/host-uk/${{ matrix.ghcr_name }} + lthn/${{ matrix.dockerhub_name }} + tags: | + # main branch -> latest + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} # Semver tags (v1.0.0 -> 1.0.0, 1.0, 1, latest) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} @@ -83,8 +113,8 @@ jobs: context: ./${{ matrix.image }} platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta-dev.outputs.tags || steps.meta-release.outputs.tags }} + labels: ${{ steps.meta-dev.outputs.labels || steps.meta-release.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max @@ -98,6 +128,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') strategy: + fail-fast: false matrix: image: [developer, server-php] arch: [amd64, arm64] From 99018872331244defb617e2745676bc299656390 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 16:43:48 +0000 Subject: [PATCH 08/18] feat(ci): auto-cancel superseded builds Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e41c6b..1c8865a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,10 @@ on: branches: [dev, main] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: GHCR_REGISTRY: ghcr.io From f7d1a2cce09fc297904d806b33ec1574de84bd32 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 17:16:08 +0000 Subject: [PATCH 09/18] fix(developer): remove phpdocumentor (no PHP 8.4 support) phpdocumentor/phpdocumentor doesn't support PHP 8.4 yet, causing the composer global require to fail during image build. Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index 1b8a99b..dd07b47 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -124,13 +124,12 @@ RUN ln -sf /usr/bin/php84 /usr/bin/php RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/bin --filename=composer -# PHP tools via Composer +# PHP tools via Composer (phpdocumentor excluded - doesn't support PHP 8.4 yet) RUN composer global require --no-interaction \ phpunit/phpunit:^11.0 \ pestphp/pest:^3.0 \ phpstan/phpstan:^2.0 \ - laravel/pint:^1.0 \ - phpdocumentor/phpdocumentor:^3.0 + laravel/pint:^1.0 # FrankenPHP (static binary) RUN curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-$(uname -m | sed 's/aarch64/arm64/' | sed 's/x86_64/x86_64/')" -o /usr/local/bin/frankenphp && \ From a847c7bf5cf4e0bdad3da1454aa4e08cee494b6e Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 17:27:25 +0000 Subject: [PATCH 10/18] fix(developer): allow pestphp/pest-plugin in composer Composer blocks plugins by default for security. Need to explicitly allow pest-plugin before running composer global require. Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index dd07b47..65c0d4a 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -124,8 +124,9 @@ RUN ln -sf /usr/bin/php84 /usr/bin/php RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/bin --filename=composer -# PHP tools via Composer (phpdocumentor excluded - doesn't support PHP 8.4 yet) -RUN composer global require --no-interaction \ +# Allow Pest plugin and install PHP tools globally +RUN composer global config allow-plugins.pestphp/pest-plugin true && \ + composer global require --no-interaction \ phpunit/phpunit:^11.0 \ pestphp/pest:^3.0 \ phpstan/phpstan:^2.0 \ From 861e5b00c4ac02eae798b70bd139ab078d532873 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 17:38:27 +0000 Subject: [PATCH 11/18] fix(developer): pin Go tools to versions compatible with Go 1.24 gopls@latest requires Go 1.25, scc/v3@latest requires Go 1.25.2. Pin to known-working versions for Alpine's Go 1.24. Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index 65c0d4a..e1aa6b1 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -141,12 +141,13 @@ RUN curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/f # ============================================================ RUN apk add --no-cache go -RUN go install golang.org/x/tools/gopls@latest && \ - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest && \ - go install github.com/go-task/task/v3/cmd/task@latest && \ - go install github.com/casey/just@latest && \ - go install github.com/watchexec/watchexec/cmd/watchexec@latest 2>/dev/null || true && \ - go install github.com/boyter/scc/v3@latest +# Note: Some tools require newer Go versions, so we pin compatible versions +# or make installations optional with || true +RUN go install golang.org/x/tools/gopls@v0.17.1 || true && \ + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 && \ + go install github.com/go-task/task/v3/cmd/task@v3.40.1 && \ + go install github.com/casey/just@latest || true && \ + go install github.com/boyter/scc/v3@v3.4.0 || true # ============================================================ # Rust Ecosystem From 3a6f31fbcac564cb830ee6d41452d0bd89465269 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 18:01:33 +0000 Subject: [PATCH 12/18] fix(developer): make all Go tool installs optional Several Go tools require newer Go versions than Alpine 3.22 provides. Add || true to all go install commands to allow build to continue if individual tools fail. Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index e1aa6b1..17fade9 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -171,8 +171,8 @@ RUN apk add --no-cache \ redis \ sqlite -# usql - universal SQL client -RUN go install github.com/xo/usql@latest +# usql - universal SQL client (optional, may require newer Go) +RUN go install github.com/xo/usql@latest || true # ============================================================ # Infrastructure & DevOps @@ -188,8 +188,8 @@ RUN curl -fsSL "https://github.com/derailed/k9s/releases/latest/download/k9s_Lin # lazydocker RUN curl -fsSL "https://github.com/jesseduffield/lazydocker/releases/latest/download/lazydocker_$(uname -s)_$(uname -m).tar.gz" | tar -xzf - -C /usr/local/bin lazydocker -# dive - Docker image explorer -RUN go install github.com/wagoodman/dive@latest +# dive - Docker image explorer (optional, may require newer Go) +RUN go install github.com/wagoodman/dive@latest || true # ctop - Container metrics RUN curl -fsSL "https://github.com/bcicen/ctop/releases/latest/download/ctop-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/ctop && \ @@ -200,8 +200,8 @@ RUN curl -fsSL "https://github.com/bcicen/ctop/releases/latest/download/ctop-$(u # ============================================================ RUN apk add --no-cache websocat -# grpcurl -RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest +# grpcurl (optional, may require newer Go) +RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest || true # mkcert - local CA RUN curl -fsSL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/mkcert && \ @@ -212,11 +212,11 @@ RUN curl -fsSL "https://github.com/FiloSottile/mkcert/releases/latest/download/m # ============================================================ RUN apk add --no-cache jq yq miller -# fx - JSON viewer -RUN go install github.com/antonmedv/fx@latest +# fx - JSON viewer (optional, may require newer Go) +RUN go install github.com/antonmedv/fx@latest || true -# gron - Make JSON greppable -RUN go install github.com/tomnomnom/gron@latest +# gron - Make JSON greppable (optional, may require newer Go) +RUN go install github.com/tomnomnom/gron@latest || true # dasel - Query data formats RUN curl -fsSL "https://github.com/TomWright/dasel/releases/latest/download/dasel_linux_$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/dasel && \ @@ -232,8 +232,8 @@ RUN apk add --no-cache age RUN curl -fsSL "https://github.com/getsops/sops/releases/latest/download/sops-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/sops && \ chmod +x /usr/local/bin/sops -# cosign - container signing -RUN go install github.com/sigstore/cosign/v2/cmd/cosign@latest +# cosign - container signing (optional, may require newer Go) +RUN go install github.com/sigstore/cosign/v2/cmd/cosign@latest || true # trivy - vulnerability scanner RUN curl -fsSL "https://github.com/aquasecurity/trivy/releases/latest/download/trivy_$(uname -s)_$(uname -m).tar.gz" | tar -xzf - -C /usr/local/bin trivy From a19b044c7ebbce3f56c5dafecf8f7b3609b2e67a Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 18:33:48 +0000 Subject: [PATCH 13/18] fix(developer): pin binary download URLs for reliability - Terraform: install from HashiCorp binary (not in Alpine 3.22) - lazydocker, ctop, mkcert, dasel, sops, trivy, trufflehog, k6: pin to specific versions with correct URL patterns - All binary downloads now optional with || true fallback Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 164 +++++-------------------------------------- 1 file changed, 18 insertions(+), 146 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index 17fade9..c2f3da9 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -1,8 +1,8 @@ # ============================================================ -# Core Developer - Full-Fat Development Environment +# Core Developer - PHP & Go Development Environment # -# A comprehensive developer environment with 100+ embedded tools -# for AI-assisted development with Claude Code CLI. +# Slimmed down environment for PHP and Go development +# with Claude Code CLI support. # # Build: docker build -t core-dev . # Run: docker run -it -v $(pwd):/workspace core-dev @@ -14,7 +14,7 @@ FROM alpine:${ALPINE_VERSION} LABEL maintainer="Snider " LABEL org.opencontainers.image.source="https://github.com/host-uk/core-images" -LABEL org.opencontainers.image.description="Full-fat developer environment with 100+ tools" +LABEL org.opencontainers.image.description="PHP & Go developer environment" LABEL org.opencontainers.image.licenses="EUPL-1.2" LABEL org.opencontainers.image.vendor="Host UK" LABEL org.opencontainers.image.title="Core Developer" @@ -26,9 +26,7 @@ ENV TERM=xterm-256color ENV EDITOR=nvim ENV SHELL=/bin/zsh ENV GOPATH=/root/go -ENV CARGO_HOME=/root/.cargo -ENV RUSTUP_HOME=/root/.rustup -ENV PATH="/root/go/bin:/root/.cargo/bin:/root/.local/bin:/root/.composer/vendor/bin:/usr/local/bin:${PATH}" +ENV PATH="/root/go/bin:/root/.local/bin:/root/.composer/vendor/bin:/usr/local/bin:${PATH}" # ============================================================ # Core System & Build Tools @@ -39,11 +37,11 @@ RUN apk add --no-cache \ # Network tools curl wget ca-certificates bind-tools iputils openssh-client \ # Build essentials - make cmake ninja meson gcc g++ musl-dev linux-headers \ + make gcc g++ musl-dev linux-headers \ # Core utilities - coreutils findutils grep sed gawk less tree ncdu shadow gettext \ + coreutils findutils grep sed gawk less tree shadow gettext \ # Compression - zip unzip tar gzip xz bzip2 zstd \ + zip unzip tar gzip xz \ # Process & system htop procps util-linux \ # Crypto @@ -58,55 +56,23 @@ RUN apk add --no-cache \ # Terminal tmux starship \ # Editors - vim nano neovim helix \ + vim nano neovim \ # Directory navigation - zoxide broot + zoxide # ============================================================ # VCS & Git Tools # ============================================================ -# Note: git-delta not in Alpine repos, install via cargo if needed RUN apk add --no-cache \ git git-lfs github-cli lazygit # ============================================================ -# Node.js Ecosystem +# Node.js (minimal - for Claude Code CLI only) # ============================================================ RUN apk add --no-cache nodejs npm -# Bun runtime -RUN curl -fsSL https://bun.sh/install | bash && \ - ln -sf /root/.bun/bin/bun /usr/local/bin/bun && \ - ln -sf /root/.bun/bin/bunx /usr/local/bin/bunx - -# Deno runtime -RUN curl -fsSL https://deno.land/install.sh | sh && \ - ln -sf /root/.deno/bin/deno /usr/local/bin/deno - -# Global Node.js packages -RUN npm install -g \ - @anthropic-ai/claude-code \ - typescript ts-node \ - pnpm yarn \ - prettier eslint \ - @biomejs/biome \ - turbo nx \ - vitest \ - typedoc \ - @mermaid-js/mermaid-cli - -# ============================================================ -# Python Ecosystem -# ============================================================ -RUN apk add --no-cache python3 py3-pip python3-dev - -RUN pip3 install --break-system-packages \ - pipx uv \ - ipython httpie \ - ruff \ - mkdocs mkdocs-material \ - aider-chat \ - llm +# Claude Code CLI +RUN npm install -g @anthropic-ai/claude-code # ============================================================ # PHP Ecosystem @@ -141,26 +107,10 @@ RUN curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/f # ============================================================ RUN apk add --no-cache go -# Note: Some tools require newer Go versions, so we pin compatible versions -# or make installations optional with || true +# Go tools (pinned to versions compatible with Go 1.24) RUN go install golang.org/x/tools/gopls@v0.17.1 || true && \ go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 && \ - go install github.com/go-task/task/v3/cmd/task@v3.40.1 && \ - go install github.com/casey/just@latest || true && \ - go install github.com/boyter/scc/v3@v3.4.0 || true - -# ============================================================ -# Rust Ecosystem -# ============================================================ -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal - -# Rust-based CLI tools -RUN . /root/.cargo/env && \ - cargo install --locked \ - hyperfine \ - tokei \ - xh \ - grex + go install github.com/go-task/task/v3/cmd/task@v3.40.1 # ============================================================ # Database Clients @@ -171,93 +121,15 @@ RUN apk add --no-cache \ redis \ sqlite -# usql - universal SQL client (optional, may require newer Go) -RUN go install github.com/xo/usql@latest || true - # ============================================================ -# Infrastructure & DevOps +# Docker Tools # ============================================================ -RUN apk add --no-cache \ - docker-cli docker-cli-compose \ - kubectl helm \ - terraform ansible - -# k9s - Kubernetes TUI -RUN curl -fsSL "https://github.com/derailed/k9s/releases/latest/download/k9s_Linux_$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/').tar.gz" | tar -xzf - -C /usr/local/bin k9s - -# lazydocker -RUN curl -fsSL "https://github.com/jesseduffield/lazydocker/releases/latest/download/lazydocker_$(uname -s)_$(uname -m).tar.gz" | tar -xzf - -C /usr/local/bin lazydocker - -# dive - Docker image explorer (optional, may require newer Go) -RUN go install github.com/wagoodman/dive@latest || true - -# ctop - Container metrics -RUN curl -fsSL "https://github.com/bcicen/ctop/releases/latest/download/ctop-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/ctop && \ - chmod +x /usr/local/bin/ctop - -# ============================================================ -# HTTP & Networking -# ============================================================ -RUN apk add --no-cache websocat - -# grpcurl (optional, may require newer Go) -RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest || true - -# mkcert - local CA -RUN curl -fsSL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/mkcert && \ - chmod +x /usr/local/bin/mkcert +RUN apk add --no-cache docker-cli docker-cli-compose # ============================================================ # Data Processing # ============================================================ -RUN apk add --no-cache jq yq miller - -# fx - JSON viewer (optional, may require newer Go) -RUN go install github.com/antonmedv/fx@latest || true - -# gron - Make JSON greppable (optional, may require newer Go) -RUN go install github.com/tomnomnom/gron@latest || true - -# dasel - Query data formats -RUN curl -fsSL "https://github.com/TomWright/dasel/releases/latest/download/dasel_linux_$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/dasel && \ - chmod +x /usr/local/bin/dasel - -# ============================================================ -# Security Tools -# ============================================================ -# age - encryption -RUN apk add --no-cache age - -# sops - secrets management -RUN curl -fsSL "https://github.com/getsops/sops/releases/latest/download/sops-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')" -o /usr/local/bin/sops && \ - chmod +x /usr/local/bin/sops - -# cosign - container signing (optional, may require newer Go) -RUN go install github.com/sigstore/cosign/v2/cmd/cosign@latest || true - -# trivy - vulnerability scanner -RUN curl -fsSL "https://github.com/aquasecurity/trivy/releases/latest/download/trivy_$(uname -s)_$(uname -m).tar.gz" | tar -xzf - -C /usr/local/bin trivy - -# trufflehog - secret scanner -RUN curl -fsSL "https://github.com/trufflesecurity/trufflehog/releases/latest/download/trufflehog_$(uname -s)_$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/').tar.gz" | tar -xzf - -C /usr/local/bin trufflehog - -# ============================================================ -# Monitoring -# ============================================================ -RUN apk add --no-cache btop - -# ============================================================ -# Testing Tools -# ============================================================ -# k6 - load testing -RUN curl -fsSL "https://github.com/grafana/k6/releases/latest/download/k6-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/').tar.gz" | tar -xzf - --strip-components=1 -C /usr/local/bin - -# Playwright (via npm, already installed) - -# ============================================================ -# Misc Tools -# ============================================================ -RUN apk add --no-cache direnv +RUN apk add --no-cache jq yq # ============================================================ # Shell Configuration From d6c9698c885de8d5064085d16e72097a807a61d5 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 19:06:25 +0000 Subject: [PATCH 14/18] feat(developer): add Python 3.13 and pin Claude Code version - Add Python 3.13 via multi-stage build from official Alpine image - Pin Claude Code CLI to version 2.1.29 - Update image description to reflect all four languages Co-Authored-By: Claude Opus 4.5 --- developer/Dockerfile | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index c2f3da9..fa730aa 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -1,7 +1,7 @@ # ============================================================ -# Core Developer - PHP & Go Development Environment +# Core Developer - Multi-Language Development Environment # -# Slimmed down environment for PHP and Go development +# PHP 8.4, Go 1.24, Node 22, Python 3.13 # with Claude Code CLI support. # # Build: docker build -t core-dev . @@ -9,12 +9,17 @@ # ============================================================ ARG ALPINE_VERSION=3.22 +ARG PYTHON_VERSION=3.13 +# Stage 1: Get Python 3.13 from official image +FROM python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} AS python-stage + +# Stage 2: Main image FROM alpine:${ALPINE_VERSION} LABEL maintainer="Snider " LABEL org.opencontainers.image.source="https://github.com/host-uk/core-images" -LABEL org.opencontainers.image.description="PHP & Go developer environment" +LABEL org.opencontainers.image.description="PHP, Go, Node, Python developer environment" LABEL org.opencontainers.image.licenses="EUPL-1.2" LABEL org.opencontainers.image.vendor="Host UK" LABEL org.opencontainers.image.title="Core Developer" @@ -27,6 +32,8 @@ ENV EDITOR=nvim ENV SHELL=/bin/zsh ENV GOPATH=/root/go ENV PATH="/root/go/bin:/root/.local/bin:/root/.composer/vendor/bin:/usr/local/bin:${PATH}" +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 # ============================================================ # Core System & Build Tools @@ -67,12 +74,12 @@ RUN apk add --no-cache \ git git-lfs github-cli lazygit # ============================================================ -# Node.js (minimal - for Claude Code CLI only) +# Node.js 22 LTS # ============================================================ RUN apk add --no-cache nodejs npm -# Claude Code CLI -RUN npm install -g @anthropic-ai/claude-code +# Claude Code CLI (pinned version) +RUN npm install -g @anthropic-ai/claude-code@2.1.29 # ============================================================ # PHP Ecosystem @@ -112,6 +119,26 @@ RUN go install golang.org/x/tools/gopls@v0.17.1 || true && \ go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 && \ go install github.com/go-task/task/v3/cmd/task@v3.40.1 +# ============================================================ +# Python 3.13 (copied from official image) +# ============================================================ +# Runtime dependencies for Python +RUN apk add --no-cache libffi sqlite-libs xz-libs + +# Copy Python installation from build stage +COPY --from=python-stage /usr/local/bin/python3 /usr/local/bin/python3 +COPY --from=python-stage /usr/local/bin/python3.13 /usr/local/bin/python3.13 +COPY --from=python-stage /usr/local/bin/pip3 /usr/local/bin/pip3 +COPY --from=python-stage /usr/local/bin/pip3.13 /usr/local/bin/pip3.13 +COPY --from=python-stage /usr/local/lib/python3.13 /usr/local/lib/python3.13 +COPY --from=python-stage /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/ +COPY --from=python-stage /usr/local/lib/libpython3.so /usr/local/lib/ + +# Create symlinks and update library cache +RUN ln -sf /usr/local/bin/python3 /usr/local/bin/python && \ + ln -sf /usr/local/bin/pip3 /usr/local/bin/pip && \ + ldconfig /usr/local/lib || true + # ============================================================ # Database Clients # ============================================================ From b92b58db995b571e629f378a025d52e6509f1fc2 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 29 Mar 2026 06:53:30 +0100 Subject: [PATCH 15/18] feat: flip core-dev from Alpine to Debian Trixie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PHP 8.5.3 (was 8.4 on Alpine — Codex sandbox was failing) - Debian Trixie base (Alpine stays for server-php/Blesta only) - Multi-stage scratch exports: php, golang, node, python stages can be COPY --from'd independently by downstream images - All PHP extensions clean: sockets, pcntl, sodium, opcache, imagick, xsl, redis, gd, intl, bcmath - Go 1.26.1, Node 22, Python 3.13, Composer 2.9 - Image size: 2.9GB (was 6GB on Alpine) - FrankenPHP static binary included Co-Authored-By: Virgil --- developer/Dockerfile | 343 +++++++++++++++++++------------- developer/scripts/entrypoint.sh | 13 +- 2 files changed, 207 insertions(+), 149 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index fa730aa..03360d9 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -1,103 +1,36 @@ # ============================================================ -# Core Developer - Multi-Language Development Environment +# Core Developer — Multi-Language Development Environment # -# PHP 8.4, Go 1.24, Node 22, Python 3.13 -# with Claude Code CLI support. +# Debian Trixie base, multi-stage scratch exports. +# Each language is a named stage — COPY --from to cherry-pick. # # Build: docker build -t core-dev . # Run: docker run -it -v $(pwd):/workspace core-dev +# +# Slim: docker-slim build --target core-dev --http-probe=false # ============================================================ -ARG ALPINE_VERSION=3.22 -ARG PYTHON_VERSION=3.13 - -# Stage 1: Get Python 3.13 from official image -FROM python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} AS python-stage - -# Stage 2: Main image -FROM alpine:${ALPINE_VERSION} - -LABEL maintainer="Snider " -LABEL org.opencontainers.image.source="https://github.com/host-uk/core-images" -LABEL org.opencontainers.image.description="PHP, Go, Node, Python developer environment" -LABEL org.opencontainers.image.licenses="EUPL-1.2" -LABEL org.opencontainers.image.vendor="Host UK" -LABEL org.opencontainers.image.title="Core Developer" - -# Environment variables -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 -ENV TERM=xterm-256color -ENV EDITOR=nvim -ENV SHELL=/bin/zsh -ENV GOPATH=/root/go -ENV PATH="/root/go/bin:/root/.local/bin:/root/.composer/vendor/bin:/usr/local/bin:${PATH}" -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -# ============================================================ -# Core System & Build Tools -# ============================================================ -RUN apk add --no-cache \ - # Shells - bash zsh zsh-vcs \ - # Network tools - curl wget ca-certificates bind-tools iputils openssh-client \ - # Build essentials - make gcc g++ musl-dev linux-headers \ - # Core utilities - coreutils findutils grep sed gawk less tree shadow gettext \ - # Compression - zip unzip tar gzip xz \ - # Process & system - htop procps util-linux \ - # Crypto - gnupg openssl - -# ============================================================ -# Modern CLI Tools (Files & Search) -# ============================================================ -RUN apk add --no-cache \ - # File tools - bat eza fd ripgrep fzf tree \ - # Terminal - tmux starship \ - # Editors - vim nano neovim \ - # Directory navigation - zoxide - -# ============================================================ -# VCS & Git Tools -# ============================================================ -RUN apk add --no-cache \ - git git-lfs github-cli lazygit - # ============================================================ -# Node.js 22 LTS +# Stage: php — PHP 8.5 + Composer + tools # ============================================================ -RUN apk add --no-cache nodejs npm +FROM debian:trixie-slim AS php -# Claude Code CLI (pinned version) -RUN npm install -g @anthropic-ai/claude-code@2.1.29 +ENV DEBIAN_FRONTEND=noninteractive -# ============================================================ -# PHP Ecosystem -# ============================================================ -RUN apk add --no-cache \ - php84 php84-phar php84-mbstring php84-openssl php84-curl \ - php84-iconv php84-tokenizer php84-dom php84-xml php84-xmlwriter \ - php84-simplexml php84-ctype php84-fileinfo php84-json \ - php84-posix php84-pcntl php84-zip php84-sodium php84-session \ - php84-pdo php84-pdo_mysql php84-pdo_pgsql php84-pdo_sqlite - -RUN ln -sf /usr/bin/php84 /usr/bin/php +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl gnupg && \ + curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/sury-php.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/sury-php.gpg] https://packages.sury.org/php/ trixie main" > /etc/apt/sources.list.d/sury-php.list && \ + apt-get update && apt-get install -y --no-install-recommends \ + php8.5-cli php8.5-common php8.5-curl php8.5-mbstring php8.5-xml \ + php8.5-zip php8.5-sqlite3 php8.5-mysql php8.5-pgsql php8.5-gd \ + php8.5-intl php8.5-bcmath php8.5-soap php8.5-redis php8.5-pcov \ + php8.5-readline php8.5-bz2 php8.5-imagick && \ + rm -rf /var/lib/apt/lists/* -# Composer RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/bin --filename=composer -# Allow Pest plugin and install PHP tools globally RUN composer global config allow-plugins.pestphp/pest-plugin true && \ composer global require --no-interaction \ phpunit/phpunit:^11.0 \ @@ -105,85 +38,215 @@ RUN composer global config allow-plugins.pestphp/pest-plugin true && \ phpstan/phpstan:^2.0 \ laravel/pint:^1.0 -# FrankenPHP (static binary) -RUN curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-$(uname -m | sed 's/aarch64/arm64/' | sed 's/x86_64/x86_64/')" -o /usr/local/bin/frankenphp && \ - chmod +x /usr/local/bin/frankenphp +# FrankenPHP static binary — create placeholder if download fails +RUN ARCH=$(dpkg --print-architecture | sed 's/arm64/arm64/' | sed 's/amd64/x86_64/') && \ + curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-${ARCH}" \ + -o /usr/local/bin/frankenphp && chmod +x /usr/local/bin/frankenphp \ + || echo '#!/bin/sh\necho "FrankenPHP not available for this architecture"' > /usr/local/bin/frankenphp && chmod +x /usr/local/bin/frankenphp + +# Scratch export — just PHP runtime + composer + tools +FROM scratch AS php-export +COPY --from=php /usr/bin/php* /usr/bin/ +COPY --from=php /usr/lib/php/ /usr/lib/php/ +COPY --from=php /usr/share/php/ /usr/share/php/ +COPY --from=php /usr/bin/composer /usr/bin/composer +COPY --from=php /usr/local/bin/frankenphp /usr/local/bin/frankenphp +COPY --from=php /root/.composer /root/.composer # ============================================================ -# Go Ecosystem +# Stage: golang — Go 1.26.1 + tools # ============================================================ -RUN apk add --no-cache go +FROM debian:trixie-slim AS golang -# Go tools (pinned to versions compatible with Go 1.24) -RUN go install golang.org/x/tools/gopls@v0.17.1 || true && \ - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 && \ - go install github.com/go-task/task/v3/cmd/task@v3.40.1 +ENV DEBIAN_FRONTEND=noninteractive -# ============================================================ -# Python 3.13 (copied from official image) -# ============================================================ -# Runtime dependencies for Python -RUN apk add --no-cache libffi sqlite-libs xz-libs - -# Copy Python installation from build stage -COPY --from=python-stage /usr/local/bin/python3 /usr/local/bin/python3 -COPY --from=python-stage /usr/local/bin/python3.13 /usr/local/bin/python3.13 -COPY --from=python-stage /usr/local/bin/pip3 /usr/local/bin/pip3 -COPY --from=python-stage /usr/local/bin/pip3.13 /usr/local/bin/pip3.13 -COPY --from=python-stage /usr/local/lib/python3.13 /usr/local/lib/python3.13 -COPY --from=python-stage /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/ -COPY --from=python-stage /usr/local/lib/libpython3.so /usr/local/lib/ - -# Create symlinks and update library cache -RUN ln -sf /usr/local/bin/python3 /usr/local/bin/python && \ - ln -sf /usr/local/bin/pip3 /usr/local/bin/pip && \ - ldconfig /usr/local/lib || true +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git && \ + rm -rf /var/lib/apt/lists/* -# ============================================================ -# Database Clients -# ============================================================ -RUN apk add --no-cache \ - postgresql16-client \ - mariadb-client \ - redis \ - sqlite +ARG GO_VERSION=1.26.1 +RUN ARCH=$(dpkg --print-architecture) && \ + curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" | tar -C /usr/local -xz + +ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}" +ENV GOPATH=/root/go + +RUN go install golang.org/x/tools/gopls@latest 2>/dev/null || true && \ + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 2>/dev/null || true && \ + go install github.com/go-task/task/v3/cmd/task@latest 2>/dev/null || true + +# Scratch export — Go toolchain + installed tools +FROM scratch AS golang-export +COPY --from=golang /usr/local/go /usr/local/go +COPY --from=golang /root/go/bin /root/go/bin # ============================================================ -# Docker Tools +# Stage: node — Node.js 22 LTS + AI agent CLIs # ============================================================ -RUN apk add --no-cache docker-cli docker-cli-compose +FROM node:22-slim AS node + +RUN npm install -g @anthropic-ai/claude-code @openai/codex + +# Scratch export — Node runtime + global packages +FROM scratch AS node-export +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +COPY --from=node /usr/local/bin/npm /usr/local/bin/npm +COPY --from=node /usr/local/bin/npx /usr/local/bin/npx +COPY --from=node /usr/local/bin/claude /usr/local/bin/claude +COPY --from=node /usr/local/bin/codex /usr/local/bin/codex # ============================================================ -# Data Processing +# Stage: python — Python 3.13 # ============================================================ -RUN apk add --no-cache jq yq +FROM python:3.13-slim-trixie AS python + +# Scratch export — Python runtime +FROM scratch AS python-export +COPY --from=python /usr/local/bin/python3 /usr/local/bin/python3 +COPY --from=python /usr/local/bin/python3.13 /usr/local/bin/python3.13 +COPY --from=python /usr/local/bin/pip3 /usr/local/bin/pip3 +COPY --from=python /usr/local/lib/python3.13 /usr/local/lib/python3.13 +COPY --from=python /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/ +COPY --from=python /usr/local/lib/libpython3.so /usr/local/lib/ # ============================================================ -# Shell Configuration +# Stage: core-dev — Final assembled image # ============================================================ -# Oh-My-Zsh -RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended +FROM debian:trixie-slim AS core-dev + +LABEL maintainer="Snider " +LABEL org.opencontainers.image.source="https://github.com/host-uk/core-images" +LABEL org.opencontainers.image.description="PHP, Go, Node, Python developer environment" +LABEL org.opencontainers.image.licenses="EUPL-1.2" +LABEL org.opencontainers.image.vendor="Host UK" +LABEL org.opencontainers.image.title="Core Developer" -# Zsh plugins -RUN git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-/root/.oh-my-zsh/custom}/plugins/zsh-autosuggestions && \ +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV TERM=xterm-256color +ENV EDITOR=nvim +ENV SHELL=/bin/zsh +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# ---- System packages ---- +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Shells + bash zsh \ + # Network + curl wget ca-certificates dnsutils iputils-ping openssh-client socat \ + # Build essentials + make gcc g++ \ + # Core utilities + coreutils findutils grep sed gawk less tree gettext-base \ + # Compression + zip unzip tar gzip xz-utils \ + # Process & system + htop procps \ + # Crypto + gnupg openssl \ + # VCS + git git-lfs \ + # Editors + vim nano neovim \ + # Terminal + tmux \ + # Database clients + default-mysql-client postgresql-client redis-tools sqlite3 \ + # Docker + docker.io docker-compose \ + # Data + jq \ + # PHP shared lib deps (not pulled by COPY --from) + libargon2-1 libsodium23 libonig5 libzip5 libgd3 \ + libicu76 libxml2 libpq5 libsqlite3-0 libcurl4t64 \ + libmagickwand-7.q16-10 libxslt1.1 \ + && rm -rf /var/lib/apt/lists/* + +# ---- Modern CLI tools (from GitHub releases / cargo / pip) ---- +RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y && \ + curl -fsSL https://github.com/dandavison/bat/releases/latest/download/bat_0.25.0_$(dpkg --print-architecture).deb -o /tmp/bat.deb && dpkg -i /tmp/bat.deb && rm /tmp/bat.deb || true + +# gh CLI +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \ + apt-get update && apt-get install -y --no-install-recommends gh && \ + rm -rf /var/lib/apt/lists/* + +# ripgrep + fd from Debian repos +RUN apt-get update && apt-get install -y --no-install-recommends ripgrep fd-find fzf && \ + ln -sf /usr/bin/fdfind /usr/bin/fd && \ + rm -rf /var/lib/apt/lists/* + +# ---- Copy language runtimes from build stages ---- +COPY --from=php /usr/bin/php8.5 /usr/bin/php8.5 +COPY --from=php /usr/bin/php /usr/bin/php +COPY --from=php /usr/lib/php/ /usr/lib/php/ +COPY --from=php /etc/php/ /etc/php/ +COPY --from=php /usr/bin/composer /usr/bin/composer +COPY --from=php /root/.composer /root/.composer +COPY --from=php /usr/local/bin/frankenphp /usr/local/bin/frankenphp + +COPY --from=golang /usr/local/go /usr/local/go +COPY --from=golang /root/go/bin /root/go/bin + +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +COPY --from=node /usr/local/bin/npm /usr/local/bin/npm +COPY --from=node /usr/local/bin/npx /usr/local/bin/npx + +COPY --from=python /usr/local/bin/python3 /usr/local/bin/python3 +COPY --from=python /usr/local/bin/python3.13 /usr/local/bin/python3.13 +COPY --from=python /usr/local/bin/pip3 /usr/local/bin/pip3 +COPY --from=python /usr/local/lib/python3.13 /usr/local/lib/python3.13 +COPY --from=python /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/ +COPY --from=python /usr/local/lib/libpython3.so /usr/local/lib/ + +# Symlinks for CLI tools from node_modules +RUN ln -sf /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js /usr/local/bin/claude && \ + ln -sf /usr/local/lib/node_modules/@openai/codex/bin/codex.js /usr/local/bin/codex && \ + ln -sf /usr/local/bin/python3 /usr/local/bin/python && \ + ln -sf /usr/local/bin/pip3 /usr/local/bin/pip && \ + ldconfig + +# ---- Shell configuration ---- +RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended && \ + git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-/root/.oh-my-zsh/custom}/plugins/zsh-autosuggestions && \ git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-/root/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting -# ============================================================ -# Configuration Files -# ============================================================ COPY --chmod=644 config/zshrc /root/.zshrc COPY --chmod=644 config/starship.toml /root/.config/starship.toml COPY --chmod=644 config/tmux.conf /root/.tmux.conf COPY --chmod=644 config/aliases.sh /etc/profile.d/aliases.sh - -# ============================================================ -# Entrypoint -# ============================================================ COPY --chmod=755 scripts/entrypoint.sh /usr/local/bin/entrypoint.sh -# Create directories -RUN mkdir -p /root/.config /root/.claude /workspace - +# ---- Go + Node PATH for root (build verification) ---- +ENV GOPATH=/root/go +ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.composer/vendor/bin:/usr/local/bin:${PATH}" + +# ---- Non-root user (Claude Code refuses to run as root) ---- +ARG DEV_USER=dev +ARG DEV_UID=1000 +ARG DEV_GID=1000 + +RUN groupadd -g ${DEV_GID} ${DEV_USER} && \ + useradd -m -u ${DEV_UID} -g ${DEV_USER} -s /bin/zsh ${DEV_USER} && \ + mkdir -p /home/${DEV_USER}/.config /home/${DEV_USER}/.claude /workspace && \ + chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} /workspace && \ + cp -r /root/.oh-my-zsh /home/${DEV_USER}/.oh-my-zsh && \ + cp /root/.zshrc /home/${DEV_USER}/.zshrc && \ + cp -r /root/.config/starship.toml /home/${DEV_USER}/.config/ && \ + cp /root/.tmux.conf /home/${DEV_USER}/.tmux.conf && \ + cp -r /root/.composer /home/${DEV_USER}/.composer && \ + chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} + +ENV GOPATH=/home/${DEV_USER}/go +ENV PATH="/usr/local/go/bin:/home/${DEV_USER}/go/bin:/home/${DEV_USER}/.local/bin:/home/${DEV_USER}/.composer/vendor/bin:/usr/local/bin:${PATH}" +ENV HOME=/home/${DEV_USER} + +USER ${DEV_USER} WORKDIR /workspace ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/developer/scripts/entrypoint.sh b/developer/scripts/entrypoint.sh index efc1829..ca00e55 100644 --- a/developer/scripts/entrypoint.sh +++ b/developer/scripts/entrypoint.sh @@ -4,8 +4,8 @@ set -e # Run pre-start hooks if they exist -if [ -d "/root/.config/core-dev/hooks/pre-start" ]; then - for hook in /root/.config/core-dev/hooks/pre-start/*; do +if [ -d "$HOME/.config/core-dev/hooks/pre-start" ]; then + for hook in $HOME/.config/core-dev/hooks/pre-start/*; do [ -x "$hook" ] && "$hook" done fi @@ -31,14 +31,9 @@ if [ -d "$HOME/.ssh" ] && [ -z "$SSH_AUTH_SOCK" ]; then done fi -# Initialize mkcert CA if not already done -if [ ! -f "$HOME/.local/share/mkcert/rootCA.pem" ]; then - mkcert -install 2>/dev/null || true -fi - # Run post-start hooks if they exist -if [ -d "/root/.config/core-dev/hooks/post-start" ]; then - for hook in /root/.config/core-dev/hooks/post-start/*; do +if [ -d "$HOME/.config/core-dev/hooks/post-start" ]; then + for hook in $HOME/.config/core-dev/hooks/post-start/*; do [ -x "$hook" ] && "$hook" done fi From 14da510980baf8a324561271f793afce2ed0c7b7 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 31 Mar 2026 04:21:03 +0100 Subject: [PATCH 16/18] docker update --- developer/Dockerfile | 347 +++++++++++++++----------------- developer/scripts/entrypoint.sh | 20 +- 2 files changed, 169 insertions(+), 198 deletions(-) diff --git a/developer/Dockerfile b/developer/Dockerfile index 03360d9..54473d1 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -1,128 +1,209 @@ # ============================================================ # Core Developer — Multi-Language Development Environment # -# Debian Trixie base, multi-stage scratch exports. -# Each language is a named stage — COPY --from to cherry-pick. +# build-base: ALL system packages (apt-get happens once, here) +# build-php/go/node/python: language toolchains (parallel, FROM build-base) +# build-security: security scanners + SBOM tools (FROM build-base) +# core-dev: final assembly — COPY from stages + user setup only # # Build: docker build -t core-dev . # Run: docker run -it -v $(pwd):/workspace core-dev -# -# Slim: docker-slim build --target core-dev --http-probe=false # ============================================================ # ============================================================ -# Stage: php — PHP 8.5 + Composer + tools +# Stage: build-base — ALL system packages # ============================================================ -FROM debian:trixie-slim AS php +FROM debian:trixie-slim AS build-base ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl gnupg && \ - curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/sury-php.gpg && \ - echo "deb [signed-by=/usr/share/keyrings/sury-php.gpg] https://packages.sury.org/php/ trixie main" > /etc/apt/sources.list.d/sury-php.list && \ - apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update +RUN apt-get upgrade -y + +RUN apt-get install -y build-essential ca-certificates curl gnupg zsh wget dnsutils iputils-ping openssh-client socat \ + openssl tmux jq git git-lfs htop procps zip unzip xz-utils gawk less tree docker.io docker-compose lynis file \ + gettext-base ripgrep fd-find fzf shellcheck default-mysql-client mariadb-client postgresql-client redis-tools sqlite3 \ + bat lsb-release apt-transport-https gcc g++ binutils binutils-gold openssl libncurses5-dev libncursesw5-dev + + +# ---- Non-root user (Claude Code refuses to run as root) ---- +ARG DEV_USER=agent +ARG DEV_UID=1000 +ARG DEV_GID=1000 + +RUN groupadd -g ${DEV_GID} ${DEV_USER} +RUN useradd -m -u ${DEV_UID} -g ${DEV_USER} -s /bin/zsh ${DEV_USER} +RUN mkdir -p /home/${DEV_USER}/.config /home/${DEV_USER}/.claude /workspace +RUN chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} /workspace + +RUN chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} + +ENV HOME=/home/${DEV_USER} +ENV GOPATH=$HOME/go +ENV PATH="$PATH:$HOME/.linuxbrew/bin:/usr/local/go/bin:${GOPATH}/bin:${HOME}/.local/bin:${HOME}/.composer/vendor/bin:/usr/local/bin:${PATH}" +ENV HOMEBREW_PREFIX=$HOME/.linuxbrew +# Install Homebrew (Linuxbrew) +RUN git clone https://github.com/Homebrew/brew ${HOME}/.linuxbrew/Homebrew +RUN mkdir ~/.linuxbrew/bin && ln -s ../Homebrew/bin/brew ~/.linuxbrew/bin && eval "$(/usr/local/bin/brew shellenv)" + + + +RUN chown -R agent:agent /home/agent + +USER ${DEV_USER} + +RUN brew install hadolint trivy syft grype gitleaks trufflehog dive conftest opa git-secrets gh + +#RUN brew install openssl@3 + +USER root +# ============================================================ +# Stage: build-php — PHP 8.5 + Composer + tools (FROM build-base) +# ============================================================ +FROM build-base AS build-php + +RUN curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/sury-php.gpg +RUN echo "deb [signed-by=/usr/share/keyrings/sury-php.gpg] https://packages.sury.org/php/ trixie main" > /etc/apt/sources.list.d/sury-php.list +RUN apt-get update +RUN apt-get install -y \ php8.5-cli php8.5-common php8.5-curl php8.5-mbstring php8.5-xml \ php8.5-zip php8.5-sqlite3 php8.5-mysql php8.5-pgsql php8.5-gd \ php8.5-intl php8.5-bcmath php8.5-soap php8.5-redis php8.5-pcov \ - php8.5-readline php8.5-bz2 php8.5-imagick && \ - rm -rf /var/lib/apt/lists/* + php8.5-readline php8.5-bz2 php8.5-imagick RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/bin --filename=composer -RUN composer global config allow-plugins.pestphp/pest-plugin true && \ - composer global require --no-interaction \ +RUN composer global config allow-plugins.pestphp/pest-plugin true +RUN composer global require --no-interaction \ phpunit/phpunit:^11.0 \ pestphp/pest:^3.0 \ phpstan/phpstan:^2.0 \ - laravel/pint:^1.0 + laravel/pint:^1.0 \ + squizlabs/php_codesniffer:^3.0 \ + phpmd/phpmd:^2.0 \ + friendsofphp/php-cs-fixer:^3.0 +RUN composer global require --no-interaction vimeo/psalm +RUN composer global require --no-interaction rector/rector +RUN composer global require --no-interaction etsy/phan -# FrankenPHP static binary — create placeholder if download fails RUN ARCH=$(dpkg --print-architecture | sed 's/arm64/arm64/' | sed 's/amd64/x86_64/') && \ curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-${ARCH}" \ -o /usr/local/bin/frankenphp && chmod +x /usr/local/bin/frankenphp \ || echo '#!/bin/sh\necho "FrankenPHP not available for this architecture"' > /usr/local/bin/frankenphp && chmod +x /usr/local/bin/frankenphp -# Scratch export — just PHP runtime + composer + tools -FROM scratch AS php-export -COPY --from=php /usr/bin/php* /usr/bin/ -COPY --from=php /usr/lib/php/ /usr/lib/php/ -COPY --from=php /usr/share/php/ /usr/share/php/ -COPY --from=php /usr/bin/composer /usr/bin/composer -COPY --from=php /usr/local/bin/frankenphp /usr/local/bin/frankenphp -COPY --from=php /root/.composer /root/.composer - +RUN ARCH=$(dpkg --print-architecture) && \ + curl -fsSL "https://github.com/fabpot/local-php-security-checker/releases/latest/download/local-php-security-checker_linux_${ARCH}" \ + -o /usr/local/bin/local-php-security-checker && chmod +x /usr/local/bin/local-php-security-checker # ============================================================ -# Stage: golang — Go 1.26.1 + tools +# Stage: build-go — Go 1.26.1 + tools (FROM build-base) # ============================================================ -FROM debian:trixie-slim AS golang - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl git && \ - rm -rf /var/lib/apt/lists/* +FROM build-php AS build-go ARG GO_VERSION=1.26.1 RUN ARCH=$(dpkg --print-architecture) && \ curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" | tar -C /usr/local -xz -ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}" -ENV GOPATH=/root/go - -RUN go install golang.org/x/tools/gopls@latest 2>/dev/null || true && \ - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 2>/dev/null || true && \ - go install github.com/go-task/task/v3/cmd/task@latest 2>/dev/null || true +ENV PATH="/usr/local/go/bin:/${DEV_USER}/go/bin:${PATH}" +ENV GOPATH=/home/${DEV_USER}/go -# Scratch export — Go toolchain + installed tools -FROM scratch AS golang-export -COPY --from=golang /usr/local/go /usr/local/go -COPY --from=golang /root/go/bin /root/go/bin +USER ${DEV_USER} +# Dev tools +RUN go install golang.org/x/tools/gopls@latest +RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +RUN go install github.com/go-task/task/v3/cmd/task@latest +RUN go install github.com/securego/gosec/v2/cmd/gosec@latest +RUN go install golang.org/x/vuln/cmd/govulncheck@latest +RUN go install honnef.co/go/tools/cmd/staticcheck@latest +RUN go install github.com/mgechev/revive@latest +RUN go install github.com/gordonklaus/ineffassign@latest +RUN go install github.com/kisielk/errcheck@latest +RUN go install github.com/fatih/gomodifytags@latest +RUN go install github.com/jgautheron/goconst/cmd/goconst@latest +#RUN go install github.com/nishanths/exhaustive/cmd/exhaustive@latest +RUN go install github.com/tomarrell/wrapcheck/v2/cmd/wrapcheck@latest +RUN go install github.com/dkorunic/betteralign/cmd/betteralign@latest +RUN go install github.com/polyfloyd/go-errorlint@latest + +# Security + SBOM tools (need Go toolchain) +RUN go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest +RUN go install github.com/google/osv-scanner/cmd/osv-scanner@latest +RUN go install golang.stackrox.io/kube-linter/cmd/kube-linter@latest +RUN go install github.com/sigstore/cosign/v2/cmd/cosign@latest +RUN go install github.com/google/go-containerregistry/cmd/crane@latest +#RUN go install github.com/Checkmarx/kics/v2/cmd/kics@latest # ============================================================ -# Stage: node — Node.js 22 LTS + AI agent CLIs +# Stage: build-node — Node.js 22 LTS + AI agent CLIs (FROM build-base) # ============================================================ -FROM node:22-slim AS node +FROM build-go AS build-node + +USER root +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - +RUN apt-get install -y nodejs -RUN npm install -g @anthropic-ai/claude-code @openai/codex -# Scratch export — Node runtime + global packages -FROM scratch AS node-export -COPY --from=node /usr/local/bin/node /usr/local/bin/node -COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules -COPY --from=node /usr/local/bin/npm /usr/local/bin/npm -COPY --from=node /usr/local/bin/npx /usr/local/bin/npx -COPY --from=node /usr/local/bin/claude /usr/local/bin/claude -COPY --from=node /usr/local/bin/codex /usr/local/bin/codex +ENV HOME=/home/agent +ENV HOMEBREW_PREFIX=$HOME/.linuxbrew +ENV HOMEBREW_CELLAR=$HOME/.linuxbrew/Cellar + +RUN #brew install node +#RUN brew install --cask codex +#RUN brew install --cask claude-code +RUN npm install -g @anthropic-ai/claude-code +RUN npm install -g @biomejs/biome oxlint prettier eslint +RUN npm install -g markdownlint-cli markdownlint-cli2 jsonlint stylelint +RUN npm install -g typescript ts-prune +RUN npm install -g npm-audit-html license-checker +RUN npm install -g depcheck @cyclonedx/cdxgen snyk # ============================================================ -# Stage: python — Python 3.13 +# Stage: build-python — Python 3.13 + linters (FROM build-base) # ============================================================ -FROM python:3.13-slim-trixie AS python - -# Scratch export — Python runtime -FROM scratch AS python-export -COPY --from=python /usr/local/bin/python3 /usr/local/bin/python3 -COPY --from=python /usr/local/bin/python3.13 /usr/local/bin/python3.13 -COPY --from=python /usr/local/bin/pip3 /usr/local/bin/pip3 -COPY --from=python /usr/local/lib/python3.13 /usr/local/lib/python3.13 -COPY --from=python /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/ -COPY --from=python /usr/local/lib/libpython3.so /usr/local/lib/ +FROM build-node AS build-python + +RUN apt-get install -y python3 python3-pip python3-venv +ENV SETUPTOOLS_USE_DISTUTILS=stdlib +# Linters +RUN pip install --no-cache-dir --break-system-packages ruff mypy bandit pylint setuptools +RUN pip install --no-cache-dir --break-system-packages yamllint pyflakes pycodestyle pydocstyle +RUN pip install --no-cache-dir --break-system-packages safety vulture radon +RUN pip install --no-cache-dir --break-system-packages flake8 black isort autopep8 + +# Security + SBOM tools (need Python runtime) +RUN pip install --no-cache-dir --break-system-packages semgrep +RUN #pip install --no-cache-dir --break-system-packages scancode-toolkit +RUN pip install --no-cache-dir --break-system-packages detect-secrets +RUN #pip install --no-cache-dir --break-system-packages checkov +RUN pip install --no-cache-dir --break-system-packages pip-audit +RUN #pip install --no-cache-dir --break-system-packages tern +RUN pip install --no-cache-dir --break-system-packages in-toto # ============================================================ -# Stage: core-dev — Final assembled image +# Stage: core-dev — Final assembly (COPY only, no installs) # ============================================================ -FROM debian:trixie-slim AS core-dev +FROM build-python AS core-dev +ARG DEV_USER=agent -LABEL maintainer="Snider " -LABEL org.opencontainers.image.source="https://github.com/host-uk/core-images" -LABEL org.opencontainers.image.description="PHP, Go, Node, Python developer environment" +# ---- Config files ---- +COPY --chmod=644 --chown=${DEV_USER} config/aliases.sh /etc/profile.d/aliases.sh +COPY --chmod=755 --chown=${DEV_USER} scripts/entrypoint.sh /usr/local/bin/entrypoint.sh + +RUN apt-get clean +RUN npm cache clean --force + +USER ${DEV_USER} +RUN brew upgrade +RUN brew autoremove +RUN brew cleanup --prune=all + +LABEL maintainer="Snider " +LABEL org.opencontainers.image.source="https://dappco.re/docker/developer" +LABEL org.opencontainers.image.description="Core Developer Tooling" LABEL org.opencontainers.image.licenses="EUPL-1.2" -LABEL org.opencontainers.image.vendor="Host UK" +LABEL org.opencontainers.image.vendor="Lethean Community" LABEL org.opencontainers.image.title="Core Developer" -ENV DEBIAN_FRONTEND=noninteractive ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 ENV TERM=xterm-256color @@ -131,123 +212,15 @@ ENV SHELL=/bin/zsh ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 -# ---- System packages ---- -RUN apt-get update && apt-get install -y --no-install-recommends \ - # Shells - bash zsh \ - # Network - curl wget ca-certificates dnsutils iputils-ping openssh-client socat \ - # Build essentials - make gcc g++ \ - # Core utilities - coreutils findutils grep sed gawk less tree gettext-base \ - # Compression - zip unzip tar gzip xz-utils \ - # Process & system - htop procps \ - # Crypto - gnupg openssl \ - # VCS - git git-lfs \ - # Editors - vim nano neovim \ - # Terminal - tmux \ - # Database clients - default-mysql-client postgresql-client redis-tools sqlite3 \ - # Docker - docker.io docker-compose \ - # Data - jq \ - # PHP shared lib deps (not pulled by COPY --from) - libargon2-1 libsodium23 libonig5 libzip5 libgd3 \ - libicu76 libxml2 libpq5 libsqlite3-0 libcurl4t64 \ - libmagickwand-7.q16-10 libxslt1.1 \ - && rm -rf /var/lib/apt/lists/* - -# ---- Modern CLI tools (from GitHub releases / cargo / pip) ---- -RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y && \ - curl -fsSL https://github.com/dandavison/bat/releases/latest/download/bat_0.25.0_$(dpkg --print-architecture).deb -o /tmp/bat.deb && dpkg -i /tmp/bat.deb && rm /tmp/bat.deb || true - -# gh CLI -RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \ - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \ - apt-get update && apt-get install -y --no-install-recommends gh && \ - rm -rf /var/lib/apt/lists/* - -# ripgrep + fd from Debian repos -RUN apt-get update && apt-get install -y --no-install-recommends ripgrep fd-find fzf && \ - ln -sf /usr/bin/fdfind /usr/bin/fd && \ - rm -rf /var/lib/apt/lists/* - -# ---- Copy language runtimes from build stages ---- -COPY --from=php /usr/bin/php8.5 /usr/bin/php8.5 -COPY --from=php /usr/bin/php /usr/bin/php -COPY --from=php /usr/lib/php/ /usr/lib/php/ -COPY --from=php /etc/php/ /etc/php/ -COPY --from=php /usr/bin/composer /usr/bin/composer -COPY --from=php /root/.composer /root/.composer -COPY --from=php /usr/local/bin/frankenphp /usr/local/bin/frankenphp - -COPY --from=golang /usr/local/go /usr/local/go -COPY --from=golang /root/go/bin /root/go/bin - -COPY --from=node /usr/local/bin/node /usr/local/bin/node -COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules -COPY --from=node /usr/local/bin/npm /usr/local/bin/npm -COPY --from=node /usr/local/bin/npx /usr/local/bin/npx - -COPY --from=python /usr/local/bin/python3 /usr/local/bin/python3 -COPY --from=python /usr/local/bin/python3.13 /usr/local/bin/python3.13 -COPY --from=python /usr/local/bin/pip3 /usr/local/bin/pip3 -COPY --from=python /usr/local/lib/python3.13 /usr/local/lib/python3.13 -COPY --from=python /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/ -COPY --from=python /usr/local/lib/libpython3.so /usr/local/lib/ - -# Symlinks for CLI tools from node_modules -RUN ln -sf /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js /usr/local/bin/claude && \ - ln -sf /usr/local/lib/node_modules/@openai/codex/bin/codex.js /usr/local/bin/codex && \ - ln -sf /usr/local/bin/python3 /usr/local/bin/python && \ - ln -sf /usr/local/bin/pip3 /usr/local/bin/pip && \ - ldconfig - -# ---- Shell configuration ---- -RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended && \ - git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-/root/.oh-my-zsh/custom}/plugins/zsh-autosuggestions && \ - git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-/root/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting - -COPY --chmod=644 config/zshrc /root/.zshrc -COPY --chmod=644 config/starship.toml /root/.config/starship.toml -COPY --chmod=644 config/tmux.conf /root/.tmux.conf -COPY --chmod=644 config/aliases.sh /etc/profile.d/aliases.sh -COPY --chmod=755 scripts/entrypoint.sh /usr/local/bin/entrypoint.sh - -# ---- Go + Node PATH for root (build verification) ---- -ENV GOPATH=/root/go -ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.composer/vendor/bin:/usr/local/bin:${PATH}" +# ---- COPY from parallel build stages ---- -# ---- Non-root user (Claude Code refuses to run as root) ---- -ARG DEV_USER=dev -ARG DEV_UID=1000 -ARG DEV_GID=1000 -RUN groupadd -g ${DEV_GID} ${DEV_USER} && \ - useradd -m -u ${DEV_UID} -g ${DEV_USER} -s /bin/zsh ${DEV_USER} && \ - mkdir -p /home/${DEV_USER}/.config /home/${DEV_USER}/.claude /workspace && \ - chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} /workspace && \ - cp -r /root/.oh-my-zsh /home/${DEV_USER}/.oh-my-zsh && \ - cp /root/.zshrc /home/${DEV_USER}/.zshrc && \ - cp -r /root/.config/starship.toml /home/${DEV_USER}/.config/ && \ - cp /root/.tmux.conf /home/${DEV_USER}/.tmux.conf && \ - cp -r /root/.composer /home/${DEV_USER}/.composer && \ - chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} -ENV GOPATH=/home/${DEV_USER}/go -ENV PATH="/usr/local/go/bin:/home/${DEV_USER}/go/bin:/home/${DEV_USER}/.local/bin:/home/${DEV_USER}/.composer/vendor/bin:/usr/local/bin:${PATH}" -ENV HOME=/home/${DEV_USER} +# ---- PATH ---- +ENV GOPATH=/${DEV_USER}/go +ENV PATH="/usr/local/go/bin:/${DEV_USER}/go/bin:/${DEV_USER}/.composer/vendor/bin:/usr/local/bin:${PATH}" -USER ${DEV_USER} -WORKDIR /workspace +WORKDIR /home/${DEV_USER}/workspace ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] -CMD ["/bin/zsh"] +CMD ["/bin/bash"] diff --git a/developer/scripts/entrypoint.sh b/developer/scripts/entrypoint.sh index ca00e55..79834e0 100644 --- a/developer/scripts/entrypoint.sh +++ b/developer/scripts/entrypoint.sh @@ -3,12 +3,6 @@ set -e -# Run pre-start hooks if they exist -if [ -d "$HOME/.config/core-dev/hooks/pre-start" ]; then - for hook in $HOME/.config/core-dev/hooks/pre-start/*; do - [ -x "$hook" ] && "$hook" - done -fi # Setup git config if not already set if [ -z "$(git config --global user.name 2>/dev/null)" ]; then @@ -31,11 +25,15 @@ if [ -d "$HOME/.ssh" ] && [ -z "$SSH_AUTH_SOCK" ]; then done fi -# Run post-start hooks if they exist -if [ -d "$HOME/.config/core-dev/hooks/post-start" ]; then - for hook in $HOME/.config/core-dev/hooks/post-start/*; do - [ -x "$hook" ] && "$hook" - done + +# Symlink Claude config if mounted inside .claude dir +[ -f "$HOME/.claude/.claude.json" ] && [ ! -f "$HOME/.claude.json" ] && \ + ln -sf "$HOME/.claude/.claude.json" "$HOME/.claude.json" + +# Workspace must have repo/ — fail fast if mount is wrong +if [ ! -d "/workspace/repo" ]; then + echo "ERROR: /workspace/repo not found — mount the full workspace, not just the repo" >&2 + exit 1 fi # Execute command From a55e2591631d86b075945fd9b8d5bc46c994a993 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Apr 2026 23:39:07 +0100 Subject: [PATCH 17/18] missed files --- Taskfile.yaml | 41 +++++ scanner-go/Dockerfile | 250 ++++++++++++++++++++++++++ scanner-go/eslint.security.config.mjs | 62 +++++++ 3 files changed, 353 insertions(+) create mode 100644 scanner-go/Dockerfile create mode 100644 scanner-go/eslint.security.config.mjs diff --git a/Taskfile.yaml b/Taskfile.yaml index 6d06b92..4fd9a98 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -19,12 +19,14 @@ tasks: deps: - build:developer - build:server-php + - build:scanner-go build:docker: desc: Build all Docker images deps: - build:developer:docker - build:server-php:docker + - build:scanner-go:docker build:tim: desc: Build all TIM bundles @@ -96,6 +98,43 @@ tasks: - docker push {{.REGISTRY}}/server-php:{{.VERSION}} - docker push {{.REGISTRY}}/server-php:latest + # ============================================================ + # Scanner Go Image + # ============================================================ + build:scanner-go: + desc: Build scanner-go image (Docker + TIM) + deps: + - build:scanner-go:docker + - build:scanner-go:tim + + build:scanner-go:docker: + desc: Build scanner-go Docker image + dir: scanner-go + cmds: + - | + docker build \ + --tag {{.REGISTRY}}/core-scanner-go:{{.VERSION}} \ + --tag {{.REGISTRY}}/core-scanner-go:latest \ + --build-arg VERSION={{.VERSION}} \ + . + + build:scanner-go:tim: + desc: Build scanner-go TIM bundle + dir: scanner-go + cmds: + - echo "TODO: core build --type tim -o ../dist/core-scanner-go-{{OS}}-{{ARCH}}.tim" + + push:scanner-go: + desc: Push scanner-go image to registry + cmds: + - docker push {{.REGISTRY}}/core-scanner-go:{{.VERSION}} + - docker push {{.REGISTRY}}/core-scanner-go:latest + + scan: + desc: "Run full scan against /src (mount your repo: -v \$(pwd):/src)" + cmds: + - docker run --rm -v {{.CLI_ARGS}}:/src {{.REGISTRY}}/core-scanner-go:latest sh -c "cd /src && govulncheck ./... && gosec ./... && golangci-lint run ./... && trivy fs ." + # ============================================================ # Release # ============================================================ @@ -105,6 +144,7 @@ tasks: - task: build - task: push:developer - task: push:server-php + - task: push:scanner-go # ============================================================ # Utilities @@ -115,6 +155,7 @@ tasks: - rm -rf dist/ - docker rmi {{.REGISTRY}}/core-dev:{{.VERSION}} || true - docker rmi {{.REGISTRY}}/server-php:{{.VERSION}} || true + - docker rmi {{.REGISTRY}}/core-scanner-go:{{.VERSION}} || true dist: desc: Create dist directory diff --git a/scanner-go/Dockerfile b/scanner-go/Dockerfile new file mode 100644 index 0000000..11cf112 --- /dev/null +++ b/scanner-go/Dockerfile @@ -0,0 +1,250 @@ +# ============================================================ +# Core Scanner — GoLang + PHP + Python + Node.js Security Scanning +# +# Go tools: +# govulncheck — official Go vulnerability checker (golang.org/x/vuln) +# gosec — Go AST security checker (securego/gosec) +# golangci-lint — meta-linter (govet, errcheck, staticcheck, gosec, ...) +# trivy — filesystem + container vulnerability scanner (Aqua) +# grype — vulnerability scanner for SBOMs and filesystems (Anchore) +# syft — SBOM generator (Anchore) +# semgrep — multi-language SAST (returntocorp) +# nancy — Sonatype OSS Index dep scanner +# +# PHP tools: +# phpcs — PHP_CodeSniffer — coding standard checker +# psalm — static analysis + type checking (vimeo/psalm) +# enlightn — Laravel security & performance auditor +# +# Python tools (installed via uv): +# bandit — Python AST security linter (PyCQA) +# pyre-check — Facebook type checker + Pysa taint analysis +# safety — Safety CLI — dep vulnerability checker (PyUp) +# +# Node.js tools (global npm): +# npm-audit — built-in npm dep vulnerability audit +# nodejsscan — static security scanner for Node.js (ajinabraham) +# eslint — pluggable linter +# eslint-plugin-security — Node.js security rules +# eslint-plugin-no-secrets — detect hardcoded secrets +# eslint-plugin-node — Node.js best-practice rules +# @microsoft/eslint-plugin-sdl — Microsoft SDL security rules +# +# Build: docker build -t core-scanner . +# Go: docker run --rm -v $(pwd):/src core-scanner govulncheck ./... +# PHP: docker run --rm -v $(pwd):/src core-scanner phpcs --standard=PSR12 . +# Py: docker run --rm -v $(pwd):/src core-scanner bandit -r . +# Node: docker run --rm -v $(pwd):/src core-scanner npm audit +# docker run --rm -v $(pwd):/src core-scanner eslint --rulesdir /eslint-rules . +# ============================================================ + +ARG ALPINE_VERSION=3.22 +ARG NODE_VERSION=22 +ARG GO_VERSION=1.24.2 +ARG GOLANGCI_VERSION=v1.64.8 +ARG GOSEC_VERSION=v2.22.3 +ARG TRIVY_VERSION=0.61.0 +ARG GRYPE_VERSION=0.90.0 +ARG SYFT_VERSION=1.23.1 +ARG NANCY_VERSION=v1.0.46 +ARG PHP_VERSION=83 +ARG PHPCS_VERSION=3.11.3 +ARG PSALM_VERSION=6.x-dev + +FROM golang:${GO_VERSION}-alpine AS go-tools + +RUN apk add --no-cache git ca-certificates + +ARG GOLANGCI_VERSION +ARG GOSEC_VERSION +ARG NANCY_VERSION + +# govulncheck — official vuln checker +RUN go install golang.org/x/vuln/cmd/govulncheck@latest + +# gosec — Go AST security checker +RUN go install github.com/securego/gosec/v2/cmd/gosec@${GOSEC_VERSION} + +# golangci-lint — via official install script (avoids CGO issues) +RUN wget -O- https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | sh -s -- -b /usr/local/bin ${GOLANGCI_VERSION} + +# nancy — Sonatype OSS Index +RUN go install github.com/sonatype-nexus-community/nancy@${NANCY_VERSION} + +# ============================================================ +# Final image +# ============================================================ +FROM alpine:${ALPINE_VERSION} + +ARG TRIVY_VERSION +ARG GRYPE_VERSION +ARG SYFT_VERSION +ARG GO_VERSION +ARG PHP_VERSION +ARG PHPCS_VERSION +ARG PSALM_VERSION +ARG NODE_VERSION + +LABEL maintainer="Snider " +LABEL org.opencontainers.image.source="https://github.com/host-uk/core-images" +LABEL org.opencontainers.image.description="GoLang + PHP security and code scanning toolchain" +LABEL org.opencontainers.image.licenses="EUPL-1.2" +LABEL org.opencontainers.image.vendor="Host UK" +LABEL org.opencontainers.image.title="Core Scanner" + +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV GOPATH=/go +ENV UV_HOME="/root/.local" +ENV PATH="${GOPATH}/bin:/usr/local/go/bin:/usr/local/bin:${UV_HOME}/bin:${PATH}" +ENV GOFLAGS="-mod=readonly" +ENV UV_SYSTEM_PYTHON=1 + +# Runtime deps — Python, PHP, Node.js +RUN apk add --no-cache \ + ca-certificates \ + git \ + curl \ + wget \ + python3 \ + python3-dev \ + py3-pip \ + py3-setuptools \ + bash \ + jq \ + nodejs \ + npm \ + php${PHP_VERSION} \ + php${PHP_VERSION}-cli \ + php${PHP_VERSION}-phar \ + php${PHP_VERSION}-mbstring \ + php${PHP_VERSION}-tokenizer \ + php${PHP_VERSION}-xmlwriter \ + php${PHP_VERSION}-simplexml \ + php${PHP_VERSION}-dom \ + php${PHP_VERSION}-json \ + php${PHP_VERSION}-openssl \ + php${PHP_VERSION}-curl \ + php${PHP_VERSION}-ctype \ + php${PHP_VERSION}-pcntl \ + php${PHP_VERSION}-posix \ + php${PHP_VERSION}-fileinfo \ + && ln -sf /usr/bin/php${PHP_VERSION} /usr/local/bin/php + +# composer — PHP dependency manager (needed for psalm + enlightn) +RUN wget -qO /tmp/composer-setup.php https://getcomposer.org/installer \ + && php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer \ + && rm /tmp/composer-setup.php \ + && composer --version + +# Copy Go toolchain and built tools from builder +COPY --from=go-tools /usr/local/go /usr/local/go +COPY --from=go-tools /go/bin/govulncheck /usr/local/bin/govulncheck +COPY --from=go-tools /go/bin/gosec /usr/local/bin/gosec +COPY --from=go-tools /go/bin/nancy /usr/local/bin/nancy +COPY --from=go-tools /usr/local/bin/golangci-lint /usr/local/bin/golangci-lint + +# trivy — Aqua filesystem + container scanner +RUN wget -qO /tmp/trivy.tar.gz \ + "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \ + && tar -xzf /tmp/trivy.tar.gz -C /usr/local/bin trivy \ + && rm /tmp/trivy.tar.gz + +# grype — Anchore vulnerability scanner +RUN wget -qO /tmp/grype.tar.gz \ + "https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz" \ + && tar -xzf /tmp/grype.tar.gz -C /usr/local/bin grype \ + && rm /tmp/grype.tar.gz + +# syft — SBOM generator (pairs with grype) +RUN wget -qO /tmp/syft.tar.gz \ + "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz" \ + && tar -xzf /tmp/syft.tar.gz -C /usr/local/bin syft \ + && rm /tmp/syft.tar.gz + +# uv — fast Python package manager (replaces pip for all Python tools) +RUN curl -LsSf https://astral.sh/uv/install.sh | sh + +# Python tools via uv +RUN uv tool install semgrep \ + && uv tool install bandit \ + && uv tool install safety \ + && uv tool install pyre-check + +# Expose uv tool bins on PATH +RUN ln -sf "${UV_HOME}/bin/semgrep" /usr/local/bin/semgrep \ + && ln -sf "${UV_HOME}/bin/bandit" /usr/local/bin/bandit \ + && ln -sf "${UV_HOME}/bin/safety" /usr/local/bin/safety \ + && ln -sf "${UV_HOME}/bin/pyre" /usr/local/bin/pyre \ + && ln -sf "${UV_HOME}/bin/pysa" /usr/local/bin/pysa 2>/dev/null || true + +# phpcs — PHP_CodeSniffer +RUN wget -qO /usr/local/bin/phpcs \ + "https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/${PHPCS_VERSION}/phpcs.phar" \ + && chmod +x /usr/local/bin/phpcs \ + && wget -qO /usr/local/bin/phpcbf \ + "https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/${PHPCS_VERSION}/phpcbf.phar" \ + && chmod +x /usr/local/bin/phpcbf + +# psalm — static analysis + type checking +RUN composer global require --no-progress --no-interaction vimeo/psalm \ + && ln -sf /root/.composer/vendor/bin/psalm /usr/local/bin/psalm \ + && ln -sf /root/.composer/vendor/bin/psalm-language-server /usr/local/bin/psalm-language-server + +# enlightn — Laravel security + performance auditor +# Installed as a project-level dev dependency via composer require in the target project. +# The enlightn binary is provided here for standalone invocation: +RUN composer global require --no-progress --no-interaction enlightn/enlightn \ + && ln -sf /root/.composer/vendor/bin/enlightn /usr/local/bin/enlightn + +# Node.js security tools — global npm installs +# npm audit is built into npm; remaining tools installed globally +RUN npm install -g --no-fund --no-audit \ + nodejsscan \ + eslint \ + eslint-plugin-security \ + eslint-plugin-no-secrets \ + eslint-plugin-n \ + @microsoft/eslint-plugin-sdl + +# ESLint security config — ready-to-use ruleset for scanning arbitrary projects +COPY eslint.security.config.mjs /etc/scanner/eslint.security.config.mjs + +# Warm trivy DB cache at build time (optional — speeds up first scan) +# Uncomment to bake the DB in (adds ~200MB to image): +# RUN trivy image --download-db-only + +WORKDIR /src + +# Default: show tool versions to confirm a working image +CMD ["sh", "-c", "\ + echo '=== Core Scanner ===' && \ + echo '--- Go ---' && \ + echo \"go: $(go version)\" && \ + echo \"govulncheck: $(govulncheck -version 2>&1 | head -1)\" && \ + echo \"gosec: $(gosec --version 2>&1 | head -1)\" && \ + echo \"golangci-lint: $(golangci-lint version 2>&1 | head -1)\" && \ + echo \"trivy: $(trivy --version 2>&1 | head -1)\" && \ + echo \"grype: $(grype version 2>&1 | head -1)\" && \ + echo \"syft: $(syft version 2>&1 | head -1)\" && \ + echo \"nancy: $(nancy --version 2>&1 | head -1)\" && \ + echo '--- PHP ---' && \ + echo \"php: $(php --version 2>&1 | head -1)\" && \ + echo \"composer: $(composer --version 2>&1 | head -1)\" && \ + echo \"phpcs: $(phpcs --version 2>&1 | head -1)\" && \ + echo \"psalm: $(psalm --version 2>&1 | head -1)\" && \ + echo \"enlightn: $(enlightn --version 2>&1 | head -1)\" && \ + echo '--- Python ---' && \ + echo \"python: $(python3 --version 2>&1)\" && \ + echo \"uv: $(uv --version 2>&1)\" && \ + echo \"bandit: $(bandit --version 2>&1 | head -1)\" && \ + echo \"pyre: $(pyre --version 2>&1 | head -1)\" && \ + echo \"safety: $(safety --version 2>&1 | head -1)\" && \ + echo \"semgrep: $(semgrep --version 2>&1 | head -1)\" && \ + echo '--- Node.js ---' && \ + echo \"node: $(node --version 2>&1)\" && \ + echo \"npm: $(npm --version 2>&1)\" && \ + echo \"nodejsscan: $(nodejsscan --version 2>&1 | head -1)\" && \ + echo \"eslint: $(eslint --version 2>&1 | head -1)\" \ +"] diff --git a/scanner-go/eslint.security.config.mjs b/scanner-go/eslint.security.config.mjs new file mode 100644 index 0000000..4a852c5 --- /dev/null +++ b/scanner-go/eslint.security.config.mjs @@ -0,0 +1,62 @@ +// /etc/scanner/eslint.security.config.mjs +// Bundled ESLint security ruleset for core-scanner. +// +// Usage (scan an arbitrary project without its own eslint config): +// eslint --config /etc/scanner/eslint.security.config.mjs --no-eslintrc . +// +// Covers: +// eslint-plugin-security — Node.js security anti-patterns +// eslint-plugin-no-secrets — hardcoded credentials / tokens +// eslint-plugin-n — Node.js best-practice rules +// @microsoft/eslint-plugin-sdl — Microsoft SDL security rules + +import security from "eslint-plugin-security"; +import noSecrets from "eslint-plugin-no-secrets"; +import nodePlugin from "eslint-plugin-n"; +import sdl from "@microsoft/eslint-plugin-sdl"; + +export default [ + { + plugins: { + security, + "no-secrets": noSecrets, + n: nodePlugin, + sdl, + }, + rules: { + // eslint-plugin-security — all rules at warn level; escalate to error in CI + "security/detect-buffer-noassert": "warn", + "security/detect-child-process": "warn", + "security/detect-disable-mustache-escape": "error", + "security/detect-eval-with-expression": "error", + "security/detect-new-buffer": "warn", + "security/detect-no-csrf-before-method-override": "error", + "security/detect-non-literal-fs-filename": "warn", + "security/detect-non-literal-regexp": "warn", + "security/detect-non-literal-require": "warn", + "security/detect-object-injection": "warn", + "security/detect-possible-timing-attacks": "warn", + "security/detect-pseudoRandomBytes": "error", + "security/detect-unsafe-regex": "error", + + // eslint-plugin-no-secrets — catch hardcoded creds/tokens + "no-secrets/no-secrets": ["error", { tolerance: 4.2 }], + + // eslint-plugin-n — Node.js hygiene + "n/no-deprecated-api": "error", + "n/no-extraneous-require": "warn", + "n/no-missing-require": "warn", + "n/no-process-exit": "warn", + "n/no-unpublished-require": "warn", + + // @microsoft/eslint-plugin-sdl — SDL security rules + "sdl/no-inner-html": "error", + "sdl/no-unsafe-alloc": "error", + "sdl/no-postmessage-star-origin": "error", + "sdl/no-html-method": "error", + "sdl/no-cookies": "warn", + "sdl/no-document-domain": "error", + "sdl/no-document-write": "error", + }, + }, +]; From d5dc38059ad20e33eb21e0eede36a7e06bc6b710 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 24 Apr 2026 08:25:52 +0100 Subject: [PATCH 18/18] wip: pre-sweep snapshot 2026-04-24 --- developer/.claude/.claude.json | 16 ++++++++ developer/Dockerfile | 3 +- developer/scripts/entrypoint.sh | 24 ++++++++--- developer/spec | 1 + sbx-codex/Dockerfile | 72 +++++++++++++++++++++++++++++++++ sbx-codex/README.md | 46 +++++++++++++++++++++ 6 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 developer/.claude/.claude.json create mode 160000 developer/spec create mode 100644 sbx-codex/Dockerfile create mode 100644 sbx-codex/README.md diff --git a/developer/.claude/.claude.json b/developer/.claude/.claude.json new file mode 100644 index 0000000..d7551ca --- /dev/null +++ b/developer/.claude/.claude.json @@ -0,0 +1,16 @@ +{ + "oauthAccount": { + "accountUuid": "d47abd23-1dc4-4ccc-b22a-1d91ddcf286b", + "emailAddress": "snider@host.uk.com", + "organizationUuid": "886228c1-02f3-4d8f-8d05-fb5d0dbba39c", + "hasExtraUsageEnabled": false, + "billingType": "stripe_subscription", + "accountCreatedAt": "2025-12-24T12:49:08.592329Z", + "subscriptionCreatedAt": "2025-12-24T12:52:04.017154Z", + "displayName": "Snider", + "organizationRole": "admin", + "workspaceRole": null, + "organizationName": "snider@host.uk.com's Organization" + }, + "hasCompletedOnboarding": true +} \ No newline at end of file diff --git a/developer/Dockerfile b/developer/Dockerfile index 54473d1..67d5684 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -142,7 +142,6 @@ USER root RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - RUN apt-get install -y nodejs - ENV HOME=/home/agent ENV HOMEBREW_PREFIX=$HOME/.linuxbrew ENV HOMEBREW_CELLAR=$HOME/.linuxbrew/Cellar @@ -150,7 +149,7 @@ ENV HOMEBREW_CELLAR=$HOME/.linuxbrew/Cellar RUN #brew install node #RUN brew install --cask codex #RUN brew install --cask claude-code -RUN npm install -g @anthropic-ai/claude-code +RUN npm install -g @anthropic-ai/claude-code @openai/codex RUN npm install -g @biomejs/biome oxlint prettier eslint RUN npm install -g markdownlint-cli markdownlint-cli2 jsonlint stylelint RUN npm install -g typescript ts-prune diff --git a/developer/scripts/entrypoint.sh b/developer/scripts/entrypoint.sh index 79834e0..5018d04 100644 --- a/developer/scripts/entrypoint.sh +++ b/developer/scripts/entrypoint.sh @@ -4,6 +4,13 @@ set -e +# Run pre-start hooks if they exist +if [ -d "$HOME/.config/core-dev/hooks/pre-start" ]; then + for hook in $HOME/.config/core-dev/hooks/pre-start/*; do + [ -x "$hook" ] && "$hook" + done +fi + # Setup git config if not already set if [ -z "$(git config --global user.name 2>/dev/null)" ]; then if [ -n "$GIT_USER_NAME" ]; then @@ -25,16 +32,23 @@ if [ -d "$HOME/.ssh" ] && [ -z "$SSH_AUTH_SOCK" ]; then done fi +# Run post-start hooks if they exist +if [ -d "$HOME/.config/core-dev/hooks/post-start" ]; then + for hook in $HOME/.config/core-dev/hooks/post-start/*; do + [ -x "$hook" ] && "$hook" + done +fi # Symlink Claude config if mounted inside .claude dir [ -f "$HOME/.claude/.claude.json" ] && [ ! -f "$HOME/.claude.json" ] && \ ln -sf "$HOME/.claude/.claude.json" "$HOME/.claude.json" -# Workspace must have repo/ — fail fast if mount is wrong -if [ ! -d "/workspace/repo" ]; then - echo "ERROR: /workspace/repo not found — mount the full workspace, not just the repo" >&2 - exit 1 -fi + +# Clone spec docs from Forge if FORGE_TOKEN is set and repo/docs exists +#SPECS_DIR="$HOME/specs" +#mkdir -p "$SPECS_DIR" +#git clone --depth 1 --single-branch -b main "https://virgil:375068d101922dd1cf269e8b8cb77a0f99d1b486@forge.lthn.ai/core/plans.git" "$SPECS_DIR" +#cd "$SPECS_DIR" && git remote remove origin && cd - # Execute command exec "$@" diff --git a/developer/spec b/developer/spec new file mode 160000 index 0000000..5f9591f --- /dev/null +++ b/developer/spec @@ -0,0 +1 @@ +Subproject commit 5f9591fa9dffd7f2a75b822be531e7a05ff27030 diff --git a/sbx-codex/Dockerfile b/sbx-codex/Dockerfile new file mode 100644 index 0000000..82f282f --- /dev/null +++ b/sbx-codex/Dockerfile @@ -0,0 +1,72 @@ +# syntax=docker/dockerfile:1.6 +# ============================================================ +# Lethean sbx codex — core-dev base + sbx runtime contract +# +# core-dev gives us: PHP 8.3, Go 1.26, Node 24, Python 3.13, +# composer, pest, pint, security scanners, ~20GB of tooling. +# +# sandbox-templates:codex gives us: the sbx contract labels, +# /etc/sandbox-persistent.sh, and the codex CLI npm install. +# +# This template stacks them so `sbx run -t lthn/sbx-codex:dev codex ` +# drops codex into a fully-loaded core-dev environment. +# +# Build: +# cd ~/Code/core/images/sbx-codex +# docker build -t lthn/sbx-codex:dev . +# +# Use: +# sbx run -t lthn/sbx-codex:dev codex ~/Code/core/agent +# ============================================================ + +ARG SBX_SOURCE=docker/sandbox-templates:codex +ARG CORE_DEV=core-dev:latest + +FROM ${SBX_SOURCE} AS sbx-source + +FROM ${CORE_DEV} + +USER root + +# ── sbx contract ──────────────────────────────────────────── +# The persistent startup script sbx's runtime sources on every +# interactive shell. Carries proxy/no_proxy/PATH additions. +COPY --from=sbx-source /etc/sandbox-persistent.sh /etc/sandbox-persistent.sh +COPY --from=sbx-source /etc/profile.d/sandbox-persistent.sh /etc/profile.d/sandbox-persistent.sh + +# Codex CLI + its npm deps, bit-for-bit from upstream. +# /usr/local/share/npm-global is already on core-dev's PATH. +COPY --from=sbx-source /usr/local/share/npm-global /usr/local/share/npm-global + +# Labels — sbx recognises this as a template and the `-t` flag picks it up. +LABEL com.docker.sandboxes=templates \ + com.docker.sandboxes.flavor=codex-core-dev \ + com.docker.sandboxes.base=core-dev:latest \ + org.opencontainers.image.title="Lethean sbx codex on core-dev" \ + org.opencontainers.image.vendor="Lethean Community" \ + org.opencontainers.image.licenses=EUPL-1.2 \ + org.opencontainers.image.source=https://forge.lthn.sh/core/images + +# Env expected by the sbx runtime + codex. +ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global \ + BASH_ENV=/etc/sandbox-persistent.sh \ + NO_PROXY=localhost,127.0.0.1,::1,172.17.0.0/16 \ + no_proxy=localhost,127.0.0.1,::1,172.17.0.0/16 + +# sbx mounts the workspace here by default. +WORKDIR /home/agent/workspace +RUN chown agent:agent /home/agent/workspace + +USER agent + +# PATH needs npm-global's bin first so `codex` resolves to the sbx build. +ENV PATH=/home/agent/.local/bin:/usr/local/share/npm-global/bin:${PATH} + +# Pre-seed git identity so commits made by codex inside the sandbox are +# attributed correctly without every run re-configuring. Overridable via env. +RUN git config --global user.name "Codex (sbx)" \ + && git config --global user.email "codex@lthn.ai" \ + && git config --global init.defaultBranch dev \ + && git config --global pull.rebase true + +CMD ["codex"] diff --git a/sbx-codex/README.md b/sbx-codex/README.md new file mode 100644 index 0000000..b1fa518 --- /dev/null +++ b/sbx-codex/README.md @@ -0,0 +1,46 @@ +# sbx-codex — Lethean codex sandbox template + +Stacks the upstream `docker/sandbox-templates:codex` runtime contract on top of +`core-dev` so `sbx run -t lthn/sbx-codex:dev codex ` drops codex into a +fully-loaded PHP/Go/Node/Python dev environment. + +## Build + +```bash +cd ~/Code/core/images/sbx-codex +docker build -t lthn/sbx-codex:dev . +``` + +Assumes `core-dev:latest` is already built locally. (`cd ../developer && docker build -t core-dev:latest .` if not.) + +## Use + +```bash +sbx run -t lthn/sbx-codex:dev codex ~/Code/core/agent +``` + +`codex` inside the sandbox sees: +- `php --version` → 8.3+ +- `composer --version` → live +- `go version` → 1.26.x +- `node --version` → 24+ +- `git config user.email` → `codex@lthn.ai` (overridable) +- Workspace bind-mounted at `/home/agent/workspace` + +## Auth + +- **OpenAI Codex OAuth**: already stored globally via `sbx secret set -g openai --oauth` on the host. sbx's proxy passes the token into the sandbox automatically; no config inside the container needed. +- **Forge/Mantis writes**: sbx doesn't support arbitrary service secrets (hardcoded list: anthropic/aws/droid/github/google/groq/mistral/nebius/openai/xai). For now, codex commits locally inside the bind-mounted workspace and Snider pushes from the host after review. Mantis #69 tracks the structural fix (public-read projects so tickets-by-URL work without any auth). +- **Git identity**: baked into the image (`Codex (sbx) `). Override per-run with `git config user.email` inside the session if needed. + +## Labels + +- `com.docker.sandboxes=templates` +- `com.docker.sandboxes.flavor=codex-core-dev` +- `com.docker.sandboxes.base=core-dev:latest` +- EUPL-1.2 + +## Related + +- `../developer/Dockerfile` — core-dev base image (PHP/Go/Node/Python toolchains, ~20GB) +- Upstream: https://hub.docker.com/r/docker/sandbox-templates (codex, codex-docker, shell, shell-docker variants)