Zillowe FoundationZillowe Documentation

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.

RepositoryDescription
coreEssential packages and libraries; very common and well-maintained.
mainImportant packages that don't fit in core but are essential for most users.
extraNew or niche packages; less common and may be less actively maintained.
communityUser-submitted packages. New entries start here and may graduate to higher tiers.
testTesting ground for new Zoi features and packages prior to release.
archiveArchived 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 of FileGroup tables.
  • Each FileGroup has a platforms list to specify which OS it applies to.
  • The files field inside the group is a list of FileCopy tables.
  • Each FileCopy has a source (relative to the build/archive root) and a destination (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: Force zoi 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.

  1. 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, or source).

  2. zoi package build <path/to/file.meta.json>: Takes the meta.json file, downloads all the assets for the current platform, verifies them, and bundles them into a single Zoi package archive (.pkg.tar.zst).

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