Creating Packages
A complete guide on how to create a package for Zoi.
This guide provides a start-to-finish walkthrough of creating a new package, testing it locally, and publishing it to the official Zoi package repositories for everyone to use.
Understanding Zoi Repositories
Zoi organizes its packages into several repositories, each with a specific purpose. When you contribute a new package, you'll need to decide which repository is the best fit.
Repository | Description |
---|---|
core | Essential packages and libraries; very common and well-maintained. |
main | Important packages that don't fit in core but are essential for most users. |
extra | New or niche packages; less common and may be less actively maintained. |
community | User-submitted packages. New entries start here and may graduate to higher tiers. |
test | Testing ground for new Zoi features and packages prior to release. |
archive | Archived packages that are no longer maintained. |
For your first contribution, you will almost always be adding your package to the community
repository.
For more information about repositories visit here
Step 1: Creating Your Package File
The heart of every Zoi package is its .pkg.lua
definition file. This is a Lua script that allows for dynamic logic, making it powerful for complex packages with variable URLs or platform-specific needs.
A .pkg.lua
file defines a package by calling global functions like package{}
, install{}
, and dependencies{}
. You have access to global variables like PKG
(the package being defined) and SYSTEM
(with OS
, ARCH
, DISTRO
) to make your package definition dynamic.
Here is a basic example:
-- my-cli.pkg.lua
-- Use local variables for repeated values
local repo_owner = "user"
local repo_name = "my-cli"
local version = "1.2.3"
-- The main package definition table
package({
name = repo_name,
repo = "community",
version = version,
description = "A simple command-line utility.",
website = "https://example.com/" .. repo_name,
git = "https://github.com/" .. repo_owner .. "/" .. repo_name,
maintainer = {
name = "Your Name",
email = "[email protected]",
key = "DEADC0DEDEADBEEFDEADC0DEDEADBEEFDEADC0DE" -- GPG Key Fingerprint or URL
},
license = "MIT",
tags = { "cli", "devtools" },
bins = { "my-cli" }, -- Binaries this package provides
conflicts = { "old-cli" } -- Packages this conflicts with
})
-- The installation methods
install({
{
name = "Binary",
type = "binary",
-- Use Lua to construct the URL dynamically
url = "https://github.com/" .. repo_owner .. "/" .. repo_name .. "/releases/download/v" .. PKG.version .. "/" .. repo_name .. "-" .. SYSTEM.OS .. "-" .. SYSTEM.ARCH,
platforms = { "all" }
}
})
-- Dependencies
dependencies({
build = {
required = { "native:go" }
}
})
Step 2: Defining an Installation Method
The install{}
block tells Zoi how to get the software. You can provide multiple methods, and Zoi will pick the best one. If you set selectable = true
, the user will be prompted to choose.
install({
selectable = true, -- Allows the user to choose between these methods
{
name = "Binary", -- A descriptive name for the method
type = "binary",
url = "...",
platforms = { "linux-amd64", "macos-amd64", "windows-amd64" }
},
{
name = "Build from Source",
type = "source",
url = PKG.git, -- Use the git URL from the package definition
platforms = { "all" },
-- Optional: build inside a Docker container for reproducibility.
-- Use "hub:" for Docker Hub and "ghcr:" for GitHub Container Registry.
docker_image = "hub:golang:1.21",
build_commands = { "make" },
-- After building, Zoi will look for an executable with the same name as the package.
-- If the binary has a different name or is in a subdirectory, specify its path.
-- When using docker_image, this path is inside the container.
bin_path = "build/my-cli"
}
})
For a full list of installation types (binary
, com_binary
, source
, script
) and their options, see the Package Examples.
Copying Additional Files
For source
and com_binary
installation types, you can specify additional files or directories to be copied from the build environment (or extracted archive) to the user's system. This is useful for installing things like documentation, shell completions, or other assets that are not part of the main binary.
This is done using the files
field within an installation method:
install({
{
name = "Build from Source",
type = "source",
url = "...",
build_commands = { "make" },
bin_path = "build/my-cli",
files = {
{
platforms = { "linux", "macos" },
files = {
{ source = "man/my-cli.1", destination = "/usr/local/share/man/man1/my-cli.1" },
{ source = "completions/my-cli.bash", destination = "/usr/share/bash-completion/completions/my-cli" }
}
},
{
platforms = { "windows" },
files = {
{ source = "docs/LICENSE", destination = "C:\ProgramData\my-cli\LICENSE" }
}
}
}
}
})
- The
files
field is a list ofFileGroup
tables. - Each
FileGroup
has aplatforms
list to specify which OS it applies to. - The
files
field inside the group is a list ofFileCopy
tables. - Each
FileCopy
has asource
(relative to the build/archive root) and adestination
(an absolute path on the user's system). You can use~/
in the destination to refer to the user's home directory.
Security: Checksums and Signatures
It is highly recommended to include checksums and GPG signatures to verify downloads.
install({
{
type = "binary",
url = "...",
platforms = { "all" },
-- URL to a checksums file (e.g. checksums.txt)
checksums = release_base_url .. "/checksums.txt",
-- GPG signature for the file
sigs = {
{
file = "my-cli-" .. SYSTEM.OS .. "-" .. SYSTEM.ARCH,
sig = release_base_url .. "/my-cli.sig"
}
}
}
})
Zoi will use the key
from the maintainer
or author
block to verify the signature. You can manage keys with the zoi pgp
command.
Step 3: Adding Dependencies
Define dependencies in the dependencies{}
block.
dependencies({
-- Build-time dependencies
build = {
required = { "native:make", "native:gcc" },
optional = { "native:rust:for rust language support" }
},
-- Runtime dependencies
runtime = {
required = { "zoi:some-base-library" },
-- Let the user choose a GUI provider
options = {
{
name = "GUI",
desc = "GUI Providers",
all = false, -- 'false' means choose one, 'true' allows multiple
depends = {
"native:qt6:for KDE desktop environments",
"native:gtk4:for GNOME-based desktop environments"
}
}
}
}
})
For more details, see the Dependencies guide.
Step 4: Advanced Features
Zoi supports many advanced features in the package{}
block:
alt
: Redirect to another package, URL, or file.updater
: Forcezoi update
to use a specific install method (e.g.source
).rollback = false
: Disable rollback backups for this package.updates
: A list of messages (breaking changes, vulnerabilities) to show the user before installation.
See the Package Examples for how to use these.
Step 5: Testing Your Package Locally
Before publishing, you must test your package locally.
# Install from your local .pkg.lua file
zoi install ./my-package.pkg.lua
# If testing a source build specifically
zoi build ./my-package.pkg.lua
After installation, run the package's command to ensure it works, then uninstall it to test cleanup.
zoi uninstall my-package
Step 6: The zoi package
Workflow (Advanced)
For more complex packages, Zoi provides a dedicated package
command set for a structured build process.
-
zoi package meta <path/to/file.pkg.lua>
: Generates a*.meta.json
file. This file resolves all the dynamic Lua script logic for each platform into a static description of all assets, checksums, and signatures. You can also specify--type <type>
to force generation from a specific installation method (binary
,com_binary
, orsource
). -
zoi package build <path/to/file.meta.json>
: Takes themeta.json
file, downloads all the assets for the current platform, verifies them, and bundles them into a single Zoi package archive (.pkg.tar.zst
). -
zoi package install <path/to/file.pkg.tar.zst>
: Installs a package from a local Zoi package archive. This is how Zoi installs pre-built packages from a repository.
This workflow is used internally by Zoi's CI/CD to build and publish official packages, but you can also use it for your own complex build pipelines.
Step 7: Publishing Your Package
Once your package is tested and ready, you can publish it to the official repositories or host your own.
For a complete guide on publishing, please see:
Last updated on