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" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc4a9fa..1c8865a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,15 +1,24 @@ +# Host UK Container Images +# +# 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: [main] + branches: [dev, main] tags: ['v*'] pull_request: - branches: [main] + branches: [dev, main] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: - REGISTRY: ghcr.io + GHCR_REGISTRY: ghcr.io jobs: # ============================================================ @@ -23,10 +32,18 @@ jobs: packages: write strategy: + fail-fast: false matrix: image: - developer - server-php + 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 @@ -42,21 +59,57 @@ 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: 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.REGISTRY }}/host-uk/${{ matrix.image == 'developer' && 'core-dev' || matrix.image }} + images: | + ${{ env.GHCR_REGISTRY }}/host-uk/${{ matrix.ghcr_name }} tags: | type=ref,event=branch 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}} - type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + flavor: | + latest=false - name: Build and push uses: docker/build-push-action@v5 @@ -64,62 +117,64 @@ 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 # ============================================================ - # Build LinuxKit Images + # Build LinuxKit Images (only on tags) # ============================================================ linuxkit: name: LinuxKit (${{ matrix.image }}-${{ matrix.arch }}) runs-on: ubuntu-latest - needs: docker # Needs Docker images to be built first + needs: docker + if: startsWith(github.ref, 'refs/tags/v') strategy: + fail-fast: false 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 +207,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 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/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 5943521..67d5684 100644 --- a/developer/Dockerfile +++ b/developer/Dockerfile @@ -1,289 +1,225 @@ # ============================================================ -# Core Developer - Full-Fat Development Environment +# Core Developer — Multi-Language Development Environment # -# A comprehensive developer environment with 100+ embedded tools -# for AI-assisted development with Claude Code CLI. +# 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 # ============================================================ -ARG ALPINE_VERSION=3.22 +# ============================================================ +# Stage: build-base — ALL system packages +# ============================================================ +FROM debian:trixie-slim AS build-base -FROM alpine:${ALPINE_VERSION} +ENV DEBIAN_FRONTEND=noninteractive -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.licenses="EUPL-1.2" -LABEL org.opencontainers.image.vendor="Host UK" -LABEL org.opencontainers.image.title="Core Developer" +RUN apt-get update +RUN apt-get upgrade -y -# 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 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}" +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 -# ============================================================ -# 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 cmake ninja meson gcc g++ musl-dev linux-headers \ - # Core utilities - coreutils findutils grep sed gawk less tree ncdu shadow gettext \ - # Compression - zip unzip tar gzip xz bzip2 zstd \ - # 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 helix \ - # Directory navigation - zoxide broot +# ---- Non-root user (Claude Code refuses to run as root) ---- +ARG DEV_USER=agent +ARG DEV_UID=1000 +ARG DEV_GID=1000 -# ============================================================ -# VCS & Git Tools -# ============================================================ -RUN apk add --no-cache \ - git git-lfs github-cli lazygit git-delta +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 -# ============================================================ -# Node.js Ecosystem -# ============================================================ -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 +RUN chown -R ${DEV_USER}:${DEV_USER} /home/${DEV_USER} -# ============================================================ -# Python Ecosystem -# ============================================================ -RUN apk add --no-cache python3 py3-pip python3-dev +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 pip3 install --break-system-packages \ - pipx uv \ - ipython httpie \ - ruff \ - mkdocs mkdocs-material \ - aider-chat \ - llm +RUN brew install hadolint trivy syft grype gitleaks trufflehog dive conftest opa git-secrets gh +#RUN brew install openssl@3 + +USER root # ============================================================ -# PHP Ecosystem +# Stage: build-php — PHP 8.5 + Composer + tools (FROM build-base) # ============================================================ -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 +FROM build-base AS build-php -RUN ln -sf /usr/bin/php84 /usr/bin/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 -# Composer RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/bin --filename=composer -# PHP tools via Composer +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 \ - phpdocumentor/phpdocumentor:^3.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 - -# ============================================================ -# Go Ecosystem -# ============================================================ -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 - -# ============================================================ -# 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 - -# ============================================================ -# Database Clients -# ============================================================ -RUN apk add --no-cache \ - postgresql16-client \ - mariadb-client \ - redis \ - sqlite - -# usql - universal SQL client -RUN go install github.com/xo/usql@latest + 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 + +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 + +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: build-go — Go 1.26.1 + tools (FROM build-base) +# ============================================================ +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:/${DEV_USER}/go/bin:${PATH}" +ENV GOPATH=/home/${DEV_USER}/go + +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 # ============================================================ -# Infrastructure & DevOps +# Stage: build-node — Node.js 22 LTS + AI agent CLIs (FROM build-base) # ============================================================ -RUN apk add --no-cache \ - docker-cli docker-cli-compose \ - kubectl helm \ - terraform ansible +FROM build-go AS build-node -# 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 +USER root +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - +RUN apt-get install -y nodejs -# 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 +ENV HOME=/home/agent +ENV HOMEBREW_PREFIX=$HOME/.linuxbrew +ENV HOMEBREW_CELLAR=$HOME/.linuxbrew/Cellar -# dive - Docker image explorer -RUN go install github.com/wagoodman/dive@latest - -# 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 +RUN #brew install node +#RUN brew install --cask codex +#RUN brew install --cask 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 +RUN npm install -g npm-audit-html license-checker +RUN npm install -g depcheck @cyclonedx/cdxgen snyk # ============================================================ -# HTTP & Networking +# Stage: build-python — Python 3.13 + linters (FROM build-base) # ============================================================ -RUN apk add --no-cache websocat +FROM build-node AS build-python -# grpcurl -RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest +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 -# 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 +# 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 # ============================================================ -# Data Processing +# Stage: core-dev — Final assembly (COPY only, no installs) # ============================================================ -RUN apk add --no-cache jq yq miller - -# fx - JSON viewer -RUN go install github.com/antonmedv/fx@latest +FROM build-python AS core-dev +ARG DEV_USER=agent -# gron - Make JSON greppable -RUN go install github.com/tomnomnom/gron@latest - -# 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 -RUN go install github.com/sigstore/cosign/v2/cmd/cosign@latest - -# 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 +# ---- 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 -# ============================================================ -# 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 +RUN apt-get clean +RUN npm cache clean --force -# Playwright (via npm, already installed) +USER ${DEV_USER} +RUN brew upgrade +RUN brew autoremove +RUN brew cleanup --prune=all -# ============================================================ -# Misc Tools -# ============================================================ -RUN apk add --no-cache direnv +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="Lethean Community" +LABEL org.opencontainers.image.title="Core Developer" -# ============================================================ -# Shell Configuration -# ============================================================ -# Oh-My-Zsh -RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended +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 -# Zsh plugins -RUN 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 from parallel build stages ---- -# ============================================================ -# 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 +# ---- 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}" -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 efc1829..5018d04 100644 --- a/developer/scripts/entrypoint.sh +++ b/developer/scripts/entrypoint.sh @@ -3,9 +3,10 @@ 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,17 +32,23 @@ 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 +# 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" + + +# 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) 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", + }, + }, +]; 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/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. 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' +]); 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 "$@"