diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0ddf0317 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +.git/ +build/ +cmake-build-*/ +bin/ +out/ +.cache/ +**/.DS_Store + +# IDE +.idea/ +.vs/ +*.user +*.suo + +# CI +.github/ + +# Scripts +scripts/ + +# Docs +*.md +!README.md + +# Docker files +docker-compose*.yml \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..269cbd50 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Official environment variables for BeamMP Server: https://docs.beammp.com/server/manual/#env +BEAMMP_DEBUG=false +BEAMMP_PRIVATE=true +BEAMMP_PORT=30814 +BEAMMP_MAX_CARS=1 +BEAMMP_MAX_PLAYERS=8 +BEAMMP_MAP=/levels/gridmap_v2/info.json +BEAMMP_NAME=BeamMP Server +BEAMMP_DESCRIPTION="BeamMP Default Description" +BEAMMP_TAGS= +BEAMMP_RESOURCE_FOLDER=Resources +BEAMMP_AUTH_KEY= +BEAMMP_LOG_CHAT=true \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..bdb6dd2c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,128 @@ +name: Docker Build and Publish (native multi-arch) + +on: + push: + branches: + - 'develop' + - 'minor' + tags: + - 'v*' + pull_request: + +env: + REGISTRY: ghcr.io + +jobs: + build: + name: Build ${{ matrix.arch }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-24.04 + - arch: arm64 + platform: linux/arm64 + runner: ubuntu-24.04-arm + + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set image name (lowercase) + run: echo "IMAGE_NAME=${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/metadata-action@v5 + id: meta + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-${{ matrix.arch }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - uses: docker/build-push-action@v6 + with: + context: . + platforms: ${{ matrix.platform }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }} + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }},mode=max + + provenance: false + sbom: false + + merge: + runs-on: ubuntu-24.04 + needs: [build] + permissions: + contents: read + packages: write + + steps: + - name: Set image name (lowercase) + run: echo "IMAGE_NAME=${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV + + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/metadata-action@v5 + id: meta + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Create multi-arch manifests + env: + TAGS: ${{ steps.meta.outputs.tags }} + run: | + set -euo pipefail + + while IFS= read -r tag; do + [ -n "$tag" ] || continue + + echo "Merging: $tag" + docker buildx imagetools create \ + -t "$tag" \ + "${tag}-amd64" \ + "${tag}-arm64" + done <<< "$TAGS" + + - name: Inspect + run: | + # Display the created image details for verification + # Use the first tag from the list for inspection + TAG_TO_INSPECT=$(echo "${{ steps.meta.outputs.tags }}" | head -n 1) + docker buildx imagetools inspect $TAG_TO_INSPECT || true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4b06a998..31ecbcbe 100644 --- a/.gitignore +++ b/.gitignore @@ -478,3 +478,8 @@ callgrind.* notes/* compile_commands.json nohup.out + +build/ + +# Ignore Docker env file +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c0ef1983 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,99 @@ +FROM debian:12-slim AS builder + +ARG VCPKG_COMMIT=5bf0c55239da398b8c6f450818c9e28d36bf9966 +ARG BUILD_PARALLEL=2 +ARG ENABLE_LTO=ON + +ENV DEBIAN_FRONTEND=noninteractive \ + CMAKE_BUILD_TYPE=Release \ + VCPKG_FORCE_SYSTEM_BINARIES=1 \ + VCPKG_FEATURE_FLAGS=manifests + +WORKDIR /work + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + zip unzip tar \ + git \ + cmake \ + ninja-build \ + build-essential \ + pkg-config \ + liblua5.3-0 liblua5.3-dev \ + binutils \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone https://github.com/microsoft/vcpkg /work/vcpkg && \ + cd /work/vcpkg && \ + git checkout ${VCPKG_COMMIT} && \ + ./bootstrap-vcpkg.sh -disableMetrics + +ENV VCPKG_ROOT=/work/vcpkg + +COPY vcpkg.json ./ +COPY deps/ ./deps/ +COPY cmake/ ./cmake/ +COPY CMakeLists.txt ./ +COPY include/ ./include/ +COPY src/ ./src/ +COPY test/ ./test/ + +RUN --mount=type=cache,target=/root/.cache/vcpkg,sharing=locked \ + --mount=type=cache,target=/work/vcpkg/downloads,sharing=locked \ + --mount=type=cache,target=/work/vcpkg/buildtrees,sharing=locked \ + --mount=type=cache,target=/work/vcpkg/packages,sharing=locked \ + --mount=type=cache,target=/work/build-server,sharing=locked \ + mkdir -p /work/out && \ + cmake -S /work -B /work/build-server -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=/work/vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_EXE_LINKER_FLAGS="-Wl,--export-dynamic" \ + -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" \ + -DBeamMP-Server_ENABLE_LTO=${ENABLE_LTO} \ + && cmake --build /work/build-server --parallel ${BUILD_PARALLEL} -t BeamMP-Server \ + && objcopy --only-keep-debug /work/build-server/BeamMP-Server /work/build-server/BeamMP-Server.debug \ + && strip --strip-unneeded /work/build-server/BeamMP-Server \ + && objcopy --add-gnu-debuglink=/work/build-server/BeamMP-Server.debug /work/build-server/BeamMP-Server \ + && install -m 0755 /work/build-server/BeamMP-Server /work/out/BeamMP-Server + +RUN --mount=type=cache,target=/root/.cache/vcpkg,sharing=locked \ + --mount=type=cache,target=/work/vcpkg/downloads,sharing=locked \ + --mount=type=cache,target=/work/vcpkg/buildtrees,sharing=locked \ + --mount=type=cache,target=/work/vcpkg/packages,sharing=locked \ + --mount=type=cache,target=/work/build-tests,sharing=locked \ + cmake -S /work -B /work/build-tests -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=/work/vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_EXE_LINKER_FLAGS="-Wl,--export-dynamic" \ + -DBeamMP-Server_ENABLE_LTO=OFF \ + && cmake --build /work/build-tests --parallel 1 -t BeamMP-Server-tests + +FROM debian:12-slim AS runtime + +RUN apt-get update && apt-get install -y --no-install-recommends \ + liblua5.3-0 \ + lua5.3 \ + luarocks \ + build-essential \ + pkg-config \ + liblua5.3-dev \ + git \ + unzip \ + curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -u 1000 beammp && \ + mkdir -p /app /app/data /config /resources && \ + chown -R beammp:beammp /app /config /resources + +COPY --from=builder /work/out/BeamMP-Server /app/BeamMP-Server + +WORKDIR /app +USER beammp + +EXPOSE 30814 +ENTRYPOINT ["/app/BeamMP-Server"] \ No newline at end of file diff --git a/cmake/Git.cmake b/cmake/Git.cmake index d88f5a04..aaf96769 100644 --- a/cmake/Git.cmake +++ b/cmake/Git.cmake @@ -1,21 +1,38 @@ -find_package(Git) +find_package(Git QUIET) + +# Only try to update submodules if: +# - the option is enabled +# - git is available +# - we are in a real git checkout (".git" exists) if(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES) if(Git_FOUND) - message(STATUS "Git found, submodule update and init") - execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - message(SEND_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules. This may result in missing dependencies.") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") + message(STATUS "Git found, submodule update and init") + execute_process( + COMMAND "${GIT_EXECUTABLE}" submodule update --init --recursive + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE GIT_SUBMOD_RESULT + ) + if(NOT GIT_SUBMOD_RESULT EQUAL 0) + message(SEND_ERROR + "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules. " + "This may result in missing dependencies." + ) + endif() + else() + message(STATUS "No .git directory found - skipping submodule update (assume submodules are already present).") endif() else() - message(SEND_ERROR "git required for checking out submodules, but not found. Submodules will not be checked out - this may result in missing dependencies.") + message(SEND_ERROR + "git required for checking out submodules, but not found. Submodules will not be checked out - " + "this may result in missing dependencies." + ) endif() endif() -if(Git_FOUND) - +if(Git_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") + # If you compute PRJ_GIT_HASH somewhere else, keep that logic there. else() - message(STATUS "Git not found - the version will not include a git hash.") + message(STATUS "Git not found (or not a git checkout) - the version will not include a git hash.") set(PRJ_GIT_HASH "unknown") -endif() +endif() \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 00000000..e9903a7a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,18 @@ +services: + beammp-server: + build: + context: . + dockerfile: Dockerfile + image: ghcr.io/beammp/beammp-server + container_name: beammp-server + restart: unless-stopped + ports: + - "${BEAMMP_PORT}:${BEAMMP_PORT}/tcp" + - "${BEAMMP_PORT}:${BEAMMP_PORT}/udp" + volumes: + - ./Resources:/app/Resources + env_file: + - .env + environment: + - BEAMMP_PROVIDER_DISABLE_CONFIG=true +