Packaging Ghostty
A real-world guide to packaging Ghostty with Zoi, including native dependencies, Zig builds, and split packages.
This guide walks through packaging Ghostty as a Zoi package. Ghostty is a good real-world example because it is not just a single downloaded binary: it has native Linux desktop dependencies, Zig build tooling, generated resources, shell integration files, terminfo files, manuals, desktop files, icons, shared libraries, and optional Nautilus integration.
The example adopts the rigorous authoritative standards required for the Zoidberg main tier:
- fetch the Ghostty release tarball from GitHub
- mandatory integrity verification with
sha512 - install native build and runtime dependencies with
native: - run Ghostty's Zig cache bootstrap script
- build with
zig build - split the package into functional sub-packages using the standard
:lib,:dev, and:docsconvention.
Linux-focused guide
This is a Linux source-build guide. Ghostty packaging differs across platforms, and the exact native dependency names can vary by distribution. Treat this as a strong starting point for a maintained registry package, not as a universal package definition for every operating system.
Package Shape
Ghostty maps naturally to a split package with 13 sub-packages, offering granular control over what gets installed:
| Sub-package | Origin | Purpose |
|---|---|---|
ghostty:main | PKGBUILD + RPM | Primary Binary: The terminal binary, desktop files, and icons. |
ghostty:lib | RPM spec | Runtime: Shared library (libghostty-vt.so) |
ghostty:dev | RPM spec | Development: Headers and .pc file |
ghostty:terminfo | both | Terminfo entries, useful on hosts accessed over SSH |
ghostty:bash-completion | RPM spec | Bash completions + bash shell integration |
ghostty:fish-completion | RPM spec | Fish completions + fish shell integration |
ghostty:zsh-completion | RPM spec | Zsh completions + zsh shell integration |
ghostty:nushell-completion | RPM spec | Nushell completions |
ghostty:nautilus | both | Optional GNOME Files integration |
ghostty:neovim | RPM spec | Neovim syntax highlighting and compiler |
ghostty:vim | RPM spec | Vim syntax highlighting and compiler |
ghostty:docs | RPM spec | Documentation: HTML documentation |
ghostty:lang | RPM spec | Translation files (if present) |
Installing ghostty installs the main terminal plus shared library and terminfo by default, while leaving optional components like shell completions, editor integrations, and Nautilus integration for explicit selection.
Full Source
The complete package definition is available at:
examples/ghostty/ghostty.pkg.luaIt covers all 13 sub-packages listed above and the full build lifecycle. The remainder of this guide explains the key design decisions.
Key Design Decisions
Build Once, Split Later
The prepare() function downloads, extracts, and builds Ghostty in a single pass into a DESTDIR install tree. The package(args) function then copies different parts of that tree depending on which sub-package is being assembled:
function prepare()
-- Mandatory integrity check
local file = UTILS.DOWNLOAD(url, archive, "sha512-...")
UTILS.EXTRACT(file, "src")
cmd("cd src/" .. src_dir
.. " && ZIG_GLOBAL_CACHE_DIR=" .. zig_global_cache_dir
.. " ./nix/build-support/fetch-zig-cache.sh")
cmd("cd src/" .. src_dir
.. " && DESTDIR=" .. BUILD_DIR .. "/install zig build"
.. " --summary all --prefix /usr"
.. " --system " .. zig_global_cache_dir .. "/p"
.. " -Doptimize=ReleaseFast -Dgtk-x11=true -Dcpu=baseline"
.. " -Dpie=true -Demit-docs"
.. " -Dversion-string=" .. PKG.version .. "-zoi"
.. " --build-id=sha1")
endPer-Sub-Package Dependencies
The dependencies() block defines shared runtime deps (GTK4, Wayland, etc.) and sub-package-specific deps. For example, shell completion sub-packages require the corresponding shell, and Nautilus requires GNOME-specific Python bindings:
dependencies({
build = {
types = {
source = {
required = {
"native:zig", "native:blueprint-compiler",
"native:pandoc-cli", "native:pkg-config"
}
}
}
},
runtime = {
required = {
"native:bzip2", "native:fontconfig", "native:freetype2",
"native:glib2", "native:gtk4", "native:gtk4-layer-shell",
"native:harfbuzz", "native:libadwaita", "native:libpng",
"native:libx11", "native:oniguruma", "native:pixman",
"native:wayland", "native:zlib"
},
sub_packages = {
["bash-completion"] = { required = { "native:bash-completion" } },
["fish-completion"] = { required = { "native:fish" } },
["zsh-completion"] = { required = { "native:zsh" } },
["nushell-completion"] = { required = { "native:nushell" } },
nautilus = {
required = {
"native:nautilus-python",
"native:python3-gobject"
}
},
neovim = { required = { "native:neovim" } },
vim = { required = { "native:vim" } },
dev = { required = { "native:zig" } },
}
}
})Library and Development Split
Following the Zoidberg policy, the shared library and development artefacts are separate sub-packages. The :lib sub-package contains only the versioned .so.* runtime files, while :dev ships headers, the unversioned .so symlink, and the pkg-config file:
elseif sub == "lib" then
cmd("rm -rf lib-runtime && mkdir -p lib-runtime")
cmd("cp -a " .. root .. "/lib/. lib-runtime/ 2>/dev/null; true")
cmd("rm -rf lib-runtime/pkgconfig 2>/dev/null; true")
zcp("lib-runtime", "${pkgstore}/lib")
elseif sub == "dev" then
if UTILS.FS.exists(root .. "/include") then
zcp(root .. "/include", "${pkgstore}/include")
end
if UTILS.FS.exists(root .. "/lib/pkgconfig") then
zcp(root .. "/lib/pkgconfig", "${pkgstore}/lib/pkgconfig")
end
if UTILS.FS.exists(root .. "/lib/libghostty-vt.so") then
cmd("mkdir -p dev-lib")
cmd("cp -a " .. root .. "/lib/libghostty-vt.so dev-lib/")
zcp("dev-lib", "${pkgstore}/lib")
end
endPlatform-Specific Optimization
The file includes a sys_libs variable for distributions that package Zig dependencies as system libraries (like openSUSE). Uncomment and set the flag when building on such distributions:
-- On openSUSE, set to:
-- local sys_libs = "-fsys=freetype -fsys=harfbuzz -fsys=fontconfig -fsys=libpng
-- -fsys=zlib -fsys=oniguruma -fsys=gtk4-layer-shell"
local sys_libs = ""Build and Validate
Validate the package:
zoi package doctor ./ghostty.pkg.lua --platform linux-amd64Run package tests:
zoi package test ./ghostty.pkg.lua --platform linux-amd64Build distributable archives:
zoi package build ./ghostty.pkg.lua --platform linux-amd64 --testInstall only the main terminal:
zoi package install ./ghostty-1.3.1-linux-amd64.zpa --sub mainInstall the default sub-packages from the package definition:
zoi install ./ghostty.pkg.luaMaintainer Notes
When turning this into a registry package:
- MUST confirm the SHA512 hash in
prepare()matches the current release - MUST use valid SPDX license expressions
- confirm native package names for each supported distro
- adjust copied resource paths based on the current Ghostty install tree
- consider a separate pre-compiled package once trusted release archives are available
- keep Nautilus, shell completions, and editor integrations optional because they pull in extra dependencies
For package fields and lifecycle details, see Creating Packages.
2026 © All Rights Reserved.
- All the content is available under CC BY-SA 4.0, expect where otherwise stated.
- Source code is available on GitLab, licensed under Apache 2.0.
Last updated on
