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.
Metadata
Some examples focus on a specific feature and omit repetitive metadata for
brevity. A package intended to parse and publish must include the required
fields described in Creating Packages,
including name, repo, description, maintainer, and either version
or versions.
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:
default:
DEBUG: "true"
DATABASE_URL: "postgres://localhost:5432/mydb"
linux-amd64:
DEBUG: "true"
DATABASE_URL: "postgres://localhost:5432/mydb"
CC: "gcc"
macos-arm64:
DEBUG: "true"
DATABASE_URL: "postgres://localhost:5432/mydb"
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 for granular file manipulations on real filesystem paths. For package staging, use zcp, zmkdir, and related staging helpers.
function package()
-- Create a nested directory structure manually
zmkdir("${pkgstore}/share/myapp/icons")
-- Copy and set permissions
if UTILS.FS.exists("assets/logo.png") then
zcp("assets/logo.png", "${pkgstore}/share/myapp/icons/logo.png")
-- Set to 644 (rw-r--r--)
zchmod("${pkgstore}/share/myapp/icons/logo.png", 420)
end
-- Rename a binary based on platform
if SYSTEM.OS == "macos" then
UTILS.FS.move(BUILD_DIR .. "/build/bin/tool", BUILD_DIR .. "/build/bin/tool-macos")
zcp("build/bin/tool-macos", "${pkgstore}/bin/tool-macos")
else
zcp("build/bin/tool", "${pkgstore}/bin/tool")
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 -j2")
end
-- package.pkg.lua
INCLUDE("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
