Skip to content

Infrastructure Setup

Summary: The build infrastructure runs on a single Windows 11 machine with a GitHub Actions self-hosted runner. This document covers machine setup, directory structure, required secrets, and scaling considerations.

Table of Contents


Machine Specs

Component Value
OS Windows 11 Home
CPU i5-14400F (10 cores / 16 threads)
RAM 16 GB
UE Version 5.6
.NET 9.0.304
Git 2.45.1 + LFS 3.5.1

Directory Structure

C:\
├── Program Files\Epic Games\UE_5.6\   UE engine installation
├── Actions\                            GitHub Actions runner
│   └── _work\                          Runner workspace (builds happen here)
├── BuildArtifacts\                     Packaged build output
│   ├── 18\                             Build #18 (folder per run number)
│   │   └── Windows\                    Packaged game files
│   ├── 19\
│   └── ...                             Last 5 builds kept, older auto-deleted
├── SharedDDC\                          Derived Data Cache (shared across builds)
├── BPTCloudDir\                        BuildPatchTool chunk storage
├── Tools\
│   └── BuildPatchTool\                 Standalone BPT from Epic Dev Portal
│       └── Engine\Binaries\Win64\
│           └── BuildPatchTool.exe      v1.7.3
└── GameServer\                         Dev game server deployment (future)
    └── ProjectEternal\

Windows Defender Exclusions

These paths should be excluded from real-time scanning for build performance:

Path Why
C:\Program Files\Epic Games\UE_5.6\ Engine reads thousands of files during compile
C:\Actions\_work\ Build workspace with constant file I/O
C:\SharedDDC\ DDC cache has many small files
C:\BuildArtifacts\ Large packaged builds

Runner Installation

Setup Steps

  1. Download Windows x64 runner from repository Settings > Actions > Runners > New
  2. Extract to C:\Actions\
  3. Configure with labels:
    ./config.cmd --url https://github.com/oliwervik/Project-Eternal
      --token <TOKEN>
      --labels self-hosted,Windows,X64,UE5
      --name "eternal-build-01"
      --work C:\Actions\_work
    
  4. Install and start as Windows Service:
    ./svc.cmd install
    ./svc.cmd start
    

Runner Labels

Label Purpose
self-hosted Required by GitHub
Windows OS identifier
UE5 Marks machine as having UE installed

Important Notes

Consideration Detail
Windows 11 Home No Hyper-V or proper Remote Desktop
Must stay logged in Lock screen is fine, sign-out breaks the service
Remote access Use Tailscale or RustDesk for remote management

GitHub Secrets

Required Secrets

Secret Purpose Where to Find
REPO_PAT Git checkout with LFS access GitHub Settings > Developer Settings > Tokens
EGS_ORGANIZATION_ID Epic org identifier Dev Portal > Organization Settings
EGS_PRODUCT_ID Product identifier Dev Portal > Product Settings
EGS_ARTIFACT_ID Artifact identifier Dev Portal > Artifacts and Binaries
EGS_CLIENT_ID BPT authentication Dev Portal > Product Settings > BPT Credentials
EGS_CLIENT_SECRET BPT authentication Dev Portal > Product Settings > BPT Credentials
EGS_SANDBOX_ID Sandbox for label operations Dev Portal > Product Settings > Sandboxes
DISCORD_WEBHOOK_URL Build notifications Discord > Server Settings > Integrations > Webhooks

Common Pitfalls

Pitfall Prevention
Typo in secret name (e.g., ESG_ vs EGS_) Verify with gh secret list
BPT credentials vs EOS credentials BPT has its own Client ID/Secret separate from EOS
Secret set as Variable instead of Secret Check both tabs in GitHub Settings

Performance Considerations

RAM Constraints

Operation Typical RAM Usage
C++ Compilation (UBT) 8-12 GB
Cook (RunUAT) 10-14 GB
Cook + Package combined Up to 14 GB peak

16 GB RAM is tight. The machine will be slow during builds. Consider upgrading to 32 GB.

Disk Budget

Item Approximate Size
UE 5.6 engine ~60 GB
Project workspace ~15 GB (with LFS)
Packaged build (each) ~2-5 GB
5 retained builds ~10-25 GB
SharedDDC ~10-30 GB (grows over time)
BPT cloud dir ~5-10 GB
Total CI footprint ~100-150 GB

DDC (Derived Data Cache)

The SharedDDC at C:\SharedDDC\ persists across builds. First cook is slow (generates all derived data), subsequent cooks reuse cached data significantly faster.


Scaling Path

Phase When What
1 (current) Single machine One self-hosted runner, sequential builds
2 2nd machine available Add runner with same labels, GitHub distributes jobs
3 Need cloud burst Azure/AWS Windows VM as ephemeral runner
4 5+ devs, frequent builds Migrate to Horde with UBA distributed compilation

Re-enabling LFS Locking for a Team

Git LFS file-locking is currently OFF (solo dev — see CLAUDE.md › Git LFS File Locking). LFS storage stays on regardless; only the locking workflow is disabled. Re-enabling it for a multi-dev team means restoring 4 independent layers — miss one and files stay writable-but-unprotected or the editor still refuses to force checkout. Do not touch the filter=lfs entries in .gitattributes — those are storage, not locking.

The 4 Layers

# Layer Where Scope Action to re-enable
1 Server-side locks LFS server Shared Re-lock the files that should be protected (git lfs lock <path>). git lfs locks is the server-side source of truth for what is held.
2 lockable attribute .gitattributes (committed) Shared Restore lockable on the [attr]lock macro (line 1). *.uasset/*.umap reference that lock macro, so both patterns become read-only-until-checked-out again. This is the team-facing layer.
3 locksverify git config per-machine Per-machine Set lfs.<remote-url>/info/lfs.locksverify=true so pushes fail on edits to files locked by someone else.
4 UE Git plugin Saved/Config/WindowsEditor/SourceControlSettings.ini Per-machine Set [GitSourceControl.GitSourceControlSettings] UsingGitLfsLocking=True. Saved/ is gitignored, so every machine sets this itself. The editor rewrites this file on exit — flip it while the editor is CLOSED.

Clearing Read-Only Bits on Already-Checked-Out Files

Restoring the lockable attribute (layer 2) only affects future checkouts — files already on disk keep whatever read-only state they currently have. After re-enabling, sweep existing assets so their read-only bit matches the new locking regime (set read-only on unlocked files, writable on files you hold a lock for): iterate Content/**/*.uasset and *.umap and set the IsReadOnly attribute accordingly.

The same sweep is required when disabling locking. Removing the attribute did NOT clear the bit on already-checked-out files: 3375 stale read-only assets survived the 2026-05-25 disable and silently failed every package save/rename/redirector write until a full clear on 2026-07-07 (Get-ChildItem Content -Recurse -File | ? IsReadOnly | % { $_.IsReadOnly = $false }). Run that on every machine after flipping locking off (see learning stale-readonly-flags-fail-bulk-asset-saves).

Verification

  • git lfs locks returns the expected held locks (server truth, not local cache).
  • A fresh checkout of a *.uasset lands read-only on disk.
  • The editor shows the Check Out / lock workflow again.
  • A push touching a file locked by another user is rejected.

Related: reference_lfs_locking_disable (session brain), project_asset_repo_health.



Recent Changes

Date Change Impact
2026-07-03 Add "Re-enabling LFS Locking for a Team" runbook (4 layers + read-only bit sweep) Document how to restore locking when the team grows
2026-03-02 Created documentation Initial infrastructure docs
2026-02-27 Runner operational Self-hosted runner building and uploading successfully
2026-02-27 Standalone BPT installed C:\Tools\BuildPatchTool\ for EGS uploads