Skip to content

Lay out the Linux release as a Unix prefix, and update flat installs to it - #350

Closed
startupfoundry wants to merge 3 commits into
OpenCommunityEdition:mainfrom
OpenH1:linux-fhs-layout
Closed

startupfoundry wants to merge 3 commits into
OpenCommunityEdition:mainfrom
OpenH1:linux-fhs-layout

Conversation

@startupfoundry

Copy link
Copy Markdown

The Linux download becomes a prefix that packagers can copy into /usr as is, as ORION suggested on Discord with an AUR package in mind. The game also keeps working when it is just unpacked anywhere, and existing installs move over through the self-updater.

Layout (halo-linux-i686-<config>.zip):

bin/opence
lib/opence/libSDL3.so.0                         rpath $ORIGIN:$ORIGIN/../lib/opence (DT_RPATH kept)
share/opence/brokers.txt
share/applications/opence.desktop               desktop-file-validate passes
share/icons/hicolor/{16..512}/apps/opence.png   from the existing icon art (tools/app_icon.py)
share/doc/opence/                               the READMEs
share/licenses/opence/                          LICENSE.md and the bundled libraries' licences
  • bin/opence: the executable takes the project's name instead of /usr/bin/halo. Steam's window class follows the executable name, so it matches the desktop entry. Developer builds are still build/linux/halo.
  • Licences: all of them go in share/licenses/opence/, the Arch convention, rather than splitting them with share/doc.

Where the game finds its files (port/linux/src/install_layout.c):

  • The game's folder: if the executable is in bin/ with share/opence/ beside it, the folder above bin/ is the game's folder. Otherwise, as today, it is the executable's own folder.
  • Portable (unpacked anywhere, or a developer build): config.toml, maps/ and debug.txt stay in that folder, as now.
  • Installed: the folder can't be written, or the tree contains an empty share/opence/packaged, which a package adds.
    • Config goes in ~/.config/halo-linux/.
    • Maps, the log and saves go in ~/.local/share/halo-linux/, the save root existing installs already use.
    • The game never updates itself.
    • The marker is a file rather than a build option, so an AUR -bin package can repackage the release zip without rebuilding.
  • Windows, Android and the web are unchanged.

Updates and migration.

  • The constraint: the updaters already in players' hands ask for halo-linux-<config>.zip and only take a zip's top-level files. Given a tree they fail ("the download is not a zip file this build can read"), so those players could never update again.
  • So each release keeps halo-linux-<config>.zip for now: the same build, laid out flat.
    • Old updaters install that as before.
    • The new build runs fine flat.
    • At its next update it asks for halo-linux-i686-<config>.zip and moves the install to the tree. In place of halo it leaves a script that starts bin/opence, so Steam shortcuts keep working.
  • The new updater also:
    • refuses entry names that would leave the game's folder;
    • skips links;
    • keeps executable bits;
    • deletes replaced files at the next start (they are listed in update-old.txt).
  • For testing without GitHub: debug.update_from (HALO_UPDATE_FROM) updates from a local zip.
  • Dropping the flat zip: it can be dropped after a few releases. After that, players who skipped every transition release have to download once by hand.

Packaging.

  • tools/linux_package.py is the one place the layout lives. It is used by ci_build.py and the release job; the release job now zips with it because GitHub's artifacts lose the executable bit.
  • python tools/linux_package.py tree "$pkgdir/usr" lays out a source build.
  • A new "Packaging" section in port/linux/README.md covers what goes where, the packaged marker and the runtime dependencies. A package can leave out lib/opence to use the system's lib32-sdl3.

Test:

  • New tests: tools/test_linux_package.py, which also checks that the updater, ci_build.py and the workflow agree on both zip names; tools/test_install_layout.py; and a harness test of the updater's name checks, with negative controls. They pass, as do tools/test_linux_port.py and tools/harness (288).
  • tools/ci_build.py linux release built both zips.
  • The fresh tree, unpacked, started windowed from bin/opence.
  • Migration, run from local zips:
    • An install of the current main release was updated by main's own updater code through the new flat zip.
    • The new build then moved it to the tree.
    • It started from bin/opence and from the halo script.
    • It then updated tree to tree.
    • main's updater was confirmed to fail on the tree zip alone.
  • Installed cases: a read-only /usr-style prefix and a writable prefix with the packaged marker both started with nothing written beside the executable and no update attempt.
  • Not built here: Windows, Android and the web. Their paths are unchanged, and updater.c was syntax-checked with _WIN32, HALO_ANDROID and HALO_WEB. The workflow changes have not run on GitHub.

…to it

Packagers (an AUR package is planned) want the Linux download laid out as
a prefix they can copy to /usr. The release is now a tree,
halo-linux-i686-<config>.zip (the 32-bit x86 tree; halo-linux-x86_64 is another build):

    bin/opence
    lib/opence/libSDL3.so.0         (rpath $ORIGIN:$ORIGIN/../lib/opence, DT_RPATH kept)
    share/opence/brokers.txt
    share/applications/opence.desktop
    share/icons/hicolor/<n>x<n>/apps/opence.png   (16 to 512, tools/app_icon.py)
    share/doc/opence/               (the READMEs)
    share/licenses/opence/          (LICENSE.md and the carried licenses)

install_layout.c finds the game's files from its executable: bin/ beside
share/opence is a tree whose root is the game's folder; otherwise the
executable's folder is (build/linux/halo, the flat release). A portable
game keeps config.toml, maps/ and debug.txt in its folder, as before. An
installed one (a folder the player cannot write to, or a tree with
share/opence/packaged, which packages add) keeps them in
~/.config/halo-linux and ~/.local/share/halo-linux (the save root's
folder) and does not update itself. brokers.txt is the one beside
config.toml, else share/opence's. Windows, Android and the web are
unchanged.

Updates: the updaters already in players' hands take only a zip's
top-level files and ask for halo-linux-<config>.zip, so a tree under that
name would fail every update (checked: "the download is not a zip file
this build can read"). Releases therefore keep halo-linux-<config>.zip as
the same build laid out flat, which those updaters install as before. The
new updater asks for the tree's zip, unpacks folders (names checked: none
may leave the game's folder; links skipped; executables as the zip marks
them), and, run flat, moves the install to the tree: it puts a script that
starts bin/opence in place of halo, so Steam shortcuts keep working, and
deletes the flat files the tree moved. Replaced files are listed in
update-old.txt and deleted at the next start. debug.update_from
(HALO_UPDATE_FROM) updates from a local zip, for testing this without
GitHub.

The updater asks for halo-linux-i686-<config>.zip (UPDATE_PLATFORM
"linux-i686"); tools/test_linux_package.py checks that the updater,
ci_build.py and the workflow agree on both zips' names.

tools/linux_package.py lays out both (ci_build.py stages them; "tree" does
it for a source package) and zips them with their modes, which the
release job now uses, as GitHub's artifacts lose the executable bit.

Checked: the release built with tools/ci_build.py linux release; the tree
unpacked and started windowed; an install of origin/main's release updated
by origin/main's updater code (run against the local flat zip), then by
the new build to the tree, then started from bin/opence and from the halo
script, then updated tree to tree; the same old updater shown to fail on
the tree's zip alone; a read-only prefix and a writable one with
share/opence/packaged started with nothing written beside the executable,
the files in the XDG folders, and no update; tools/test_linux_package.py,
tools/test_install_layout.py, the new harness test of the updater's names
(with negative controls), tools/test_linux_port.py and tools/harness.
Not built here: Windows, Android and the web (their code paths compile to
what they were; updater.c was syntax-checked with _WIN32, HALO_ANDROID and
HALO_WEB).
…pdate an installed game

A game whose folder cannot be written to counted as installed, and an
installed game took config.toml from ~/.config/halo-linux and the game
data from ~/.local/share/halo-linux only. So a portable copy on a
read-only USB stick or mount, or an /opt/halo an administrator filled with
maps, ignored the config.toml and maps/ beside the game, which the flat
releases had read from there. A read-only folder without
share/opence/packaged now keeps what it has: its config.toml and maps/
(matched without case, as the data root's is) are read in place, and only
what it lacks goes to the XDG folders (those alone are made). A packaged
tree still keeps everything in the XDG folders, as designed.

Without a home folder (no XDG_CONFIG_HOME, XDG_DATA_HOME or HOME) the
reason the game counted as installed was cleared, so install_is_system()
returned 0 and the updater treated a packaged or /usr install as
portable. The reason now stays: where the player's files go (the game's
folder, whatever it allows) is decided apart from whether the game
updates itself, and an installed game never does.

Checked: tools/test_install_layout.py gains a read-only flat folder with
config.toml and maps, a read-only folder with maps only, a read-only tree
with and without maps, a packaged tree whose folder has config.toml and
maps (still the XDG folders), a packaged tree and a read-only /usr tree
with HOME unset (installed, files in the game's folder), and a portable
tree with HOME unset; six of them fail on the previous install_layout.c.
The row of the network test's settings gave "off" as the default of them
all, but debug.network_test_start is 15 seconds (port_config.c). The
other debug.* rows were checked against port_config.c's entries (names,
defaults, environment variables, platforms): every debug setting has a
row in this README or the Android one, and none here is marked as for one
processor only.
@bnunu
bnunu requested review from bnunu and cybersecurity and removed request for cybersecurity October 14, 2026 08:38
@VerySmuggy VerySmuggy added enhancement New feature or request priority: medium Normal priority labels Oct 19, 2026
@cybersecurity

Copy link
Copy Markdown
Collaborator

Conflicts in seven files, and its updater changes would have to be rewritten on top of the newer rollback and size checks. Please rebase it and then do a split resubmission.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: medium Normal priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants