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.

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:
    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")
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 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
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 -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")
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