ci: run client builds on a Java 25 build agent instead of the controller

The toolchain image becomes a Jenkins inbound agent (jenkins/inbound-agent,
Java 25) labelled dorknet-builder. Jenkins itself stays on the stock image
and runs no builds, which also clears the built-in-node warning.

- Dockerfile: inbound-agent base; restic cache on the agent's own volume
- docker-compose.yml: the agent as a Coolify Docker Compose application,
  reaching Jenkins over WebSocket on the predefined Coolify network
- Jenkinsfile: agent { label 'dorknet-builder' }
- README: Java 25 for both sides (Java 21 support ends Sep 2027, Java 17
  is unsupported), restart policy, node setup and Coolify deployment
This commit is contained in:
Alexa
2026-10-25 01:14:17 +03:00
parent 0839d932ce
commit cf3e6ea025
4 changed files with 102 additions and 72 deletions
Vendored
+12 -13
View File
@@ -1,14 +1,13 @@
// DorkNet client builds — PC (patcher zip + full client) and Quest (.xapk). // DorkNet client builds — PC (patcher zip + full client) and Quest (.xapk).
// //
// Runs on the Jenkins image from ci/jenkins (the stock image plus Python with // Runs on the "dorknet-builder" agent from ci/jenkins (an inbound agent with
// LIEF, .NET, the Android NDK/build-tools, apktool, restic and rclone). // Python + LIEF, .NET, the Android NDK/build-tools, apktool, restic and rclone).
// //
// Stock builds come out of the restic build archive on the FTP. Where it lives // Stock builds come out of the restic build archive on the FTP. Where it lives
// is server configuration, not part of the repo: the FTP host, login and the // is server configuration, not part of the repo: the FTP host, login and the
// restic password are Jenkins credentials, and the repository folders come // restic password are Jenkins credentials, and the repository folders come
// from the Jenkins container's environment (ARCHIVE_PC_REPO / // from the agent's environment (ARCHIVE_PC_REPO / ARCHIVE_QUEST_REPO), each
// ARCHIVE_QUEST_REPO), each overridable per run. A build is picked by // overridable per run. A build is picked by snapshot tags.
// snapshot tags.
// //
// On a push, only the platforms whose code changed are rebuilt. A manual run // On a push, only the platforms whose code changed are rebuilt. A manual run
// picks the platforms with PLATFORMS. See ci/jenkins/README.md. // picks the platforms with PLATFORMS. See ci/jenkins/README.md.
@@ -23,7 +22,7 @@ def buildPc = false
def buildQuest = false def buildQuest = false
pipeline { pipeline {
agent any agent { label 'dorknet-builder' }
options { options {
timestamps() timestamps()
@@ -45,9 +44,9 @@ pipeline {
string(name: 'QUEST_SNAPSHOT_TAGS', defaultValue: '', string(name: 'QUEST_SNAPSHOT_TAGS', defaultValue: '',
description: "restic tags picking the stock Quest build, comma = AND. Empty: ${QUEST_DEFAULT_TAGS}") description: "restic tags picking the stock Quest build, comma = AND. Empty: ${QUEST_DEFAULT_TAGS}")
string(name: 'PC_REPO', defaultValue: '', string(name: 'PC_REPO', defaultValue: '',
description: 'PC restic repository folder on the FTP. Empty: ARCHIVE_PC_REPO from the Jenkins environment.') description: 'PC restic repository folder on the FTP. Empty: ARCHIVE_PC_REPO set on the agent.')
string(name: 'QUEST_REPO', defaultValue: '', string(name: 'QUEST_REPO', defaultValue: '',
description: 'Quest restic repository folder on the FTP. Empty: ARCHIVE_QUEST_REPO from the Jenkins environment.') description: 'Quest restic repository folder on the FTP. Empty: ARCHIVE_QUEST_REPO set on the agent.')
booleanParam(name: 'LIST_SNAPSHOTS', defaultValue: false, booleanParam(name: 'LIST_SNAPSHOTS', defaultValue: false,
description: 'Only list the snapshots matching the tags in both repositories (to find a build), then stop.') description: 'Only list the snapshots matching the tags in both repositories (to find a build), then stop.')
} }
@@ -88,8 +87,8 @@ pipeline {
"PC_TAGS=${params.PC_SNAPSHOT_TAGS}", "QUEST_TAGS=${params.QUEST_SNAPSHOT_TAGS}"]) { "PC_TAGS=${params.PC_SNAPSHOT_TAGS}", "QUEST_TAGS=${params.QUEST_SNAPSHOT_TAGS}"]) {
sh ''' sh '''
export RESTIC_CACHE_DIR="$DORKNET_CACHE/restic" export RESTIC_CACHE_DIR="$DORKNET_CACHE/restic"
pc="${PC_REPO_OVERRIDE:-${ARCHIVE_PC_REPO:?set ARCHIVE_PC_REPO on Jenkins or PC_REPO on the run}}" pc="${PC_REPO_OVERRIDE:-${ARCHIVE_PC_REPO:?set ARCHIVE_PC_REPO on the agent or PC_REPO on the run}}"
quest="${QUEST_REPO_OVERRIDE:-${ARCHIVE_QUEST_REPO:?set ARCHIVE_QUEST_REPO on Jenkins or QUEST_REPO on the run}}" quest="${QUEST_REPO_OVERRIDE:-${ARCHIVE_QUEST_REPO:?set ARCHIVE_QUEST_REPO on the agent or QUEST_REPO on the run}}"
echo "== PC: $pc"; archive-restore list "$pc" "$PC_TAGS" echo "== PC: $pc"; archive-restore list "$pc" "$PC_TAGS"
echo "== Quest: $quest"; archive-restore list "$quest" "$QUEST_TAGS" echo "== Quest: $quest"; archive-restore list "$quest" "$QUEST_TAGS"
''' '''
@@ -113,7 +112,7 @@ pipeline {
withEnv(["REPO_OVERRIDE=${params.PC_REPO}", "TAGS=${params.PC_SNAPSHOT_TAGS ?: PC_DEFAULT_TAGS}"]) { withEnv(["REPO_OVERRIDE=${params.PC_REPO}", "TAGS=${params.PC_SNAPSHOT_TAGS ?: PC_DEFAULT_TAGS}"]) {
sh(returnStdout: true, script: ''' sh(returnStdout: true, script: '''
export RESTIC_CACHE_DIR="$DORKNET_CACHE/restic" export RESTIC_CACHE_DIR="$DORKNET_CACHE/restic"
repo="${REPO_OVERRIDE:-${ARCHIVE_PC_REPO:?set ARCHIVE_PC_REPO on Jenkins or PC_REPO on the run}}" repo="${REPO_OVERRIDE:-${ARCHIVE_PC_REPO:?set ARCHIVE_PC_REPO on the agent or PC_REPO on the run}}"
archive-restore restore "$repo" "$TAGS" "$DORKNET_CACHE/pc" --exclude /.DepotDownloader archive-restore restore "$repo" "$TAGS" "$DORKNET_CACHE/pc" --exclude /.DepotDownloader
''').trim().readLines().last() ''').trim().readLines().last()
} }
@@ -141,7 +140,7 @@ pipeline {
withEnv(["REPO_OVERRIDE=${params.QUEST_REPO}", "TAGS=${params.QUEST_SNAPSHOT_TAGS ?: QUEST_DEFAULT_TAGS}"]) { withEnv(["REPO_OVERRIDE=${params.QUEST_REPO}", "TAGS=${params.QUEST_SNAPSHOT_TAGS ?: QUEST_DEFAULT_TAGS}"]) {
sh(returnStdout: true, script: ''' sh(returnStdout: true, script: '''
export RESTIC_CACHE_DIR="$DORKNET_CACHE/restic" export RESTIC_CACHE_DIR="$DORKNET_CACHE/restic"
repo="${REPO_OVERRIDE:-${ARCHIVE_QUEST_REPO:?set ARCHIVE_QUEST_REPO on Jenkins or QUEST_REPO on the run}}" repo="${REPO_OVERRIDE:-${ARCHIVE_QUEST_REPO:?set ARCHIVE_QUEST_REPO on the agent or QUEST_REPO on the run}}"
archive-restore restore "$repo" "$TAGS" "$DORKNET_CACHE/quest" archive-restore restore "$repo" "$TAGS" "$DORKNET_CACHE/quest"
''').trim().readLines().last() ''').trim().readLines().last()
} }
@@ -187,7 +186,7 @@ pipeline {
} }
cleanup { cleanup {
// Runs after archiving. Keeps the restic cache and restored stock // Runs after archiving. Keeps the restic cache and restored stock
// builds ($DORKNET_CACHE in jenkins_home, outside the workspace); drops // builds ($DORKNET_CACHE on the agent, outside the workspace); drops
// the workspace's build output and scratch. // the workspace's build output and scratch.
cleanWs(deleteDirs: true, notFailBuild: true) cleanWs(deleteDirs: true, notFailBuild: true)
} }
+13 -12
View File
@@ -1,7 +1,7 @@
# DorkNet Jenkins: the stock Jenkins image plus everything the PC and Quest # DorkNet build agent: a Jenkins inbound agent with everything the PC and
# client builds need, so the job (the Jenkinsfile at the repo root) runs right # Quest client builds need. It runs next to Jenkins on Coolify, connects to
# on the controller. Jobs, plugins and settings stay in the jenkins_home # the controller over WebSocket, and runs the jobs labelled "dorknet-builder"
# volume; only the image changes. # (the Jenkinsfile at the repo root). Jenkins itself stays on the stock image.
# #
# PC Python + .NET SDK tools/build_pc.py # PC Python + .NET SDK tools/build_pc.py
# Quest Python/LIEF + JDK + Android NDK, build-tools, apktool # Quest Python/LIEF + JDK + Android NDK, build-tools, apktool
@@ -9,13 +9,13 @@
# Builds restic + rclone stock builds restored from the restic # Builds restic + rclone stock builds restored from the restic
# archive on the FTP (archive-restore.sh) # archive on the FTP (archive-restore.sh)
# #
# Java 25: Jenkins supports it until Sep 2029 (Java 21 only until Sep 2027).
# Keep the agent's Java the same major version as the controller's.
#
# See ci/jenkins/README.md. # See ci/jenkins/README.md.
# Match this to the Jenkins image your Dokploy service runs today, so the ARG AGENT_IMAGE=jenkins/inbound-agent:latest-jdk25
# upgrade keeps the same Jenkins version and Java: e.g. FROM ${AGENT_IMAGE}
# --build-arg JENKINS_IMAGE=jenkins/jenkins:2.492.3-lts-jdk17
ARG JENKINS_IMAGE=jenkins/jenkins:lts-jdk17
FROM ${JENKINS_IMAGE}
USER root USER root
@@ -73,9 +73,10 @@ RUN wget -q https://github.com/restic/restic/releases/download/v${RESTIC}/restic
COPY archive-restore.sh /usr/local/bin/archive-restore COPY archive-restore.sh /usr/local/bin/archive-restore
RUN chmod +x /usr/local/bin/archive-restore RUN chmod +x /usr/local/bin/archive-restore
# Restored stock builds and the restic cache live in the persistent # Restored stock builds and the restic cache. Mount a volume here so each
# jenkins_home volume, so each multi-GB build is downloaded once. # multi-GB stock build is downloaded once, not after every restart.
ENV DORKNET_CACHE=/var/jenkins_home/dorknet-cache ENV DORKNET_CACHE=/home/jenkins/dorknet-cache
RUN mkdir -p ${DORKNET_CACHE} && chown jenkins:jenkins ${DORKNET_CACHE}
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
DOTNET_NOLOGO=1 DOTNET_NOLOGO=1
+59 -29
View File
@@ -3,12 +3,13 @@
The `Jenkinsfile` at the repo root builds the DorkNet clients on your existing The `Jenkinsfile` at the repo root builds the DorkNet clients on your existing
Jenkins. Jenkins.
It runs right on the Jenkins container, so that container's image needs the The builds run on a **build agent**, not on Jenkins itself:
build toolchain: Python with LIEF, the .NET SDK, the Android NDK and build-tools, - **The agent:** a container built from `ci/jenkins/Dockerfile`, which is the
apktool, restic and rclone. `ci/jenkins/Dockerfile` is the stock Jenkins inbound agent plus Python with LIEF, the .NET SDK, the Android NDK
Jenkins image with that toolchain added. Your jobs, plugins and settings live and build-tools, apktool, restic and rclone. It runs on Coolify next to
in the `jenkins_home` volume, not in the image, so switching images leaves Jenkins and connects to it over WebSocket on Docker's internal network. The
them alone. job runs on it through the `dorknet-builder` label.
- **Jenkins itself:** stays on the stock image and runs no builds.
| Platform | Output (Jenkins artifacts) | How | | Platform | Output (Jenkins artifacts) | How |
|---|---|---| |---|---|---|
@@ -37,8 +38,8 @@ The job defaults to the builds this branch targets: Steam manifest
build can be chosen per run. Tick **LIST_SNAPSHOTS** to print the snapshots build can be chosen per run. Tick **LIST_SNAPSHOTS** to print the snapshots
matching a filter without building anything. matching a filter without building anything.
A restored snapshot is cached in `jenkins_home/dorknet-cache`, so each stock A restored snapshot is cached on the agent's `dorknet_cache` volume, so each
build is downloaded once. stock build is downloaded once.
A Quest build needs a resolved hook table for its versionCode, committed under A Quest build needs a resolved hook table for its versionCode, committed under
`DorkNet.Quest/targets/`. Only `20230317-2128210.json` exists today. The build `DorkNet.Quest/targets/`. Only `20230317-2128210.json` exists today. The build
@@ -57,32 +58,61 @@ Resolve a table for another build locally with `build.py build --dump-cs`.
## Setup ## Setup
### 1. Switch your Jenkins to this image ### 1. Jenkins itself
On your Jenkins service in Dokploy: Jenkins runs as a Coolify service from a Compose file:
1. **Build from this repo.** - **Use the Java 25 image**, e.g. `jenkins/jenkins:latest-jdk25`, or
- For an Application service: provider GitHub, branch `march-2023-03-21`, `2.587-jdk25` to pin. Jenkins supports Java 25 until September 2029, Java 21
build type Dockerfile, Dockerfile path `ci/jenkins/Dockerfile`, build only until September 2027, and Java 17 not at all. Keep the agent on the same
context `ci/jenkins`. Java major version; its image defaults to Java 25.
- For a Compose service: change its `jenkins` service to `build:` from - **Add `restart: unless-stopped`** to the `jenkins` service. A restart from
`ci/jenkins` as in `ci/jenkins/docker-compose.yml`. the Jenkins UI exits the container with code 5 and relies on Docker to
- Either way, **keep the same volume on `/var/jenkins_home`**. start it again. Without a restart policy it stays stopped.
2. **Match the Jenkins version.** Set the build argument `JENKINS_IMAGE` to the - **Turn on "Connect to predefined network"** (Advanced) so the agent can
image the service runs today (e.g. `jenkins/jenkins:2.492.3-lts-jdk17`). reach Jenkins by its container name, `jenkins-<service uuid>`.
The default is `jenkins/jenkins:lts-jdk17`. - **Run no builds on it:** **Manage Jenkins → Nodes → Built-In Node →
3. **Add the environment:** Configure**, set **Number of executors** to `0`.
### 2. The agent node
**Manage Jenkins → Nodes → New Node**, a permanent agent:
| Setting | Value |
|---|---|
| Name | `dorknet-builder` |
| Labels | `dorknet-builder` |
| Remote root directory | `/home/jenkins/agent` |
| Launch method | Launch agent by connecting it to the controller, with **Use WebSocket** ticked |
| Number of executors | 1 |
Save, open the node, and copy its **secret**.
### 3. Deploy the agent on Coolify
1. **Create a Docker Compose application** from this repository: build pack
Docker Compose, base directory `/`, compose location
`/ci/jenkins/docker-compose.yml`. It needs no domain. Under Advanced, turn
on **Connect to predefined network**.
2. **Set its environment:**
| Variable | Value | | Variable | Value |
|---|---| |---|---|
| `JENKINS_URL` | Jenkins' **internal** address, `http://jenkins-<service uuid>:8080/`. Not the public URL, which is behind Cloudflare Access. |
| `JENKINS_SECRET` | the node secret from step 2 |
| `JENKINS_AGENT_NAME` | optional, default `dorknet-builder` |
| `ARCHIVE_PC_REPO` | Steam repository folder on the FTP, e.g. `Rec Room Versions/steam-v2` (plain text, not URL-encoded) | | `ARCHIVE_PC_REPO` | Steam repository folder on the FTP, e.g. `Rec Room Versions/steam-v2` (plain text, not URL-encoded) |
| `ARCHIVE_QUEST_REPO` | Quest repository folder, e.g. `Rec Room Versions/quest-v2` | | `ARCHIVE_QUEST_REPO` | Quest repository folder, e.g. `Rec Room Versions/quest-v2` |
| `ARCHIVE_FTP_TLS` | `explicit` (default), `implicit` or `none` | | `ARCHIVE_FTP_TLS` | `explicit` (default), `implicit` or `none` |
4. **Redeploy.** The first image build downloads the Android NDK (~1.5 GB) and 3. **Deploy.** The first image build downloads the Android NDK (~1.5 GB) and
takes a while. takes a while. The node shows as connected in Jenkins once the agent is up.
### 2. Plugins Both Jenkins and the agent join the server's predefined Coolify network, so
the agent reaches Jenkins by container name. Check the name under the Jenkins
service's containers before using it.
### 4. Plugins
The job needs these plugins: The job needs these plugins:
@@ -96,7 +126,7 @@ The job needs these plugins:
Most are part of the suggested set. Most are part of the suggested set.
### 3. Credentials ### 5. Credentials
Add these under **Manage Jenkins → Credentials**, with exactly these IDs: Add these under **Manage Jenkins → Credentials**, with exactly these IDs:
@@ -115,7 +145,7 @@ Add these under **Manage Jenkins → Credentials**, with exactly these IDs:
different key means every player has to uninstall before installing a CI different key means every player has to uninstall before installing a CI
build. build.
### 4. The job ### 6. The job
1. Create a **New Item → Pipeline** (e.g. `dorknet-client`). 1. Create a **New Item → Pipeline** (e.g. `dorknet-client`).
2. Under **Pipeline**, choose **Pipeline script from SCM**: 2. Under **Pipeline**, choose **Pipeline script from SCM**:
@@ -126,7 +156,7 @@ build.
4. Run it once with **Build** so Jenkins learns the parameters. Later runs 4. Run it once with **Build** so Jenkins learns the parameters. Later runs
offer **Build with Parameters**. offer **Build with Parameters**.
### 5. GitHub webhook ### 7. GitHub webhook
In the repository's **Settings → Webhooks**: In the repository's **Settings → Webhooks**:
@@ -136,5 +166,5 @@ In the repository's **Settings → Webhooks**:
### Disk ### Disk
The restored stock builds are cached in `jenkins_home/dorknet-cache`, several The agent's `dorknet_cache` volume holds the restored stock builds, several GB
GB each. Jenkins keeps the artifacts of the last 3 runs. each. Jenkins keeps the artifacts of the last 3 runs, on its own volume.
+18 -18
View File
@@ -1,31 +1,31 @@
# Reference for the Jenkins service on Dokploy, built from this directory's # DorkNet build agent on Coolify. Deploy as a Docker Compose application from
# Dockerfile (the stock Jenkins image plus the client build toolchain). # this repository with compose location /ci/jenkins/docker-compose.yml and
# # "Connect to predefined network" on. It needs no domain: it dials out to
# Use it on your EXISTING Jenkins service rather than as a second Jenkins. # Jenkins over WebSocket on Docker's internal network. Set the variables in
# The data volume must be the one Jenkins uses today, or Jenkins starts empty. # the resource's Environment Variables; see ci/jenkins/README.md.
# Point `volumes:` at that volume (and its name under the top-level
# `volumes:`, marked `external: true`), or keep this service's name and volume
# if they already match. See ci/jenkins/README.md.
services: services:
jenkins: agent:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args:
# The image your Jenkins runs today, so the Jenkins version and Java
# stay the same.
JENKINS_IMAGE: ${JENKINS_IMAGE:-jenkins/jenkins:lts-jdk17}
restart: unless-stopped restart: unless-stopped
expose:
- "8080"
environment: environment:
# Jenkins as seen from inside Docker (e.g. http://<jenkins container
# name>:8080), not the public URL, which is behind Cloudflare Access.
JENKINS_URL: ${JENKINS_URL:?set JENKINS_URL to Jenkins' internal address}
JENKINS_AGENT_NAME: ${JENKINS_AGENT_NAME:-dorknet-builder}
JENKINS_SECRET: ${JENKINS_SECRET:?set JENKINS_SECRET to the node secret}
JENKINS_WEB_SOCKET: "true"
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
# Where the stock builds live in the archive (restic repository folders # Where the stock builds live in the archive (restic repository folders
# on the FTP), as plain text. Overridable per run. # on the FTP, plain text). Overridable per run.
ARCHIVE_PC_REPO: ${ARCHIVE_PC_REPO:-} ARCHIVE_PC_REPO: ${ARCHIVE_PC_REPO:-}
ARCHIVE_QUEST_REPO: ${ARCHIVE_QUEST_REPO:-} ARCHIVE_QUEST_REPO: ${ARCHIVE_QUEST_REPO:-}
ARCHIVE_FTP_TLS: ${ARCHIVE_FTP_TLS:-explicit} ARCHIVE_FTP_TLS: ${ARCHIVE_FTP_TLS:-explicit}
volumes: volumes:
- jenkins_home:/var/jenkins_home - dorknet_cache:/home/jenkins/dorknet-cache
- agent_work:/home/jenkins/agent
volumes: volumes:
jenkins_home: dorknet_cache:
agent_work: