Package Examples
A comprehensive set of examples for creating Zoi packages and project configurations.
This document provides a wide range of examples demonstrating the flexibility and power of Zoi's packaging system. These examples cover everything from basic binaries to complex background services and multi-component packages.
1. Simple Pre-compiled Binary
The most common use case: downloading a pre-built binary from a release page.
-- my-cli.pkg.lua
metadata({
name = "my-cli",
repo = "community",
version = "1.2.3",
description = "A cool command-line tool.",
website = "https://example.com/my-cli",
license = "MIT",
bins = { "my-cli" },
types = { "pre-compiled" },
})
function prepare()
local version = PKG.version
local os = SYSTEM.OS
local arch = SYSTEM.ARCH
local ext = (os == "windows") and "zip" or "tar.gz"
-- Construct the download URL
local url = string.format("https://example.com/my-cli/releases/download/v%s/my-cli-%s-%s.%s", version, os, arch, ext)
-- Download and extract into a directory named "extracted"
UTILS.EXTRACT(url, "extracted")
end
function package()
local bin_name = (SYSTEM.OS == "windows") and "my-cli.exe" or "my-cli"
-- Stage the binary to the package store
zcp("extracted/" .. bin_name, "${pkgstore}/bin/" .. bin_name)
end2. Background Service (Daemon)
Example of a package that defines a background process managed by the system (systemd, launchd, or sc.exe).
-- redis-server.pkg.lua
metadata({
name = "redis-server",
repo = "core",
version = "7.2.4",
description = "In-memory data structure store",
license = "BSD-3-Clause",
bins = { "redis-server", "redis-cli" },
types = { "pre-compiled" }
})
-- Define the background service
service({
-- Command to run the daemon
run = "${pkgstore}/bin/redis-server ${pkgstore}/etc/redis.conf",
-- Start automatically at boot/login
run_at_load = true,
working_dir = "${pkgstore}",
env = {
REDIS_PORT = "6379"
},
log_path = "/var/log/redis.log"
})
function prepare()
local url = "https://example.com/redis-" .. SYSTEM.OS .. ".tar.zst"
UTILS.EXTRACT(url, "src")
end
function package()
zcp("src/bin", "${pkgstore}/bin")
-- Bundle a default config file next to the .pkg.lua script
zcp("${pkgluadir}/redis.conf", "${pkgstore}/etc/redis.conf")
end3. Split Package (Library & Headers)
Large projects often split binaries, libraries, and headers into separate sub-packages.
-- libexample.pkg.lua
metadata({
name = "libexample",
repo = "main",
version = "1.0.0",
description = "An example C library",
license = "LGPL-2.1-only",
-- Define installable components
sub_packages = { "lib", "headers", "docs" },
-- Default sub-packages when running 'zoi install libexample'
main_subs = { "lib", "headers" },
types = { "source" }
})
function prepare()
cmd("git clone https://github.com/example/libexample.git source")
end
function package(args)
cmd("cd source && make")
if args.sub == "lib" then
zcp("source/libexample.so", "${pkgstore}/lib/libexample.so")
elseif args.sub == "headers" then
zcp("source/include/example.h", "${pkgstore}/include/example.h")
elseif args.sub == "docs" then
zcp("source/README.md", "${pkgstore}/share/doc/libexample/README.md")
end
end4. App Template (Project Boilerplate)
Templates for zoi create to bootstrap new projects.
-- rust-web-starter.pkg.lua
metadata({
name = "rust-web-starter",
repo = "community",
type = "app",
version = "1.0.0",
description = "A production-ready Rust web service template"
})
function package()
-- Everything staged to ${createpkgdir} is copied to the user's CWD
zcp("${pkgluadir}/template/Cargo.toml", "${createpkgdir}/Cargo.toml")
zcp("${pkgluadir}/template/src", "${createpkgdir}/src")
zcp("${pkgluadir}/template/.env.example", "${createpkgdir}/.env")
end5. Zoi Extension (Custom Plugin)
Extensions that add new commands to Zoi itself.
-- audit-plugin.pkg.lua
metadata({
name = "audit-plugin",
repo = "community",
type = "extension",
version = "1.0",
description = "Adds a 'zoi audit' command to check for vulnerabilities",
extension = {
type = "zoi",
changes = {
{
type = "plugin",
name = "audit",
script = [[
zoi.register_command({
name = "audit",
description = "Check installed packages for security advisories",
callback = function(args)
zoi.ui.print("Scanning your environment...", "cyan")
local installed = zoi.list_installed()
for _, pkg in ipairs(installed) do
-- Dummy logic: alert on old openssl
if pkg.name == "openssl" and pkg.version == "1.1.1" then
zoi.ui.print("VULNERABLE: " .. pkg.name .. " v" .. pkg.version, "red")
end
end
end
})
]]
}
}
}
})6. Complex zoi.yaml (Project Environment)
A real-world project configuration with custom environments and a development shell.
# zoi.yaml
name: my-backend-api
# Project-local package isolation
config:
local: true
# Required packages for development
pkgs:
- zoi:postgresql
- zoi:redis
- zoi:[email protected]
# Environment variables for 'zoi dev'
shell:
env:
DEBUG: "true"
DATABASE_URL: "postgres://localhost:5432/mydb"
platform:
linux-amd64:
CC: "gcc"
macos-arm64:
CC: "clang"
# Short aliases for common tasks
commands:
- cmd: build
run: go build -o api main.go
- cmd: test
run: go test ./...
- cmd: db:up
run: zoi service start postgresql
# Full environment setups
environments:
- name: CI Pipeline
cmd: ci
run:
- go mod download
- zoi run build
- zoi run test
env:
NODE_ENV: "test"7. Global Transaction Hook
Automate tasks like reloading shell completions when packages are changed.
-- shell-hook.pkg.lua
metadata({
name = "shell-hook",
repo = "community",
type = "extension",
version = "1.0",
description = "Auto-reloads Zoi shell completions",
extension = {
type = "zoi",
changes = {
{
type = "hook",
name = "reload-completions",
content = [[
name: reload-completions
description: Updates completions when Zoi packages change
trigger:
paths:
- "home/.zoi/pkgs/bin/**"
operation: ["install", "remove"]
action:
when: PostTransaction
exec: zoi shell bash --scope user
]]
}
}
}
})8. Multi-Manager Dependencies (Hybrid)
Zoi can bridge multiple package managers in a single dependency tree.
-- data-cli.pkg.lua
metadata({
name = "data-cli",
repo = "community",
version = "2.1.0",
description = "A CLI tool that requires system and language libs",
license = "MIT",
bins = { "data-cli" },
types = { "source" }
})
dependencies({
build = {
-- Use system manager for compiler
required = { "native:gcc", "native:make" }
},
runtime = {
-- Require a library from APT/Brew and a tool from Cargo
required = {
"native:openssl",
"cargo:ripgrep",
"npm:chalk"
},
optional = {
"zoi:extra-plugins:Additional features"
}
}
})
function package()
cmd("make build")
zcp("bin/data-cli", "${pkgstore}/bin/data-cli")
end9. Dynamic Versioning from GitHub
Avoid manual version bumps by fetching the latest release tag directly from the GitHub API.
-- fetch the latest release tag (e.g. "v1.2.3")
local tag = UTILS.FETCH.GITHUB.LATEST.release({ repo = "Zillowe/Zoi" })
-- strip "v" prefix if present
local version = tag:gsub("^v", "")
metadata({
name = "zoi-latest",
repo = "test",
version = version,
description = "Always tracks the latest Zoi release",
maintainer = { name = "Automated", email = "[email protected]" },
types = { "pre-compiled" }
})
function prepare()
local url = "https://example.com/download/" .. version .. "/bin.tar.gz"
UTILS.EXTRACT(url, "src")
end10. Platform Mapping
Map Zoi's platform strings to the specific naming convention used by an upstream project.
-- mapping table for target triples
local triples = {
["linux-amd64"] = "x86_64-unknown-linux-gnu",
["linux-arm64"] = "aarch64-unknown-linux-gnu",
["macos-amd64"] = "x86_64-apple-darwin",
["macos-arm64"] = "aarch64-apple-darwin",
["windows-amd64"] = "x86_64-pc-windows-msvc"
}
local current = SYSTEM.OS .. "-" .. SYSTEM.ARCH
local triple = triples[current] or error("Unsupported platform: " .. current)
metadata({
name = "cross-tool",
repo = "main",
version = "1.0.0",
description = "A tool with specific platform naming",
bins = { "cross-tool" },
types = { "pre-compiled" }
})
function prepare()
local url = string.format("https://dl.example.com/%s/tool-%s.zip", PKG.version, triple)
UTILS.EXTRACT(url, "out")
end11. Configuration File Handling (backup)
Ensure user modifications to configuration files are preserved during upgrades.
-- web-server.pkg.lua
metadata({
name = "web-server",
repo = "community",
version = "1.5.0",
description = "Web server with persistent config",
-- Files listed here are saved as .zoisave/zoinew on upgrade/remove
backup = { "etc/server.conf" },
types = { "pre-compiled" }
})
function package()
zcp("bin/server", "${pkgstore}/bin/web-server")
-- Bundle default config
zcp("${pkgluadir}/default.conf", "${pkgstore}/etc/server.conf")
end12. Virtual Provider (provides)
Allow other packages to depend on a generic feature regardless of the specific provider.
-- mariadb.pkg.lua
metadata({
name = "mariadb",
repo = "core",
version = "11.2.2",
description = "Fast and reliable SQL server",
license = "GPL-2.0",
-- Provides the virtual 'sql-server' package
provides = { "sql-server" },
types = { "pre-compiled" }
})
-- Another package can now depend on "zoi:sql-server"
-- and Zoi will offer to install mariadb or mysql.13. Conditional Logic in Lifecycle
Use the SYSTEM and BUILD_TYPE variables to handle platform-specific build steps or architectures.
function prepare()
if SYSTEM.OS == "windows" then
cmd("powershell -File build.ps1")
else
cmd("./configure --prefix=" .. STAGING_DIR)
-- Optimize for ARM64 if detected
if SYSTEM.ARCH == "arm64" then
cmd("make CFLAGS='-O3 -march=native'")
else
cmd("make")
end
end
end
function package()
local ext = (SYSTEM.OS == "windows") and ".exe" or ""
local bin = "myapp" .. ext
if BUILD_TYPE == "source" then
zcp("build/" .. bin, "${pkgstore}/bin/" .. bin)
else
-- Assuming pre-compiled files were extracted to 'dist'
zcp("dist/" .. bin, "${pkgstore}/bin/" .. bin)
end
end14. Per-Sub-Package Dependencies
Define specific dependencies that are only installed for certain components of a split package.
-- graphics-stack.pkg.lua
metadata({
name = "graphics-stack",
repo = "main",
version = "1.0",
sub_packages = { "runtime", "tools", "devel" },
main_subs = { "runtime" },
types = { "source" }
})
dependencies({
runtime = {
required = { "native:libdrm" },
-- Component-specific dependencies
sub_packages = {
tools = {
required = { "native:glx-utils" }
},
devel = {
required = { "native:libdrm-devel", "native:mesa-libgl-devel" }
}
}
}
})15. Search Indexing & Discovery
Use tags to help users discover your package through zoi search.
metadata({
name = "neon-vim",
repo = "community",
version = "0.9.0",
description = "A customized Neovim distribution",
-- Keywords for search indexing
tags = { "editor", "vim", "neovim", "ide", "lua", "custom" },
license = "Apache-2.0",
bins = { "nvim" },
types = { "pre-compiled" }
})16. Advanced PGP Management
Add a trusted maintainer key to the local keyring during the build process for signature verification.
function prepare()
-- Import a maintainer key from a URL
addPgpKey("https://keys.example.com/maintainer.asc", "maintainer-key")
local url = "https://example.com/app.tar.gz"
local sig = "https://example.com/app.tar.gz.sig"
UTILS.FILE(url, "app.tar.gz")
UTILS.FILE(sig, "app.tar.gz.sig")
-- Verify signature using the imported key
if not verifySignature("app.tar.gz", "app.tar.gz.sig", "maintainer-key") then
error("Security: Invalid PGP signature!")
end
UTILS.EXTRACT("app.tar.gz", "src")
end17. Complex File System Logic
Use UTILS.FS to perform granular file manipulations within the build or staging directories.
function package()
-- Create a nested directory structure manually
local target = "${pkgstore}/share/myapp/icons"
cmd("mkdir -p " .. target)
-- Copy and set permissions
if UTILS.FS.exists("assets/logo.png") then
UTILS.FS.copy("assets/logo.png", target .. "/logo.png")
-- Set to 644 (rw-r--r--)
UTILS.FS.chmod(target .. "/logo.png", 420)
end
-- Rename a binary based on platform
if SYSTEM.OS == "macos" then
UTILS.FS.move("${pkgstore}/bin/tool", "${pkgstore}/bin/tool-darwin")
end
end18. Shared Logic with INCLUDE
Keep your package definitions DRY (Don't Repeat Yourself) by including external Lua scripts.
-- shared_build.lua
function common_make()
cmd("make clean")
cmd("make -j" .. (ZOI.parallel_jobs or 2))
end
-- package.pkg.lua
INCLUDE("${pkgluadir}/shared_build.lua")
function package()
-- Use function defined in shared_build.lua
common_make()
zcp("output/bin", "${pkgstore}/bin")
end19. Selecting Optional Dependencies (options)
Provide users with a choice between competing dependencies, such as different database drivers or GUI toolkits.
dependencies({
runtime = {
required = { "zoi:core-lib" },
options = {
{
name = "Database Driver",
desc = "Choose which database backend to use",
all = false, -- Only allow one selection
depends = {
"native:libpq:PostgreSQL support",
"native:sqlite-devel:SQLite support"
}
},
{
name = "Optional Assets",
desc = "Install extra high-res textures",
all = true, -- Allow selecting multiple
depends = {
"zoi:textures-hd",
"zoi:textures-ultra"
}
}
}
}
})20. Advanced Uninstall Cleanup
Use the uninstall() function and zrm to clean up state outside of the standard package store, such as user caches or system-wide data directories.
function uninstall()
-- Remove a package-specific cache directory in the user's home
zrm("${usrhome}/.cache/myapp")
-- Remove a system-wide log directory (requires root)
if SYSTEM.OS == "linux" then
zrm("/var/log/myapp")
end
end21. Complex Source Build (C++)
Example of a multi-step build process involving cmake, make, and environment variables.
-- graphics-engine.pkg.lua
metadata({
name = "graphics-engine",
repo = "community",
version = "3.0.1",
description = "High-performance C++ rendering engine",
license = "MIT",
types = { "source" }
})
dependencies({
build = {
required = { "native:cmake", "native:gcc-c++", "native:ninja" }
},
runtime = {
required = { "native:vulkan-loader", "native:libx11" }
}
})
function prepare()
cmd("git clone --recursive https://github.com/engine/graphics.git source")
end
function package()
-- Use Ninja for faster builds
cmd("cmake -S source -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=" .. STAGING_DIR .. "/usr")
cmd("cmake --build build")
cmd("cmake --install build")
-- Move files from staging /usr to special Zoi destinations
zcp(STAGING_DIR .. "/usr/bin", "${pkgstore}/bin")
zcp(STAGING_DIR .. "/usr/lib", "${pkgstore}/lib")
zcp(STAGING_DIR .. "/usr/include", "${pkgstore}/include")
end22. Platform-Specific Dependencies
Use the SYSTEM table within the dependencies block to dynamically change requirements.
-- cross-editor.pkg.lua
local deps = { "zoi:base-utils" }
if SYSTEM.OS == "linux" then
table.insert(deps, "native:libxkbcommon")
elseif SYSTEM.OS == "macos" then
table.insert(deps, "brew:terminal-notifier")
end
metadata({
name = "cross-editor",
repo = "main",
version = "1.0",
description = "Editor with OS-specific helpers",
types = { "pre-compiled" }
})
dependencies({
runtime = deps
})23. Interactive Selection Logic
Show how to use the options field to let users choose between feature sets.
-- media-player.pkg.lua
metadata({
name = "media-player",
repo = "community",
version = "2.0",
description = "Extensible media player",
types = { "pre-compiled" }
})
dependencies({
runtime = {
required = { "native:ffmpeg" },
options = {
{
name = "Audio Backend",
desc = "Choose how the player outputs sound",
all = false,
depends = {
"native:pulseaudio:Legacy Linux audio",
"native:pipewire:Modern Linux audio",
"native:alsa-utils:Direct hardware access"
}
}
}
}
})24. Collection Package (Metapackage)
Create a package that groups several other tools together for easy installation.
-- dev-essentials.pkg.lua
metadata({
name = "dev-essentials",
repo = "community",
type = "collection", -- No build logic, just dependencies
version = "1.0",
description = "A bundle of essential development tools"
})
dependencies({
runtime = {
"zoi:git",
"zoi:curl",
"zoi:htop",
"cargo:bat",
"cargo:exa"
}
})25. Complex Extension (Corporate Bundle)
An extension that applies multiple system-wide configuration changes at once.
-- corp-config.pkg.lua
metadata({
name = "corp-config",
repo = "community",
type = "extension",
version = "2.1",
description = "Configures Zoi for the MyCorp internal environment",
extension = {
type = "zoi",
changes = {
-- 1. Point to internal mirror
{
type = "registry-repo",
add = "https://git.mycorp.internal/zoi/zoidberg.git"
},
-- 2. Add private team registry
{
type = "registry-add",
add = "https://git.mycorp.internal/teams/dev/registry.git"
},
-- 3. Import team PGP key for verification
{
type = "pgp",
name = "mycorp-signing-key",
key = "https://keys.mycorp.internal/release.asc"
}
}
}
})26. Important Security Updates
Use the updates() function to alert users about critical changes or vulnerabilities.
-- legacy-app.pkg.lua
metadata({
name = "legacy-app",
repo = "archive",
version = "0.5.0",
description = "A legacy tool with known issues",
types = { "pre-compiled" }
})
updates({
{
type = "vulnerability",
message = "This version contains CVE-2026-1234. Upgrade to v1.0 immediately!"
},
{
type = "change",
message = "The configuration format has changed. See /usr/share/doc/legacy-app/README.md"
}
})27. Forced System Scope
Suggest that a package should always be installed system-wide (e.g. for drivers or core services).
-- system-driver.pkg.lua
metadata({
name = "system-driver",
repo = "core",
version = "1.2",
description = "A core system driver",
-- Default to system scope
scope = "system",
types = { "pre-compiled" }
})28. Package Replacement (replaces)
Smoothly transition users from an old package name to a new one.
-- modern-tool.pkg.lua
metadata({
name = "modern-tool",
repo = "main",
version = "2.0",
description = "The modern successor to 'legacy-tool'",
-- Automatically offer to uninstall the old package
replaces = { "legacy-tool" },
types = { "pre-compiled" }
})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
