Skip to content

Repository files navigation

dcs-linux-installer

Install DCS World Standalone on Linux, and keep it working.

Status: pre-alpha. Every command works — check, install, launch, patch, verify and report — including finding the DCS installs you already have, installing DCS itself through its own updater, and launching it to confirm it actually flies. The design is settled and the work is broken down in issue #1. There is no usable release yet. Everything below describes the intended tool.


What this is

Getting DCS running on Linux is not one problem, it is a pile of small ones: which Proton build, which winetricks verbs, which DLL overrides, and a handful of fixes for bugs that only show up on Linux. The community has solved all of it, but the answers are scattered across forum threads, wiki pages and half-maintained gists — and they go stale.

Worse, DCS's own updater overwrites those fixes every time it runs. Getting the game working once is achievable. Keeping it working is the part that wears people down.

dcs-linux-installer collects the known-good setup into one tool, applies the fixes, notices when a DCS update has reverted them, and puts them back.

It is aimed at anyone running Linux, not just one distro. Immutable systems like Bazzite and SteamOS are first-class.

What it will do

  • Install DCS World Standalone into a Wine prefix built with a pinned GE-Proton via umu-launcher
  • Check your system and tell you exactly what is missing, with fixes that suit your distro
  • Find DCS installs you already have — from Lutris, Heroic, Steam, or this tool
  • Patch the known Linux-specific breakages, and re-apply them after DCS updates revert them
  • Verify that DCS actually works, by reading its logs rather than assuming a process that started is a process that works
  • Report a redacted diagnostics bundle you can paste into a bug report
  • Head tracking — TrackIR and opentrack setup and permission checks

Not in scope

HOTAS configuration, VR, and SRS. Each is a large project in its own right. This tool installs the game and keeps it running.

Two things worth knowing up front

Your multiplayer access is protected by default

DCS has an Integrity Check that hashes game files. Servers running pure-client enforcement reject clients whose files have been modified — and some of the necessary Linux fixes modify exactly those files.

So every patch declares whether it carries that risk. Safe patches apply by default. Anything that could cost you multiplayer access requires an explicit opt-in and says so plainly before it writes anything. Every patch is fully revertible back to a state that passes Integrity Check.

No silent trade of your multiplayer access for a single-player bug fix.

The prefix is disposable, the download is not

DCS is over 150 GB. Most setups put it inside the Wine prefix, which means Wine's most common repair — delete the prefix and rebuild it — also deletes the download.

Here the game lives on a path you choose, outside the prefix, mapped in. The prefix stays small enough to throw away and rebuild in seconds, and you can put the game on whichever drive has room.

Planned usage

# One-shot, no install
uvx --from dcs-linux-installer dcs-linux check

# Or install the command
uv tool install dcs-linux-installer

dcs-linux check             # is this machine ready? what DCS installs exist?
dcs-linux install           # build the prefix, then hand off to the DCS updater
dcs-linux shortcut          # add a KDE/GNOME launcher for a prepared install
dcs-linux launch            # start a prepared install without judging it
dcs-linux patch             # apply the Linux fixes (IC-safe ones by default)
dcs-linux verify            # launch DCS and confirm it actually works
dcs-linux report            # diagnostics bundle for a bug report

Requires uv. It installs to your home directory without root, so this works on immutable distros too.

dcs-linux check

The first command to run. It needs no DCS install — it is the first thing to run — and reports a pass/fail row per check, each failure carrying the command that fixes it, chosen for your distro:

dcs-linux check          # exits non-zero if anything blocking is wrong
dcs-linux --json check   # same results, machine-readable

It reports your distro and whether its base system is immutable, your GPU and driver version, umu-launcher and the available GE-Proton builds, missing external tools, free disk space against what a DCS install actually needs, and whether your filesystem supports reflink snapshots.

Once DCS is installed it also checks the things that break it in ways the logs never mention — DLSS upscaling, the missing Segoe fonts the AH-64D needs, d3dcompiler_47, and whether the game and your saved games really do live outside the disposable prefix.

Head tracking

check also reports on head tracking, because on Linux it usually fails for a boring reason: the TrackIR is plugged in and your account is not allowed to open it.

┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃      ┃ Check                ┃ Result                                                             ┃
┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ warn │ Head tracker         │ TrackIR 5 at /dev/bus/usb/001/007; no udev rule installed, and     │
│      │                      │ this user cannot open the device, so nothing will move in the      │
│      │                      │ cockpit                                                            │
│      │                      │ → printf '%s\n' 'SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device",     │
│      │                      │ ATTRS{idVendor}=="131d", TAG+="uaccess"' | sudo tee                │
│      │                      │ /etc/udev/rules.d/70-trackir.rules && sudo udevadm control         │
│      │                      │ --reload-rules && sudo udevadm trigger   # then unplug and replug  │
│      │                      │ the tracker                                                        │
│ warn │ opentrack            │ a head tracker is connected but opentrack is not installed, so DCS │
│      │                      │ has nothing feeding it head movement                               │
│      │                      │ → flatpak remote-add --if-not-exists flathub                       │
│      │                      │ https://dl.flathub.org/repo/flathub.flatpakrepo && flatpak install │
│      │                      │ flathub io.github.opentrack.opentrack                              │
│ warn │ Head tracking in DCS │ nothing in the prefix points DCS at an NPClient bridge, so head    │
│      │                      │ movement will not reach the cockpit                                │
│      │                      │ → in opentrack set Output to 'Wine' and point it at                │
│      │                      │ ~/dcs-linux/prefix                                                 │
└──────┴──────────────────────┴────────────────────────────────────────────────────────────────────┘

Installing a udev rule needs root, so the tool prints the command and never runs it. None of these rows can fail the check — DCS flies without head tracking — and with no tracker connected and no opentrack installed, all three simply skip.

Head tracking is the only peripheral in scope. HOTAS, joysticks and throttles are not, and detection is limited to the NaturalPoint vendor id so it stays that way.

Paths default to ~/dcs-linux, ~/.cache/dcs-linux/toolchain and ~/.local/state/dcs-linux; override with DCS_LINUX_ROOT, DCS_LINUX_TOOLCHAIN and DCS_LINUX_STATE (which also honours XDG_STATE_HOME). DCS_LINUX_GAME moves the game directory on its own — the same choice install --game-dir makes, but remembered, so every later command measures the right drive.

The DCS installs you already have

check also lists every DCS install on the machine — from Lutris, Heroic, Steam, or this tool — with its game directory, prefix, Proton build, edition and DCS version:

2 DCS installs
┏━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃   ┃ ID       ┃ Install                                                     ┃
┡━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ → │ 778d8145 │ steam · Steam edition · DCS 2.9.28.26385 · GE-Proton9-20    │
│   │          │ game:   /mnt/games/SteamLibrary/steamapps/common/DCSWorld   │
│   │          │ prefix: /mnt/games/SteamLibrary/steamapps/compatdata/223750 │
│   │ 7976590f │ lutris · Standalone edition · DCS 2.9.30.1000 · ge-proton   │
│   │          │ game:   /games/DCS World                                    │
│   │          │ prefix: /games/prefixes/dcs                                 │
└───┴──────────┴─────────────────────────────────────────────────────────────┘

Nothing is written while discovering — your launchers can be running.

The ID is how you name one install to a command, and it is derived from the game directory, so it does not change when the prefix is rebuilt:

dcs-linux check --install 7976590f   # report on that install
dcs-linux check --install 7976       # any unambiguous prefix works

With one install found, or one of ours, it is used automatically.

dcs-linux install

Two phases in one command. First everything DCS needs except DCS: the umu-launcher zipapp, a pinned GE-Proton build, the Wine prefix, the winetricks verbs, and the mapping that keeps your game and your login outside that prefix. Then the handoff to the DCS updater, where you log in and choose modules.

dcs-linux install                          # into the default layout
dcs-linux install --game-dir /mnt/big/DCS  # put the 150 GB somewhere else
dcs-linux install --rebuild                # throw the prefix away and build it again
dcs-linux install --prefix-only            # stop at a ready prefix, download nothing

It prints one line per step, and re-running it is safe: an up-to-date prefix is left alone and only the mapping is re-asserted.

The toolchain versions are pinned, not resolved — the umu and GE-Proton builds are the exact ones this project flew to a working mission, so a bug report always names a reproducible pair. They are recorded in .dcs-linux.json inside the prefix, along with the verbs and the launch environment.

--rebuild is the repair everything else rests on. It deletes the prefix and nothing else: the game directory and Saved Games live outside it, so your download and your ED login survive. If either one is somehow inside the prefix, the rebuild refuses rather than destroying it.

Before any download starts, install re-runs the checks that it cannot itself fix — no GPU, a missing external tool, not enough disk space, a game directory inside the prefix — and stops on those. Everything else check calls blocking on a fresh machine (no umu, no Proton, no prefix) is exactly what this command is about to create.

vcrun2019 is refused if you ask for it with --verb: it causes a system-wide RAM leak. Use vcrun2015 or vcrun2022.

The DCS updater handoff

The updater needs an Eagle Dynamics account and a module selection through its own GUI, so that part is yours. install prepares everything around it, opens it, and picks the install up afterwards.

You have to fetch DCS_World_web.exe yourself — the download page needs a browser session, so it cannot be fetched for you. Put it in the toolchain directory (~/.cache/dcs-linux/toolchain) or pass --installer PATH. It is checked before it is run, and the hash of what ran is recorded.

In the updater, set the install path to D:\ — that is your game directory, mapped in — and leave torrent/P2P enabled, which is roughly ten times faster. C:\ would put the whole install inside the disposable prefix, so install refuses to open the updater at all unless the mapping is in place.

The download is 150 GB and up, measured in hours. Interrupting is safe: close the terminal, reboot, and run dcs-linux install again — the updater resumes where it stopped, and nothing is recorded until the install is finished. After that the install is registered, so check, launch, patch and verify find it without you repeating --game-dir.

When an explicit --game-dir adopts an existing install, KDE and GNOME users are offered a desktop shortcut after registration succeeds. Scripts can choose without a prompt by passing --shortcut or --no-shortcut.

Once it is done, run dcs-linux patch apply — the Linux fixes are what turn an install that starts into one that flies.

dcs-linux launch

Start a complete install whose prefix was prepared by this tool:

dcs-linux launch                    # the single eligible install
dcs-linux launch --install 7976     # choose by ID or game path

It uses the pinned umu and GE-Proton runtime, the mapped game directory and Saved Games, the Integrity Check-safe launch environment, and --no-launcher. It waits for the complete umu/Proton/Wine process tree and cleans it up if interrupted. The DCS exit code is reported but not treated as proof that the game worked; use verify when you want the log judged.

dcs-linux shortcut

Create the same KDE/GNOME desktop launcher later, without re-running install:

dcs-linux shortcut                    # the single prepared install
dcs-linux shortcut --install 7976     # choose by ID or game path

The launcher runs dcs-linux launch --install ID, so it keeps using the prepared prefix and durable mappings. It downloads DCS World's pinned multi-resolution icon (256, 48, 32 and 16 pixels) from Steam's CDN, identified through SteamDB, stores it under your XDG data directory, and assigns the local image to the launcher. Re-running the command does not download or create a duplicate when both files are already present.

dcs-linux verify

The question the rest of this is for: does it actually work?

dcs-linux verify              # launch DCS, then judge the log it wrote
dcs-linux verify --no-launch  # judge the last run, starting nothing

It opens DCS, tells you to fly to a real mission rather than stopping at the main menu, waits for you to quit, and then reads dcs.log. A process that starts proves very little here: DCS reaches the menu with no symbols in the Apache, with garbage MFDs, or having never authorized — and exits cleanly through all three. So verify reports per issue, each with its fix and the patch that applies it:

ok   Launch          DCS closed with exit code 0
ok   dcs.log         ~/dcs-linux/saved-games/DCS/Logs/dcs.log, opened 2026-08-02 21:30:03
ok   Authorization   ED authorization succeeded
FAIL Fonts           a font was requested by no name at all — the missing Segoe font …
                     → dcs-linux patch apply segoe-fonts
FAIL Crash           DCS crashed with an access violation in CockpitBase.dll
warn Shaders         DCS could not find a precompiled shader and rebuilt it …
ok   Session         DCS shut down cleanly
ok   Upscaling       OFF

Authorization failures caused by a wrong system clock are named as such rather than reported as a generic error, because nothing about the symptom suggests the clock. And a clean log is not a clean run: DLSS flicker never reaches dcs.log at all, so the static rule that catches it is applied here too.

check stays the fast answer and still launches nothing. What verify finds is also in the report bundle, under Last run.

dcs-linux report

A diagnostics bundle, as markdown, ready to paste into an issue or a forum thread:

dcs-linux report                  # to stdout
dcs-linux report --install 7976   # about one install

It carries the tool version, your distro and kernel, GPU and driver, umu and the Proton builds present, every install found, what verify makes of the last run, the graphics block of options.lua, and the parts of dcs.log worth reading — the header, any known-fatal signature, errors with the known-benign noise filtered out, and the tail.

It is meant to be safe to post in public: home and removable-drive paths, wine profile names, email addresses, Steam account ids, GUIDs and routable IP addresses are all replaced, keeping the shape of the path so it still reads as one. The ED credential (Saved Games/DCS/Config/authdata.bin) is never read at all. --no-redact turns redaction off, for a bundle you are keeping to yourself.

Redaction is pattern-based, so give the output a glance before you post it — and open an issue if something identifying gets through.

It is bounded — a healthy DCS log is 150 KB with several hundred ERROR lines, so it is excerpted rather than dumped — and it works on a machine where everything is broken and nothing is installed, which is exactly when it is needed.

dcs-linux patch

The Linux fixes, applied, reverted, and put back after a DCS update undoes them:

dcs-linux patch                # what is available, and what is applied
dcs-linux patch apply          # apply everything applicable
dcs-linux patch revert         # put the install back exactly as it was
dcs-linux patch apply segoe-fonts   # or one patch by name
dcs-linux patch clear-shader-cache  # delete the compiled shaders

DCS_updater overwrites patched files on every update and repair, so a patch is never "done". Each applied file is remembered by its hash, so the tool can tell you a fix has been undone — check reports that as a failure, and patch apply puts it back. Applying something already applied is a no-op, so running it after every DCS update is the intended habit.

Backups and state live in ~/.local/state/dcs-linux/<install id>/, outside the install, because DCS_updater repair deletes files ED's manifest does not list and would otherwise destroy the only pristine copy. Revert restores exactly what was there before — including a file DCS itself later replaced.

Nothing is written unless the whole fix can be assembled: a patch that cannot find what it needs says why and touches nothing.

Patch Integrity Check What it does
segoe-fonts safe puts a locally installed sans font into the prefix under the three Segoe names the AH-64D needs. Prefix only, so no hashed game file is involved
voice-chat ⚠️ risky comments the voice-chat entries out of optionsDb.lua — a game file DCS hashes
mfd-textures ⚠️ risky re-encodes the AH-64D MFD and sight textures with ImageMagick — game files DCS hashes

A risky patch is never swept up by a bare patch apply, and --allow-ic-risk does not widen one: it consents to a patch you named, and the multiplayer cost is printed either way. check tells you whether the install currently carries such a modification, and patch revert gives multiplayer back.

Both risky patches are workarounds for symptoms that were not reproduced on 2.9.28.26385 — voice chat loads clean, and the sight renders correctly once shaders finish compiling. They are here for anyone who does hit them; on a current install they will tell you there is nothing to fix rather than modify anything.

clear-shader-cache is maintenance rather than a patch: it deletes DCS's compiled shaders in Saved Games, which DCS rebuilds on the next launch (that launch takes several minutes). Nothing to back up, nothing to revert, and always Integrity Check safe.

Design

Area Decision
Runtime Pinned GE-Proton via umu-launcher
Layout Game outside the prefix, mapped in; prefix small and disposable
Install flow Prepare prefix → hand off to the DCS updater GUI → patch → launch or verify
Discovery Adopts existing Lutris / Heroic / Steam installs, not just its own
Patches Backup plus state file, matched by content pattern rather than line number
Patch state Stored in ~/.local/state/dcs-linux/, outside the install, where DCS_updater repair cannot delete it
Multiplayer Per-patch Integrity Check risk; risky patches opt-in only
Interface CLI subcommands with rich output, --json and --no-color, non-interactive flags throughout
Language Python, managed by uv

Full reasoning is in issue #1.

Contributing

The tool targets many distros, GPUs and launcher layouts, but is developed on one machine. Bug reports are how coverage happens — the output of dcs-linux report is the most useful thing you can attach.

Work is tracked as issues under #1. Anything labelled ready-for-agent with no open blockers is available to pick up.

Running from a checkout

The package is not on PyPI yet, so the two commands under Planned usage do not work. From a clone, uv run builds the environment from uv.lock and runs the command without putting anything on your PATH:

uv run dcs-linux check

Source edits take effect immediately — it is an editable install. --version is the one exception: the number is baked at build time, so it stays at whatever the last sync saw. Run uv sync --reinstall-package dcs-linux-installer when the number has to be right; the behaviour is current either way.

uv run --directory <clone> dcs-linux … works from any directory, and uvx --from . dcs-linux … builds a throwaway wheel from local source — the closest thing to what a user gets from PyPI. There is no python -m dcs_linux.

Every command defaults to ~/dcs-linux, so on a machine whose DCS lives elsewhere check reports no install found and skips every install-dependent row. Point it at a real one:

export DCS_LINUX_ROOT=/path/to/dcs          # expects prefix/, game/, saved-games/
export DCS_LINUX_TOOLCHAIN=/path/to/toolchain
uv run dcs-linux check

Doing that is worth more than it sounds. check and report write nothing, so they are safe to run against a real install at any time — and the first probe ever compared against a real prefix turned out to disagree with the hand-written fixture it was tested on.

What CI runs, in order:

uv run ruff check . && uv run ruff format --check .
uv run mypy
uv run pytest

Releasing

Push a version tag; that is the whole process.

git tag v0.1.0
git push origin v0.1.0

The version number comes from the tag itself, so the tag and the published package can never disagree. Uploads use PyPI trusted publishing — there is no API token in the repository or in CI. See ADR-0005.

Credit

This project stands on work the DCS Linux community did first:

Licence

MIT


Not affiliated with or endorsed by Eagle Dynamics. DCS World is their trademark.

About

Install DCS World Standalone on Linux, and keep it working across DCS updates

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages