Uploading Game Files with pingctl

Download the pingctl CLI, authenticate with a push token, and push a game server build to the PingCore CDN.

pingctl is the PingCore command line tool for uploading game server builds to the CDN. One command pushes a local build directory to a CDN source:

pingctl push ./ServerBuild

It is a single static binary with no runtime dependencies. You do not install Go, Node, rclone, or an SFTP client.

Before you can push, you need a manual CDN source and a push token issued on that source's page. See CDN Sources and Push Tokens.

Download

Download the zip for your platform. Each zip contains a single pingctl binary (pingctl.exe on Windows).

Install

  1. Extract the zip.

  2. Move the binary somewhere on your PATH (for example /usr/local/bin on Linux and macOS, or a folder listed in the Windows Path environment variable).

  3. On Linux and macOS, make it executable if your unzip tool did not preserve the flag: chmod +x pingctl.

  4. Verify the install:

pingctl version

On macOS, Gatekeeper may block the first run because the binary is not notarized. Remove the quarantine attribute and run it again:

xattr -d com.apple.quarantine ./pingctl

In CI, download and extract in one step instead of committing the binary to your repository:

curl -LO https://pingcore.io/downloads/pingctl-linux-amd64.zip
unzip pingctl-linux-amd64.zip
./pingctl version

Authenticate

pingctl authenticates with a per-source push token (cdnpush_...). The token identifies exactly one CDN source, so there is no source flag and nothing else to configure.

On a developer machine, validate and store the token once:

pingctl token set

The command prompts for the token without echoing it, validates it against the platform, and saves it owner-only in ~/.pingctl/config.json.

In CI, set the token as an environment variable instead:

export PINGCORE_PUSH_TOKEN=cdnpush_...

Token resolution order: the --token flag, then PINGCORE_PUSH_TOKEN, then the token stored by pingctl token set.

Push a build

pingctl push <dir> [flags]

Flags:

  • --token <cdnpush_...>: push token, overrides the environment variable and the stored token.

  • --dry-run: show what would upload and delete without changing anything.

  • --no-delete: keep remote files that are absent locally.

  • --concurrency <n>: parallel uploads, default 4.

  • --exclude <pattern>: skip matching paths. Repeatable, for example --exclude "*.pdb" --exclude "logs/".

A push does four things:

  1. Fetches push info from the platform: the source the token denotes, SFTP upload credentials, and the current version.

  2. Delta-uploads over SFTP. Only files whose content changed are transferred. Uploads are parallel and atomic, so an interrupted push never leaves half-written files under real names.

  3. Triggers a publish. The CDN snapshots the staged files into an immutable version and distributes it to all CDN nodes.

  4. Polls until the CDN reports the outcome and prints the published version.

Files staged over SFTP go live only when a publish runs. The CDN never snapshots a manual source on its own schedule, so a slow upload cannot go live half-finished.

Project config

A pingctl.json next to your build (or in any parent directory) holds non-secret defaults:

{
  "excludes": ["*.pdb", "logs/"]
}

It never contains the token and is safe to commit.

Security notes

  • Only a SHA-256 hash of the push token exists server-side. The token itself is shown once, when it is issued.

  • SFTP host keys are pinned on first use in ~/.pingctl/known_hosts.json. A changed host key aborts the push with instructions.

  • The push token is sent only to the PingCore API over HTTPS. The SFTP credential is scoped to the single source being pushed.

CI example

# GitHub Actions, GitLab CI, or any pipeline with a secret store:
export PINGCORE_PUSH_TOKEN="$CDN_PUSH_TOKEN"
curl -LO https://pingcore.io/downloads/pingctl-linux-amd64.zip
unzip -o pingctl-linux-amd64.zip
./pingctl push ./ServerBuild --exclude "*.pdb"

A leaked push token can push builds to its one source and do nothing else. Regenerating the token on the source page invalidates the old one immediately.