Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Build & Publish

on:
push:
branches: ['main', 'master']
tags: ['v*']
pull_request:
branches: ['main', 'master']
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
inputs:
sampsharp_ref:
description: 'SampSharp ref (commit/branch/tag)'
default: 'master'

permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
name: Build & publish base image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Compute image tags
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,prefix=sha-,format=short
type=raw,value=latest,enable={{is_default_branch}}

- name: Build & push
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SAMPSHARP_REF=${{ github.event.inputs.sampsharp_ref || 'master' }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
26 changes: 26 additions & 0 deletions 6.0.3/example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app

# Copy csproj and restore as distinct layers
COPY src/*.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY src/* ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM ghcr.io/sampsharp/sampsharp-docker:6.0.3
WORKDIR /samp
COPY --from=build-env /app/out ./gamemode

# Install SA-MP server with pawnctl
COPY pawn.json .
RUN sampctl package ensure

# Because these gamemodes were not created by pawnctl, we need to copy them after sampctl has initialized. In a future version of SampSharp
# the empty gamemode and filterscript will automatically be written to the corresponding directory if they are missing.
COPY gamemodes ./gamemodes
COPY filterscripts ./filterscripts

ENTRYPOINT ["sampctl", "package", "run"]
17 changes: 17 additions & 0 deletions 6.0.3/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Docker example
==============

### Building
```
docker build . -t testserver
```

### Running

```
docker run --rm -it -p 7777:7777/udp testserver
```

### Quircks
- SampSharp's default filterscripts and gamemode are not installed with the plugin through sampctl, so they're included in the image manually for now. In the future, the SampSharp plugin will automatically write these modes into the corresponding directories if they could not be found.
- SampSharp performs a check on the configured pawn gamemodes by default. It checks for a line containing `gamemode0 empty 1`, but sampctl generates the line `gamemode0 empty` (without the 1). Normally SampSharp wouldn't start in this situation. We work around this for now with the `skip_empty_check true` option.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
70 changes: 70 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# syntax=docker/dockerfile:1.7
#
# sampsharp-docker — slim base image: SampSharp native host + .NET runtime.
#
# Intentionally does NOT include open.mp / omp-server: SampSharp and open.mp
# have independent release cadences, gluing them into one layer is awkward.
# The consumer brings omp-server from a separate source (built from sources,
# release artefact, or its own image) — see example/.
#
# Contents:
# /server/components/SampSharp.so — native host for .NET gamemodes
# .NET 10 runtime — for executing managed gamemodes
# tini — clean SIGTERM handling
#
# Build args:
# SAMPSHARP_REPO / SAMPSHARP_REF — git source of SampSharp
# DOTNET_VERSION — major .NET runtime for the final stage

ARG DOTNET_VERSION=10.0
ARG SAMPSHARP_REPO=https://github.com/ikkentim/SampSharp.git
ARG SAMPSHARP_REF=master

# =============================================================================
# Stage 1: SampSharp.so (sampsharp-component)
# =============================================================================
FROM debian:bookworm-slim AS sampsharp-builder

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential cmake ninja-build git ca-certificates pkg-config \
&& rm -rf /var/lib/apt/lists/*

ARG SAMPSHARP_REPO
ARG SAMPSHARP_REF
WORKDIR /build
# Full clone (not --depth=1) — otherwise checkout of an arbitrary SHA fails.
RUN git clone --recursive "$SAMPSHARP_REPO" SampSharp \
&& git -C SampSharp checkout "$SAMPSHARP_REF" \
&& git -C SampSharp submodule update --init --recursive

RUN --mount=type=cache,target=/build/SampSharp-build \
set -e; \
rm -f /build/SampSharp-build/CMakeCache.txt; \
rm -rf /build/SampSharp-build/CMakeFiles; \
cmake -B /build/SampSharp-build -S SampSharp/src/sampsharp-component -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo; \
cmake --build /build/SampSharp-build --config RelWithDebInfo; \
mkdir -p /artifacts; \
cp /build/SampSharp-build/artifacts/SampSharp.so /artifacts/

# =============================================================================
# Stage 2: runtime (.NET 10 + SampSharp.so)
# =============================================================================
FROM mcr.microsoft.com/dotnet/runtime:${DOTNET_VERSION} AS runtime
WORKDIR /server

RUN apt-get update && apt-get install -y --no-install-recommends \
libssl3 libstdc++6 ca-certificates tini \
&& rm -rf /var/lib/apt/lists/*

COPY --from=sampsharp-builder /artifacts/SampSharp.so /server/components/SampSharp.so

# Directory skeleton - open.mp expects these to exist at startup.
RUN mkdir -p /server/components /server/plugins /server/scriptfiles \
/server/gamemodes /server/filterscripts /server/include

# Note: omp-server is not included — the consumer ships its own with the
# gamemode image. tini wraps the entry point; the actual CMD is up to the
# consumer.
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["./omp-server"]
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,79 @@
sampsharp-docker
================

See `example/` for how to use this image.
Slim base image for SampSharp gamemodes running on open.mp x64.

Contents:

* `/server/components/SampSharp.so` — native host that loads the .NET runtime
and your gamemode assembly inside open.mp's component system.
* `.NET 10 runtime` — for executing managed gamemodes.
* `tini` as the entry-point wrapper for clean signal handling.

What's intentionally **not** included:

* `omp-server` and the open.mp core components (`Actors.so`, `Vehicles.so`, …).
The image isn't tied to a specific open.mp version — bring your own
`omp-server` build (from source, a release artefact, or a separate image)
in the consumer Dockerfile. See `example/` for a working pattern.

Usage
-----

```Dockerfile
FROM ghcr.io/<owner>/sampsharp-docker:latest

# 1) Provide omp-server + open.mp core components (your build/release).
COPY --from=<your-omp-image> /omp-server /server/omp-server
COPY --from=<your-omp-image> /components/ /server/components/

# 2) Drop your published .NET gamemode in.
COPY ./gamemode-publish/ /server/gamemode/MyMode/

# 3) open.mp config that points at your gamemode.
COPY ./config.json /server/config.json
```

The base image sets `WORKDIR=/server`, `ENTRYPOINT=tini`, `CMD=./omp-server`,
and `EXPOSE 7777/udp` — override any of those if your scenario needs it.

Building locally
----------------

```sh
docker build -t sampsharp-docker:dev .
```

To pin a specific SampSharp commit / tag instead of `master`:

```sh
docker build \
--build-arg SAMPSHARP_REF=v0.11.0 \
-t sampsharp-docker:0.11.0 .
```

Tags published by CI
--------------------

* `:latest` — `master` of SampSharp, rebuilt weekly.
* `:sha-<short>` — every push to the default branch.
* `:<git-tag>` — release tags pushed to this repo.

CI is defined in `.github/workflows/build-and-publish.yml`. The default branch
push rebuilds and republishes; `workflow_dispatch` can override
`SAMPSHARP_REF` to build against an arbitrary upstream ref on demand.

Example
-------

See [`example/`](./example) for a complete consumer Dockerfile that builds a
trivial SampSharp gamemode against this base image, pulls in `omp-server` from
source, and runs the result.

Legacy (SampSharp 6.0.3 + classic SA-MP)
----------------------------------------

The previous incarnation of this image, targeting SampSharp 6.0.3 on classic
SA-MP via `sampctl`, is preserved under [`6.0.3/`](./6.0.3) along with its
matching example in [`6.0.3/example/`](./6.0.3/example). It's no longer built
by CI but kept for reference and for users still on the classic stack.
88 changes: 68 additions & 20 deletions example/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,74 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
# syntax=docker/dockerfile:1.7
#
# Example consumer of sampsharp-docker.
#
# Stage 1: builds open.mp x64 from upstream sources to provide omp-server +
# core components (Actors.so, Vehicles.so, etc.). In a real deployment
# you'd typically replace this with `COPY --from=<your-omp-image>`.
# Stage 2: placeholder for your .NET gamemode build (csproj + publish dir).
# Stage 3: assembly - pulls SampSharp.so from the base image, omp-server from
# stage 1, your gamemode from stage 2, plus the config.json next to
# this Dockerfile.

# Copy csproj and restore as distinct layers
COPY src/*.csproj ./
RUN dotnet restore
# =============================================================================
# Stage 1: open.mp build (replace with your own image / release artefact in prod)
# =============================================================================
FROM debian:bookworm-slim AS openmp-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential cmake ninja-build git ca-certificates pkg-config \
clang lld libssl-dev python3 python3-pip \
&& pip install --break-system-packages 'conan<2' \
&& rm -rf /var/lib/apt/lists/*

# Copy everything else and build
COPY src/* ./
RUN dotnet publish -c Release -o out
ARG OPENMP_REPO=https://github.com/openmultiplayer/open.mp.git
ARG OPENMP_REF=master
WORKDIR /build
RUN git clone --recursive "$OPENMP_REPO" open.mp \
&& git -C open.mp checkout "$OPENMP_REF" \
&& git -C open.mp submodule update --init --recursive

# Build runtime image
FROM ghcr.io/sampsharp/sampsharp-docker:6.0.3
WORKDIR /samp
COPY --from=build-env /app/out ./gamemode
RUN --mount=type=cache,target=/root/.conan \
--mount=type=cache,target=/build/openmp-build \
set -e; \
rm -f /build/openmp-build/CMakeCache.txt; \
rm -rf /build/openmp-build/CMakeFiles; \
cmake -B /build/openmp-build -S open.mp -G Ninja \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DTARGET_BUILD_ARCH=x86_64 -DSHARED_OPENSSL=TRUE; \
cmake --build /build/openmp-build --config RelWithDebInfo; \
mkdir -p /artifacts/components; \
find /build/openmp-build -maxdepth 6 -name omp-server -executable -type f -exec cp {} /artifacts/ \; ; \
find /build/openmp-build -maxdepth 6 -name '*.so' -path '*components*' -exec cp {} /artifacts/components/ \; ; \
test -x /artifacts/omp-server

# Install SA-MP server with pawnctl
COPY pawn.json .
RUN sampctl package ensure
# =============================================================================
# Stage 2: your gamemode (placeholder — replace with the real build)
# =============================================================================
# Real example would look something like:
#
# FROM mcr.microsoft.com/dotnet/sdk:10.0 AS gamemode-builder
# WORKDIR /src
# COPY MyMode.csproj ./
# RUN dotnet restore
# COPY . .
# RUN dotnet publish MyMode.csproj -c Release -o /publish
#
# For this example we just stage an empty directory.
FROM busybox:stable AS gamemode-builder
RUN mkdir -p /publish

# Because these gamemodes were not created by pawnctl, we need to copy them after sampctl has initialized. In a future version of SampSharp
# the empty gamemode and filterscript will automatically be written to the corresponding directory if they are missing.
COPY gamemodes ./gamemodes
COPY filterscripts ./filterscripts
# =============================================================================
# Stage 3: final image
# =============================================================================
FROM ghcr.io/<owner>/sampsharp-docker:latest
# (Replace <owner> with the actual repository owner once the image is published.)

ENTRYPOINT ["sampctl", "package", "run"]
COPY --from=openmp-builder /artifacts/omp-server /server/omp-server
COPY --from=openmp-builder /artifacts/components/ /server/components/

COPY --from=gamemode-builder /publish /server/gamemode/MyMode/

COPY ./config.json /server/config.json

# WORKDIR, ENTRYPOINT, CMD, EXPOSE are inherited from the base image.
Loading