Zillowe FoundationZillowe Documentation

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)
end

2. 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")
end

3. 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
end

4. 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")
end

5. 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")
end

9. 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")
end

10. 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")
end

11. 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")
end

12. 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
end

14. 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")
end

17. 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
end

18. 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")
end

19. 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
end

21. 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")
end

22. 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" }
})

A software organization

2026 © All Rights Reserved.

  • All the content is available under CC BY-SA 4.0, expect where otherwise stated.

Last updated on