processmanager

package
v0.0.0-...-c8cb9eb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: MIT Imports: 32 Imported by: 0

README

Process Managers

This package provides process manager implementations for managing game server lifecycles. Each process manager implements the contracts.ProcessManager interface.

Available Process Managers

Name Platforms Description
tmux Linux, macOS Terminal multiplexer-based process management
systemd Linux Systemd service-based process management
simple All Basic script-based process management
winsw Windows Windows Service Wrapper
shawl Windows Windows service wrapper for arbitrary programs
docker All Docker container-based process management
podman Linux, macOS Podman container-based process management

Metrics support

The Metrics(ctx, server) method returns Prometheus-style samples (see internal/app/metrics) that the daemon's metrics collector aggregates and forwards to the panel via gRPC.

PM gameap_server_up gameap_server_cpu_usage_percent gameap_server_memory_* gameap_server_network_*_bytes_total gameap_server_block_io_*_bytes_total gameap_server_process_pids
docker yes yes yes yes yes (Linux) yes
podman yes yes yes yes yes yes
systemd yes yes yes yes yes yes
tmux / simple / winsw / shawl yes

Container-backed managers tag their metrics with {server_id, server_uuid, container}. The systemd manager tags its metrics with {server_id, server_uuid, service}.

The systemd manager reads metrics from systemctl show and relies on the CPUAccounting=yes, MemoryAccounting=yes, IOAccounting=yes, IPAccounting=yes and TasksAccounting=yes directives that the daemon writes into every generated unit file. Game servers running on units created before these directives existed will report zeros (and a suppressed CPU%) until the next start/restart regenerates the unit. Metrics are also suppressed for the first sample after each restart, since the cumulative CPU counter has no baseline yet.

PID-based stats for tmux / simple / winsw / shawl are tracked as a follow-up.

Configuration

Process manager is configured in the daemon configuration file:

process_manager:
  name: docker  # or: tmux, systemd, simple, winsw, shawl, podman
  config:
    # Process manager specific configuration
    image: "debian:bookworm-slim"

Docker Process Manager

The Docker process manager runs game servers inside Docker containers using the Docker SDK.

Features
  • Container lifecycle management (create, start, stop, remove)
  • Automatic image pulling
  • Port mapping for game server ports
  • Resource limits (memory, CPU)
  • Volume mounting
  • Custom installation scripts
  • Log streaming
  • Input sending via container attach
Configuration Priority

Configuration values are resolved in the following priority order:

  1. Server Variables (server.Vars()) - Highest priority
  2. GameMod Metadata (server.GameMod().Metadata)
  3. Game Metadata (server.Game().Metadata)
  4. ProcessManager Config (process_manager.config) - Lowest priority
Metadata Keys
Runtime Configuration
Key Description Example Default
docker_image Docker image for running the server gameap/csgo:latest debian:bookworm-slim
docker_container_name Custom container name my-cs-server Server UUID
docker_memory_limit Memory limit 2g, 512m, 1024k No limit
docker_cpu_limit CPU limit (cores) 2.0, 0.5 No limit
docker_network_mode Network mode bridge, host bridge
docker_capabilities Linux capabilities (comma-separated) NET_RAW,SYS_NICE None
docker_privileged Run in privileged mode true, false false
docker_volumes Additional volumes (JSON array or comma-separated) ["/data:/data:ro"] None
docker_dns Custom DNS servers (comma-separated) 8.8.8.8,8.8.4.4 System default
docker_workdir Container working directory /home/container /server
Installation Configuration
Key Description Example Default
docker_installation_image Image for installation phase node:18-bookworm-slim None
docker_installation_script Script to run during installation See example below None
docker_installation_entrypoint Shell interpreter for the script ash, /bin/sh Auto-detected
docker_installation_user User to run installation as 1000:1000, root root

Note: If docker_installation_entrypoint is not set, the shell is auto-detected from the script's shebang line (e.g., #!/bin/ash/bin/ash). Falls back to /bin/sh if no shebang is found.

Note: Installation runs as root by default because most scripts need root permissions to install packages (apt, yum, etc.). If your script doesn't need root, set docker_installation_user to match your server user. Remember to chown files to the server user at the end of your installation script if running as root.

Examples
Basic Game Configuration (Game Metadata)
{
  "docker_image": "gameap/srcds:latest",
  "docker_memory_limit": "4g",
  "docker_cpu_limit": "2.0"
}
Server-Specific Override (Server Variables)
{
  "docker_image": "gameap/csgo:latest",
  "docker_memory_limit": "8g",
  "docker_container_name": "csgo-competitive-server"
}
Installation Script Example
{
  "docker_installation_image": "ghcr.io/parkervcp/installers:alpine",
  "docker_installation_script": "#!/bin/ash\nset -e\napk add --no-cache curl\ncurl -sL https://example.com/install.sh | ash\n"
}

The shell is auto-detected from the shebang (#!/bin/ash). To override explicitly:

{
  "docker_installation_image": "ghcr.io/parkervcp/installers:alpine",
  "docker_installation_script": "...",
  "docker_installation_entrypoint": "ash"
}
Additional Volumes

JSON array format:

{
  "docker_volumes": "[\"/shared/maps:/server/maps:ro\", \"/shared/configs:/server/configs\"]"
}

Comma-separated format:

{
  "docker_volumes": "/shared/maps:/server/maps:ro,/shared/configs:/server/configs"
}
Capabilities and Privileged Mode
{
  "docker_capabilities": "NET_RAW,NET_ADMIN,SYS_NICE",
  "docker_privileged": "false"
}
Port Mapping

Ports are automatically mapped based on server configuration:

Server Port Container Mapping
Connect Port {IP}:{ConnectPort}:{ConnectPort}/tcp and /udp
Query Port {IP}:{QueryPort}:{QueryPort}/udp (if different from Connect)
RCON Port {IP}:{RCONPort}:{RCONPort}/tcp (if different from Connect)
Container Lifecycle
Install:
  └─> If docker_installation_image && docker_installation_script:
      └─> Pull installation image
      └─> Create temp container with script
      └─> Mount server.WorkDir -> /mnt/server
      └─> Run container, wait for completion
      └─> Remove temp container
  └─> Else: Pull docker_image (optional)

Start:
  └─> Remove existing container (if any)
  └─> Pull image (if missing)
  └─> Create container
  └─> Start container

Stop:
  └─> Stop container (30s timeout)
  └─> Remove container

Status:
  └─> Inspect container
  └─> Return Running/NotRunning

GetOutput:
  └─> Get container logs (last 500 lines)

SendInput:
  └─> Attach to container stdin
  └─> Write input
Process Manager Config Options
process_manager:
  name: docker
  config:
    image: "debian:bookworm-slim"      # Default base image
    memory_limit: "2g"                  # Default memory limit
    cpu_limit: "1.0"                    # Default CPU limit
    host: "tcp://remote-docker:2376"   # Docker daemon address (DOCKER_HOST)
    cert_path: "/path/to/certs"        # TLS certificates directory (DOCKER_CERT_PATH)
    api_version: "1.41"                # Docker API version (DOCKER_API_VERSION)
Docker Connection Options
Config Key Env Var Equivalent Description
host DOCKER_HOST Docker daemon address (e.g., tcp://remote:2376, unix:///var/run/docker.sock)
cert_path DOCKER_CERT_PATH Directory containing ca.pem, cert.pem, key.pem for TLS
api_version DOCKER_API_VERSION Docker API version to use

If none of these keys are set, the client falls back to client.FromEnv (reads from environment variables).


Podman Process Manager

The Podman process manager runs game servers inside Podman containers using the Podman REST API.

Features
  • Compatible with Docker metadata keys (uses same docker_* prefix)
  • Container lifecycle management
  • Automatic image pulling
  • Port mapping
  • Resource limits
  • Volume mounting
  • Rootless container support
Prerequisites

Podman socket must be running:

# For rootless Podman
systemctl --user start podman.socket

# For root Podman
sudo systemctl start podman.socket
Configuration Priority

Same as Docker - see Configuration Priority above.

Metadata Keys

Podman uses the same metadata keys as Docker for compatibility:

Key Description Example Default
docker_image Container image gameap/csgo:latest debian:bookworm-slim
docker_container_name Custom container name my-server Server UUID
docker_memory_limit Memory limit 2g, 512m No limit
docker_cpu_limit CPU limit (cores) 2.0, 0.5 No limit
docker_network_mode Network mode bridge, host bridge
docker_capabilities Linux capabilities NET_RAW,SYS_NICE None
docker_privileged Privileged mode true, false false
docker_volumes Additional volumes ["/data:/data:ro"] None
docker_dns DNS servers 8.8.8.8,8.8.4.4 System default
docker_workdir Container working directory /home/container /server
docker_installation_image Installation image node:18 None
docker_installation_script Installation script #!/bin/bash\n... None
docker_installation_entrypoint Shell for installation script ash, /bin/sh Auto-detected from shebang
docker_installation_user User to run installation as 1000:1000, root root
Socket Configuration
process_manager:
  name: podman
  config:
    socket_path: "unix:///run/user/1000/podman/podman.sock"

Default socket paths:

  • Rootless: unix:///run/user/{UID}/podman/podman.sock
  • Root: unix:///run/podman/podman.sock
Examples
Basic Configuration
process_manager:
  name: podman
  config:
    image: "debian:bookworm-slim"
Game Metadata Example
{
  "docker_image": "gameap/minecraft:latest",
  "docker_memory_limit": "4g",
  "docker_cpu_limit": "2.0",
  "docker_dns": "8.8.8.8,1.1.1.1"
}

Comparison: Docker vs Podman

Feature Docker Podman
Windows Support Yes No
macOS Support Yes Yes
Linux Support Yes Yes
Rootless Requires setup Native
Daemon Required Daemonless
SDK Docker Go SDK REST API
Socket /var/run/docker.sock /run/podman/podman.sock

Error Handling

Both Docker and Podman process managers implement:

  • Retry logic: Connection errors are retried with exponential backoff (100ms to 5s, max 3 retries)
  • Graceful container removal: Containers are force-removed on stop/uninstall
  • Image auto-pull: Missing images are automatically pulled on start
  • Stop timeout: 30 seconds default timeout for graceful container stop

Troubleshooting

Docker

Connection refused

# Check Docker daemon is running
sudo systemctl status docker

# Check socket permissions
ls -la /var/run/docker.sock

Permission denied

# Add user to docker group
sudo usermod -aG docker $USER
# Re-login required
Podman

Socket not found

# Start Podman socket (rootless)
systemctl --user enable --now podman.socket

# Verify socket exists
ls -la /run/user/$(id -u)/podman/podman.sock

Connection refused

# Check Podman socket status
systemctl --user status podman.socket

# Test socket
curl --unix-socket /run/user/$(id -u)/podman/podman.sock http://d/v4.0.0/libpod/info

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnknownProcessManager = errors.New("unknown process manager")
	ErrEmptyUser             = errors.New("empty user")
	ErrUserNotFound          = errors.New("user not found")
	ErrInvalidUserPassword   = errors.New("invalid user password")
	ErrEmptyCommand          = errors.New("empty command")
	ErrNotImplemented        = errors.New("not implemented")
	ErrContainerNotRunning   = errors.New("container is not running")
	ErrServiceNotRunning     = errors.New("service is not running")
)

Functions

func Load

func Load(
	name string, cfg *config.Config, executor, detailedExecutor contracts.Executor,
) (contracts.ProcessManager, error)

Types

type Docker

type Docker struct {
	// contains filtered or unexported fields
}

func NewDocker

func NewDocker(cfg *config.Config, _, _ contracts.Executor) *Docker

func (*Docker) Attach

func (pm *Docker) Attach(
	ctx context.Context, server *domain.Server, in io.Reader, out io.Writer,
) error

func (*Docker) GetOutput

func (pm *Docker) GetOutput(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Docker) HasOwnInstallation

func (pm *Docker) HasOwnInstallation(server *domain.Server) bool

func (*Docker) Install

func (pm *Docker) Install(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Docker) Metrics

func (pm *Docker) Metrics(ctx context.Context, server *domain.Server) ([]domain.Metric, error)

Metrics returns CPU, memory, network and block-IO metrics for the container backing this server. Falls back to the cached liveness gauge alone when the container is missing or stats cannot be read.

func (*Docker) Restart

func (pm *Docker) Restart(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Docker) SendInput

func (pm *Docker) SendInput(
	ctx context.Context, input string, server *domain.Server, _ io.Writer,
) (domain.Result, error)

func (*Docker) Start

func (pm *Docker) Start(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Docker) Status

func (pm *Docker) Status(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Docker) Stop

func (pm *Docker) Stop(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Docker) Uninstall

func (pm *Docker) Uninstall(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

type Podman

type Podman struct {
	// contains filtered or unexported fields
}

func NewPodman

func NewPodman(cfg *config.Config, _, _ contracts.Executor) *Podman

func (*Podman) Attach

func (pm *Podman) Attach(
	_ context.Context, _ *domain.Server, _ io.Reader, _ io.Writer,
) error

func (*Podman) GetOutput

func (pm *Podman) GetOutput(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Podman) HasOwnInstallation

func (pm *Podman) HasOwnInstallation(server *domain.Server) bool

func (*Podman) Install

func (pm *Podman) Install(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Podman) Metrics

func (pm *Podman) Metrics(ctx context.Context, server *domain.Server) ([]domain.Metric, error)

Metrics returns CPU, memory, network and block-IO metrics for the container backing this server. Falls back to the cached liveness gauge alone when the container is missing or stats cannot be read.

func (*Podman) Restart

func (pm *Podman) Restart(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Podman) SendInput

func (pm *Podman) SendInput(
	ctx context.Context, input string, server *domain.Server, _ io.Writer,
) (domain.Result, error)

func (*Podman) Start

func (pm *Podman) Start(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Podman) Status

func (pm *Podman) Status(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Podman) Stop

func (pm *Podman) Stop(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*Podman) Uninstall

func (pm *Podman) Uninstall(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

type Simple

type Simple struct {
	// contains filtered or unexported fields
}

func NewSimple

func NewSimple(cfg *config.Config, executor, detailedExecutor contracts.Executor) *Simple

func (*Simple) Attach

func (pm *Simple) Attach(
	ctx context.Context, server *domain.Server, in io.Reader, out io.Writer,
) error

func (*Simple) GetOutput

func (pm *Simple) GetOutput(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Simple) HasOwnInstallation

func (pm *Simple) HasOwnInstallation(_ *domain.Server) bool

func (*Simple) Install

func (pm *Simple) Install(_ context.Context, _ *domain.Server, _ io.Writer) (domain.Result, error)

func (*Simple) Metrics

func (pm *Simple) Metrics(_ context.Context, server *domain.Server) ([]domain.Metric, error)

Metrics returns only the cached process-active gauge for now. PID-based resource collection (cpu/memory/io via gopsutil/process) is tracked as a follow-up since the simple process manager does not persist PIDs.

func (*Simple) Restart

func (pm *Simple) Restart(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Simple) SendInput

func (pm *Simple) SendInput(
	ctx context.Context, input string, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Simple) Start

func (pm *Simple) Start(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Simple) Status

func (pm *Simple) Status(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Simple) Stop

func (pm *Simple) Stop(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Simple) Uninstall

func (pm *Simple) Uninstall(_ context.Context, _ *domain.Server, _ io.Writer) (domain.Result, error)

type SystemD

type SystemD struct {
	// contains filtered or unexported fields
}

func NewSystemD

func NewSystemD(cfg *config.Config, _, detailedExecutor contracts.Executor) *SystemD

func (*SystemD) Attach

func (pm *SystemD) Attach(
	ctx context.Context, server *domain.Server, in io.Reader, out io.Writer,
) error

func (*SystemD) GetOutput

func (pm *SystemD) GetOutput(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*SystemD) HasOwnInstallation

func (pm *SystemD) HasOwnInstallation(_ *domain.Server) bool

func (*SystemD) Install

func (pm *SystemD) Install(
	_ context.Context, _ *domain.Server, _ io.Writer,
) (domain.Result, error)

func (*SystemD) Metrics

func (pm *SystemD) Metrics(ctx context.Context, server *domain.Server) ([]domain.Metric, error)

Metrics returns CPU, memory, network, block-IO and PID counters for the systemd unit backing this server. Falls back to the cached liveness gauge alone when the unit is unknown or `systemctl show` cannot be executed.

Note: services created before the *Accounting=yes directives were added to `buildServiceConfig` will report zeros (and a suppressed CPU%) until the next start/restart regenerates the unit file.

func (*SystemD) Restart

func (pm *SystemD) Restart(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*SystemD) SendInput

func (pm *SystemD) SendInput(
	ctx context.Context, input string, server *domain.Server, _ io.Writer,
) (domain.Result, error)

func (*SystemD) Start

func (pm *SystemD) Start(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*SystemD) Status

func (pm *SystemD) Status(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*SystemD) Stop

func (pm *SystemD) Stop(ctx context.Context, server *domain.Server, out io.Writer) (domain.Result, error)

func (*SystemD) Uninstall

func (pm *SystemD) Uninstall(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

type Tmux

type Tmux struct {
	// contains filtered or unexported fields
}

func NewTmux

func NewTmux(cfg *config.Config, executor, detailedExecutor contracts.Executor) *Tmux

func (*Tmux) Attach

func (pm *Tmux) Attach(
	ctx context.Context, server *domain.Server, in io.Reader, out io.Writer,
) error

func (*Tmux) GetOutput

func (pm *Tmux) GetOutput(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Tmux) HasOwnInstallation

func (pm *Tmux) HasOwnInstallation(_ *domain.Server) bool

func (*Tmux) Install

func (pm *Tmux) Install(_ context.Context, _ *domain.Server, _ io.Writer) (domain.Result, error)

func (*Tmux) Metrics

func (pm *Tmux) Metrics(_ context.Context, server *domain.Server) ([]domain.Metric, error)

Metrics returns only the cached process-active gauge for now. Tmux sessions hold child PIDs needed for resource stats; richer metrics are tracked as a follow-up.

func (*Tmux) Restart

func (pm *Tmux) Restart(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Tmux) SendInput

func (pm *Tmux) SendInput(
	ctx context.Context, input string, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Tmux) Start

func (pm *Tmux) Start(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Tmux) Status

func (pm *Tmux) Status(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Tmux) Stop

func (pm *Tmux) Stop(
	ctx context.Context, server *domain.Server, out io.Writer,
) (domain.Result, error)

func (*Tmux) Uninstall

func (pm *Tmux) Uninstall(_ context.Context, _ *domain.Server, _ io.Writer) (domain.Result, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL