# Stage 1: Coral Package Manager Implementation Plan

**Document Version:** 2.5
**Created:** January 2026
**Last Updated:** January 2026
**Status:** In Progress (Stub Implementation Complete)

## Overview

Stage 1 builds **Coral**, the package manager for Zygaena, written entirely in Reef. Coral is a single unified command with subcommands (similar to pacman/cargo).

**Prerequisites:** Stage 0 Complete (Reef 0.1.3 installed on OmniOS at 192.168.168.148)

---

## Design Decisions

| Decision | Choice | Rationale |
|----------|--------|-----------|
| **Name** | `coral` (ocean/reef theme) | Consistent with Zygaena (shark) and Reef (language) naming |
| **Architecture** | Single binary with subcommands | Simpler for users, like pacman/cargo/git |
| **Source Location** | `/home/ctusa/repos/zygaena/coral/` | Part of main zygaena repo for coordinated development |
| **Install Location** | `/usr/local/bin/coral` (OmniOS), `/usr/bin/coral` (Zygaena) | Standard Unix paths |
| **Compression** | xz (`.pkg.tar.xz`) | Good compression, widely available on illumos |
| **HTTP** | Native Reef `net.http` (report issues to Reef team) | Avoid shell exec where possible |
| **Privileges** | Root required for all operations | Multi-user system security |
| **Confirmation** | Ask by default, `-y` to skip | Safe default, scriptable override |
| **Colors** | Yes, when terminal supports it | Better UX, respects NO_COLOR standard |
| **Port Categories** | core, base, devel, desktop, extra | Clear separation of concerns |
| **Distribution** | Binary + Source packages | Binary for speed, source as fallback |
| **Repository Access** | Local filesystem + HTTP | Both equally supported (ISO/USB and mirrors) |
| **Package Signing** | Ed25519/Signify | Simple, minimal dependencies, proven security |
| **Config Format** | TOML | Human-readable, matches port definitions |
| **Third-party Repos** | Supported from day one | Enables community packages |

---

## Command Structure

```
coral <subcommand> [options] [packages...]

BUILD/CREATE:
  coral build <port>           Build package from port directory
  coral build .                Build from current directory

INSTALL/REMOVE:
  coral install <pkg...>       Install packages (resolves dependencies)
  coral remove <pkg...>        Remove packages
  coral upgrade [pkg...]       Upgrade packages (all if none specified)

QUERY:
  coral info <pkg>             Show package details
  coral list                   List installed packages
  coral list -a                List all available packages (from repos)
  coral files <pkg>            List files owned by package
  coral owner <file>           Find which package owns file
  coral search <term>          Search available packages
  coral depends <pkg>          Show dependency tree

PORTS:
  coral sync                   Update ports collection and repo metadata (git/hg)
  coral diff                   Show packages with available updates

REPOSITORY MANAGEMENT:
  coral repo init <path>       Create new repository structure
  coral repo add <path> <pkg>  Add packages to repository
  coral repo remove <path> <pkg> Remove package from repository
  coral repo list <path>       List packages in repository
  coral repo rebuild <path>    Regenerate repo.toml manifest
  coral repo sign <path>       Sign repository manifest
  coral repo verify <path>     Verify repository signatures
  coral repo sync <path> <dest> Rsync repository to mirror
  coral repo export <path> <out> Export repository for ISO/USB

KEY MANAGEMENT:
  coral key init               Initialize keyring directory
  coral key generate <name>    Generate new Ed25519 signing keypair
  coral key list               List available keys
  coral key import <file>      Import public key
  coral key export <name>      Export public key
  coral key trust <keyid>      Mark key as trusted
  coral key revoke <keyid>     Revoke trust for key

CACHE MANAGEMENT:
  coral cache clean            Remove cached packages
  coral cache clean --sources  Remove cached source tarballs
  coral cache clean --all      Remove all cached files
  coral cache list             Show cache contents and sizes

GLOBAL OPTIONS:
  -y, --yes                    Don't ask for confirmation
  -f, --force                  Force operation (overwrite conflicts)
  -v, --verbose                Verbose output
  -n, --dry-run                Show what would be done
  -q, --quiet                  Minimal output
  --no-color                   Disable colored output
  --root <path>                Use alternative root (for building ISOs)

EXAMPLES:
  coral install vim            # Install vim and dependencies
  coral install vim nano -y    # Install multiple, no confirmation
  coral build /usr/ports/core/hello
  coral search editor
  coral upgrade                # Upgrade all packages
  coral remove firefox -y
  coral repo init /srv/packages
  coral key generate official
```

---

## Configuration

### Main Configuration File

Location: `/etc/coral/coral.conf`

```toml
# Coral Package Manager Configuration

[general]
# System architecture
arch = "x86_64"

# Number of parallel build jobs (0 = auto-detect CPU count)
jobs = 0

# Ask for confirmation before operations (override with -y)
confirm = true

# Enable colored output (respects NO_COLOR env var)
color = true

[paths]
# Local ports collection
ports = "/usr/ports"

# Built package cache
packages = "/var/lib/coral/packages"

# Downloaded source tarballs
sources = "/var/lib/coral/sources"

# Installed package database
database = "/var/lib/coral/installed"

# Repository metadata cache
repos = "/var/lib/coral/repos"

# Build working directory (cleaned after build)
work = "/var/tmp/coral/work"

# Package staging directory
pkg = "/var/tmp/coral/pkg"

# Signing keys directory
keys = "/etc/coral/keys"

[build]
# Default CFLAGS for building packages
cflags = "-O2 -pipe -march=x86-64"

# Default CXXFLAGS (defaults to cflags if empty)
cxxflags = "${cflags}"

# Make flags
makeflags = "-j${jobs}"

# Strip binaries after build
strip = true

# Compress man pages with gzip
compress_man = true

# What to do when binary package not available:
# "ask" - prompt user to build from source
# "yes" - automatically build from source
# "no"  - fail if binary not available
fallback_to_source = "ask"

[network]
# Connection timeout in seconds
timeout = 30

# Number of retry attempts
retries = 3

# Use system proxy settings
use_proxy = true

[sync]
# Auto-refresh repository metadata (hours, 0 = manual only)
auto_refresh = 24

[cache]
# Keep downloaded packages after installation
keep_packages = true

# Keep downloaded source tarballs
keep_sources = true

[security]
# Require repositories to have valid signatures
require_signed_repos = true

# Require packages to have valid signatures
require_signed_packages = true
```

### Repository Configuration Directory

Location: `/etc/coral/repos.d/`

Each repository is configured in a separate TOML file.

#### Official Repository Example

File: `/etc/coral/repos.d/official.toml`

```toml
[repository]
# Unique identifier
name = "official"

# Human-readable description
description = "Official Zygaena packages"

# Enable/disable this repository
enabled = true

# Priority (higher = preferred when same package in multiple repos)
priority = 100

# Mirrors to try (in order)
[[mirrors]]
url = "https://pkg.zygaena.org/stable/${arch}/"

[[mirrors]]
url = "https://mirror.example.com/zygaena/${arch}/"

# Local fallback (e.g., installation media)
[[mirrors]]
url = "file:///mnt/cdrom/packages/"

[security]
# Require signed packages from this repo
require_signed = true

# Public key file for signature verification
keyfile = "/etc/coral/keys/official.pub"
```

#### Third-Party Repository Example

File: `/etc/coral/repos.d/community.toml`

```toml
[repository]
name = "community"
description = "Community-maintained packages"
enabled = true
priority = 50

[[mirrors]]
url = "https://community.example.org/zygaena/${arch}/"

[security]
require_signed = true
keyfile = "/etc/coral/keys/community.pub"
```

---

## Repository Manifest Format

Each repository publishes a `repo.toml` manifest that coral downloads to understand available packages.

### Manifest Structure

File: `repo.toml` (at repository root)

```toml
[repository]
# Repository identifier (must match repos.d config)
name = "official"

# Human-readable description
description = "Official Zygaena packages"

# Maintainer contact
maintainer = "[email protected]"

# Target architecture
arch = "x86_64"

# When this manifest was generated (ISO 8601)
generated = 2026-01-23T12:00:00Z

# Total number of packages
package_count = 150

# Base URL for package downloads (relative to manifest location)
base_url = "packages/"

# Signing key identifier
signing_key = "zygaena-official-2026"

# Package entries
[[package]]
name = "bash"
version = "5.2.21"
release = 1
description = "The GNU Bourne Again shell"
license = "GPL-3.0"
category = "core"
depends = ["glibc", "readline", "ncurses"]
optdepends = []
provides = ["sh"]
conflicts = []
size = 7834521           # Installed size in bytes
compressed = 1823456     # Package file size in bytes
filename = "bash-5.2.21-1.pkg.tar.xz"
sha256 = "a1b2c3d4e5f6789..."
signature = "sig:ed25519:..."  # Optional inline signature

[[package]]
name = "vim"
version = "9.1.0"
release = 1
description = "Vi IMproved - enhanced vi editor"
license = "Vim"
category = "base"
depends = ["ncurses"]
optdepends = ["python:Python scripting support", "lua:Lua scripting support"]
provides = ["vi"]
conflicts = []
size = 12456789
compressed = 3456789
filename = "vim-9.1.0-1.pkg.tar.xz"
sha256 = "fedcba987654321..."
signature = "sig:ed25519:..."

# ... more packages ...
```

### Manifest Signature

The manifest itself is signed. Signature stored in: `repo.toml.sig`

```
-----BEGIN ED25519 SIGNATURE-----
<base64 encoded signature>
-----END ED25519 SIGNATURE-----
```

---

## Package Signing

### Key Format

Coral uses Ed25519 keys (same as OpenBSD signify).

**Private key:** `<name>.sec`
```
untrusted comment: coral secret key
<base64 encoded private key>
```

**Public key:** `<name>.pub`
```
untrusted comment: coral public key
<base64 encoded public key>
```

### Key Storage

- System keys: `/etc/coral/keys/`
- User keys: `~/.coral/keys/`
- Trusted keys database: `/var/lib/coral/trusted-keys`

### Signing Workflow

```bash
# Generate a keypair for repository signing
coral key generate official

# Sign the repository manifest
coral repo sign /srv/packages

# Verify a repository
coral repo verify /srv/packages

# Import a third-party public key
coral key import community.pub

# Mark the key as trusted
coral key trust community
```

---

## Source Code Structure

```
/home/ctusa/repos/zygaena/
├── coral/                        # Package manager source
│   ├── reef.toml                 # Reef project config
│   └── src/
│       ├── main.reef             # Entry point, CLI dispatch
│       ├── commands/
│       │   ├── build.reef        # coral build
│       │   ├── install.reef      # coral install
│       │   ├── remove.reef       # coral remove
│       │   ├── upgrade.reef      # coral upgrade
│       │   ├── info.reef         # coral info
│       │   ├── list.reef         # coral list
│       │   ├── files.reef        # coral files
│       │   ├── owner.reef        # coral owner
│       │   ├── search.reef       # coral search
│       │   ├── depends.reef      # coral depends
│       │   ├── sync.reef         # coral sync
│       │   ├── diff.reef         # coral diff
│       │   ├── repo.reef         # coral repo (subcommands)
│       │   ├── key.reef          # coral key (subcommands)
│       │   └── cache.reef        # coral cache (subcommands)
│       ├── core/
│       │   ├── port.reef         # Port parsing (package.toml)
│       │   ├── package.reef      # Package archive handling
│       │   ├── database.reef     # Installed package database
│       │   ├── resolver.reef     # Dependency resolution
│       │   ├── config.reef       # Configuration loading
│       │   ├── repository.reef   # Repository manifest handling
│       │   └── signing.reef      # Ed25519 signing/verification
│       ├── util/
│       │   ├── http.reef         # HTTP downloads (wraps net.http)
│       │   ├── archive.reef      # Tar/xz operations
│       │   ├── checksum.reef     # SHA256 verification
│       │   ├── color.reef        # Terminal colors
│       │   └── prompt.reef       # User prompts
│       └── types.reef            # Shared type definitions
├── ports/                        # Port collection (Stage 2)
│   ├── core/
│   ├── base/
│   ├── devel/
│   ├── desktop/
│   └── extra/
├── docs/
│   ├── STAGE0_BOOTSTRAP.md
│   ├── STAGE1_CORAL_PLAN.md      # This document
│   └── ...
└── bootstrap/
```

---

## Port Categories

| Category | Description | Examples |
|----------|-------------|----------|
| **core** | Essential system packages | kernel, libc, coreutils, bash |
| **base** | Standard Unix utilities | vim, git, curl, openssh |
| **devel** | Development tools & libraries | gcc, cmake, python, rust, ncurses-dev |
| **desktop** | X11, window managers, GUI apps | xlibre, windowmaker, firefox |
| **extra** | Everything else | games, themes, misc utilities |

---

## Reef Language Notes

### Reserved Keywords
**IMPORTANT:** `run` is a reserved keyword for Active Objects in Reef. Command handlers must use `execute()` instead of `run()`.

### Key Syntax Rules
- Blocks end with `end` keyword: `end if`, `end for`, `end while`, `end FunctionName`, `end module`
- `fn` returns a value, `proc` does not return a value
- String interpolation: `"Hello, ${name}"` (simple variables only, not expressions)
- Mutable variables: `mut count = 0`
- Immutable variables: `let value = 42`
- Logical NOT: `not` (not `!`)
- Array allocation: `new [type](size)`
- Module declaration: `module path.to.module` ... `end module`
- Export block: `export` ... `end export`
- Entry point (`main.reef`) does NOT use a module declaration

### Module Structure Pattern
```reef
module module.name

export
    // Public interface declarations
    fn public_function(): type
    proc public_procedure()
end export

// Implementation
fn public_function(): type
    // ...
end public_function

// Private helpers (not in export block)
fn private_helper(): type
    // ...
end private_helper

end module
```

### TOML Parsing Pattern
The Reef stdlib uses parallel arrays for TOML parsing:
```reef
import encoding.toml

let keys = toml.toml_alloc_keys()      // Allocate keys array (max 256)
let vals = toml.toml_alloc_values()    // Allocate values array
let count = toml.toml_parse(content, keys, vals)
let name = toml.toml_get(keys, vals, count, "package.name")
let release = toml.toml_get_int(keys, vals, count, "package.release")
```

---

## Reef Standard Library Modules

```reef
// Core imports for Coral
import io.file            // readFile, writeFile, fileExists, readBinaryFile
import io.dir             // list_dir, dir_exists, create_dir_all, current_dir
import io.path            // join_path, dirname, basename, extension
import core.str           // split, join, contains, starts_with, ends_with, trim_ws
import sys.args           // count, get, has_flag (simple CLI parsing)
import sys.flag           // FlagParser for rich CLI parsing with help generation
import sys.process        // process_spawn_shell, process_wait
import sys.env            // get_env, set_env, has_env
import io.console         // readLine, confirm, printErr
import encoding.toml      // toml_parse, toml_get, toml_get_int, toml_has_key
import net.http           // HTTP client (HTTPS support coming from Reef team)
import time.clock         // Timestamps
import collections.list   // Dynamic lists
import collections.map    // Hash maps
import crypto.sha256      // SHA256 checksums (native, pure Reef implementation)
```

### Native vs Shell-out

| Functionality | Implementation | Notes |
|---------------|----------------|-------|
| **SHA256** | Native `crypto.sha256` | Pure Reef, supports string and binary data |
| **TOML parsing** | Native `encoding.toml` | Uses parallel arrays pattern |
| **HTTP** | Native `net.http` | HTTP now, HTTPS coming from Reef team |
| **File I/O** | Native `io.file`, `io.dir` | Full support |
| **Tar/xz** | Shell-out to `tar`, `xz` | System tools, acceptable |
| **Patch** | Shell-out to `patch` | Apply source patches (-p<strip>) |
| **Ed25519 signing** | Shell-out to `signify` | Until Reef adds crypto.ed25519 |
| **Git** | Shell-out to `git` | For `coral sync` ports update |
| **Mercurial** | Shell-out to `hg` | Alternative VCS for ports update |

---

## Core Data Types

```reef
// types.reef - Shared type definitions

// Package metadata from package.toml
type PackageInfo = struct
    name: string
    version: string
    release: int
    description: string
    url: string
    license: string
    maintainer: string
    arch: string
    category: string
    alternatives: [string]  // Alternative sets this package belongs to
    alternatives_count: int
    conflicts: [string]     // Packages that conflict with this
    conflicts_count: int
end PackageInfo

type Dependencies = struct
    runtime: [string]
    build: [string]
    optional: [string]      // Optional dependencies with descriptions
    runtime_count: int
    build_count: int
    optional_count: int
end Dependencies

type SourceInfo = struct
    urls: [string]
    checksums: [string]
    count: int
end SourceInfo

type PatchInfo = struct
    files: [string]         // Patch filenames in order
    strip: int              // Strip level for patch -p (default: 1)
    count: int              // Number of patches
end PatchInfo

type ConfigInfo = struct
    files: [string]         // Config files to protect (installed as .pkg)
    count: int
end ConfigInfo

type ScriptsInfo = struct
    pre_install: string     // Script filename or empty
    post_install: string
    pre_remove: string
    post_remove: string
end ScriptsInfo

type BuildConfig = struct
    parallel: bool
    jobs: int
end BuildConfig

// Complete port definition
type Port = struct
    info: PackageInfo
    deps: Dependencies
    sources: SourceInfo
    patches: PatchInfo
    config: ConfigInfo
    scripts: ScriptsInfo
    build_cfg: BuildConfig
    port_dir: string
end Port

// Installed package record
type InstalledPackage = struct
    info: PackageInfo
    install_date: string
    install_reason: string    // "explicit" or "dependency"
    files: [string]           // All files owned by this package
    files_count: int
    config_files: [string]    // Config files (for upgrade handling)
    config_count: int
    repository: string        // Which repo it came from
end InstalledPackage

// Remote package from repository manifest
type RemotePackage = struct
    info: PackageInfo
    depends: [string]
    optdepends: [string]
    provides: [string]
    conflicts: [string]
    size: int                 // Installed size
    compressed: int           // Download size
    filename: string
    sha256: string
    signature: string
    depends_count: int
end RemotePackage

// Repository definition
type Repository = struct
    name: string
    description: string
    enabled: bool
    priority: int
    mirrors: [string]
    mirror_count: int
    keyfile: string
    require_signed: bool
end Repository

// Build result
type BuildResult = struct
    success: bool
    package_path: string
    error_message: string
end BuildResult

// Signing key
type SigningKey = struct
    name: string
    public_key: string
    fingerprint: string
    trusted: bool
    created: string
end SigningKey
```

---

## Key Implementation Details

### 1. Main Entry Point (main.reef)

```reef
// main.reef - Entry point (no module declaration)
// Entry point files do NOT use module declarations in Reef

import sys.args
import commands.build
import commands.install
import commands.remove
import commands.repo
import commands.key
import commands.cache
// ... other imports

proc main()
    // Check for root (except for query commands)
    if not is_query_command() and not is_root()
        print_error("coral: must be run as root")
        return
    end if

    if args.count() < 2
        print_usage()
        return
    end if

    let cmd = args.get(1)

    // Parse global options
    let opts = parse_global_options()

    // Dispatch to subcommand
    if cmd == "build"
        build.execute(opts)
    elif cmd == "install"
        install.execute(opts)
    elif cmd == "remove"
        remove.execute(opts)
    elif cmd == "upgrade"
        upgrade.execute(opts)
    elif cmd == "info"
        info.execute(opts)
    elif cmd == "list"
        list.execute(opts)
    elif cmd == "files"
        files.execute(opts)
    elif cmd == "owner"
        owner.execute(opts)
    elif cmd == "search"
        search.execute(opts)
    elif cmd == "depends"
        depends.execute(opts)
    elif cmd == "sync"
        sync.execute(opts)
    elif cmd == "diff"
        diff.execute(opts)
    elif cmd == "repo"
        repo.execute(opts)
    elif cmd == "key"
        key.execute(opts)
    elif cmd == "cache"
        cache.execute(opts)
    elif cmd == "--help" or cmd == "-h"
        print_usage()
    elif cmd == "--version"
        println("coral 1.0.0")
    else
        print_error("coral: unknown command '${cmd}'")
        print_usage()
    end if
end main

fn is_query_command(): bool
    let cmd = args.get(1)
    return cmd == "info" or cmd == "list" or cmd == "files" or
           cmd == "owner" or cmd == "search" or cmd == "depends" or
           cmd == "diff" or cmd == "--help" or cmd == "-h" or
           cmd == "--version" or cmd == "cache"
end is_query_command

fn is_root(): bool
    // Check if running as root (uid 0)
    let uid_str = env.get_env("EUID")
    if str.length(uid_str) == 0
        // Fallback: try id command
        // For now assume root if EUID not set
        return true
    end if
    return uid_str == "0"
end is_root
```

### 2. Repository Management (commands/repo.reef)

```reef
module commands.repo

import sys.args
import io.file
import io.dir
import io.path
import encoding.toml
import core.signing
import util.color

proc execute(opts: GlobalOptions)
    if args.count() < 3
        print_repo_usage()
        return
    end if

    let subcmd = args.get(2)

    if subcmd == "init"
        repo_init(opts)
    elif subcmd == "add"
        repo_add(opts)
    elif subcmd == "remove"
        repo_remove(opts)
    elif subcmd == "list"
        repo_list(opts)
    elif subcmd == "rebuild"
        repo_rebuild(opts)
    elif subcmd == "sign"
        repo_sign(opts)
    elif subcmd == "verify"
        repo_verify(opts)
    elif subcmd == "sync"
        repo_sync(opts)
    elif subcmd == "export"
        repo_export(opts)
    else
        print_error("Unknown repo subcommand: ${subcmd}")
        print_repo_usage()
    end if
end run

proc repo_init(opts: GlobalOptions)
    if args.count() < 4
        print_error("Usage: coral repo init <path>")
        return
    end if

    let repo_path = args.get(3)

    // Create directory structure
    dir.create_dir_all(path.join_path(repo_path, "packages"))

    // Create initial repo.toml
    let manifest = """
[repository]
name = "unnamed"
description = "New repository"
maintainer = ""
arch = "x86_64"
generated = ""
package_count = 0
base_url = "packages/"
signing_key = ""
"""

    file.writeFile(path.join_path(repo_path, "repo.toml"), manifest)
    print_success("Repository initialized at ${repo_path}")
end repo_init

proc repo_rebuild(opts: GlobalOptions)
    if args.count() < 4
        print_error("Usage: coral repo rebuild <path>")
        return
    end if

    let repo_path = args.get(3)
    let pkg_dir = path.join_path(repo_path, "packages")

    // Scan all .pkg.tar.xz files
    let packages = dir.list_dir(pkg_dir)

    // Build manifest from package metadata
    // ... extract .PKGINFO from each package
    // ... generate repo.toml

    print_success("Repository manifest rebuilt")
end repo_rebuild

proc repo_sign(opts: GlobalOptions)
    if args.count() < 4
        print_error("Usage: coral repo sign <path> [--key <name>]")
        return
    end if

    let repo_path = args.get(3)
    let manifest_path = path.join_path(repo_path, "repo.toml")

    let key_name = args.get_flag_value("key")
    if str.length(key_name) == 0
        key_name = "default"
    end if

    // Sign the manifest
    let sig = signing.sign_file(manifest_path, key_name)
    file.writeFile(manifest_path + ".sig", sig)

    print_success("Repository signed with key '${key_name}'")
end repo_sign
```

### 3. Key Management (commands/key.reef)

```reef
module commands.key

import sys.args
import io.file
import io.dir
import io.path
import core.signing
import util.color

proc execute(opts: GlobalOptions)
    if args.count() < 3
        print_key_usage()
        return
    end if

    let subcmd = args.get(2)

    if subcmd == "init"
        key_init(opts)
    elif subcmd == "generate"
        key_generate(opts)
    elif subcmd == "list"
        key_list(opts)
    elif subcmd == "import"
        key_import(opts)
    elif subcmd == "export"
        key_export(opts)
    elif subcmd == "trust"
        key_trust(opts)
    elif subcmd == "revoke"
        key_revoke(opts)
    else
        print_error("Unknown key subcommand: ${subcmd}")
        print_key_usage()
    end if
end run

proc key_generate(opts: GlobalOptions)
    if args.count() < 4
        print_error("Usage: coral key generate <name>")
        return
    end if

    let name = args.get(3)
    let keys_dir = "/etc/coral/keys"

    // Generate Ed25519 keypair
    let keypair = signing.generate_keypair()

    // Save private key (restricted permissions)
    let sec_path = path.join_path(keys_dir, name + ".sec")
    file.writeFile(sec_path, keypair.private_key)
    // TODO: chmod 600 on sec_path

    // Save public key
    let pub_path = path.join_path(keys_dir, name + ".pub")
    file.writeFile(pub_path, keypair.public_key)

    print_success("Generated keypair '${name}'")
    println("  Private key: ${sec_path}")
    println("  Public key:  ${pub_path}")
end key_generate

proc key_list(opts: GlobalOptions)
    let keys_dir = "/etc/coral/keys"
    let files = dir.list_dir(keys_dir)

    println("Available signing keys:")
    println("")

    for f in files
        if str.ends_with(f, ".pub")
            let name = str.replace(f, ".pub", "")
            let has_private = file.fileExists(path.join_path(keys_dir, name + ".sec"))
            let key_type = "public"
            if has_private
                key_type = "public + private"
            end if
            println("  ${name} (${key_type})")
        end if
    end for
end key_list
```

### 4. Terminal Colors (util/color.reef)

```reef
module util.color

import sys.env
import sys.args

// ANSI color codes
fn red(s: string): string
    if no_color()
        return s
    end if
    return "\x1b[31m" + s + "\x1b[0m"
end red

fn green(s: string): string
    if no_color()
        return s
    end if
    return "\x1b[32m" + s + "\x1b[0m"
end green

fn yellow(s: string): string
    if no_color()
        return s
    end if
    return "\x1b[33m" + s + "\x1b[0m"
end yellow

fn blue(s: string): string
    if no_color()
        return s
    end if
    return "\x1b[34m" + s + "\x1b[0m"
end blue

fn bold(s: string): string
    if no_color()
        return s
    end if
    return "\x1b[1m" + s + "\x1b[0m"
end bold

fn no_color(): bool
    // Check --no-color flag or NO_COLOR env var
    return args.has_flag("no-color") or env.has_env("NO_COLOR")
end no_color

// Formatted output helpers
proc print_success(msg: string)
    println(green("✓") + " " + msg)
end print_success

proc print_error(msg: string)
    println(red("✗") + " " + msg)
end print_error

proc print_warning(msg: string)
    println(yellow("!") + " " + msg)
end print_warning

proc print_info(msg: string)
    println(blue("→") + " " + msg)
end print_info
```

### 5. User Prompts (util/prompt.reef)

```reef
module util.prompt

import sys.args
import io.console
import core.str

fn confirm(message: string): bool
    // Skip if -y flag
    if args.has_flag("y") or args.has_flag("yes")
        return true
    end if

    print(message + " [Y/n] ")
    let response = console.readLine()
    let r = str.trim_ws(response)

    // Empty or Y/y means yes
    return str.length(r) == 0 or r == "Y" or r == "y" or r == "yes"
end confirm

fn confirm_packages(action: string, packages: [string], count: int): bool
    if count == 0
        return false
    end if

    println("")
    println("Packages to ${action}:")
    for i in 0 to count
        println("  " + packages[i])
    end for
    println("")

    return confirm("Proceed?")
end confirm_packages

fn ask_build_from_source(pkg_name: string): bool
    println("")
    print_warning("Binary package not available for '${pkg_name}'")
    return confirm("Build from source?")
end ask_build_from_source
```

### 6. HTTP Downloads (util/http.reef)

```reef
module util.http

import net.http
import io.file
import util.color
import core.config

// Download a file from URL to destination
// Returns true on success
fn download(url: string, dest: string): bool
    print_info("Downloading: ${url}")

    let cfg = config.load()

    // Try native Reef HTTP first
    let result = http.get(url, {
        timeout: cfg.network.timeout,
        retries: cfg.network.retries
    })

    if result.status_code == 200
        file.writeFile(dest, result.body)
        return true
    elif result.status_code >= 300 and result.status_code < 400
        // Handle redirect
        let redirect_url = result.headers["Location"]
        if str.length(redirect_url) > 0
            return download(redirect_url, dest)
        end if
    end if

    print_error("HTTP error: ${result.status_code}")
    return false
end download

// Download with progress callback
fn download_with_progress(url: string, dest: string, callback: fn(int, int)): bool
    // Similar to download but calls callback(bytes_received, total_bytes)
    // Implementation depends on net.http capabilities
    return download(url, dest)  // Fallback for now
end download_with_progress

// Note: If net.http fails on illumos, we'll report to Reef team
// and can add a fallback to curl if absolutely necessary
```

### 7. Build Command (commands/build.reef)

```reef
module commands.build

import sys.args
import io.file
import io.dir
import io.path
import core.port
import core.package
import util.color
import util.http
import util.checksum

proc execute(opts: GlobalOptions)
    if args.count() < 3
        print_error("Usage: coral build <port-directory>")
        return
    end if

    let port_path = args.get(2)

    // Handle "." for current directory
    let actual_path = port_path
    if port_path == "."
        actual_path = dir.current_dir()
    end if

    // Load port
    let port_result = port.load(actual_path)
    if not port_result.success
        print_error("Failed to load port: ${port_result.error}")
        return
    end if

    let p = port_result.port

    println(bold("Building ${p.info.name}-${p.info.version}"))
    println("")

    // Step 1: Download sources
    print_info("Downloading sources...")
    if not download_sources(p)
        print_error("Failed to download sources")
        return
    end if

    // Step 2: Verify checksums
    print_info("Verifying checksums...")
    if not verify_sources(p)
        print_error("Checksum verification failed")
        return
    end if

    // Step 3: Extract sources
    print_info("Extracting sources...")
    if not extract_sources(p)
        print_error("Failed to extract sources")
        return
    end if

    // Step 4: Apply patches (if any)
    if p.patches.count > 0
        print_info("Applying patches...")
        if not apply_patches(p)
            print_error("Failed to apply patches")
            return
        end if
    end if

    // Step 5: Run build
    print_info("Building...")
    if not run_build(p, opts.verbose)
        print_error("Build failed")
        return
    end if

    // Step 6: Create package
    print_info("Creating package...")
    let pkg_path = create_package(p)
    if str.length(pkg_path) == 0
        print_error("Failed to create package")
        return
    end if

    println("")
    print_success("Package created: ${pkg_path}")
end run

fn download_sources(p: Port): bool
    for i in 0 to p.sources.count
        let url = p.sources.urls[i]
        let filename = path.basename(url)
        let dest = path.join_path(SOURCES_DIR, filename)

        if file.fileExists(dest)
            println("  " + green("✓") + " ${filename} (cached)")
            continue
        end if

        if not http.download(url, dest)
            return false
        end if
    end for
    return true
end download_sources

fn apply_patches(p: Port): bool
    let work_dir = path.join_path(WORK_DIR, p.info.name)
    let patches_dir = path.join_path(p.port_dir, "patches")
    let strip = p.patches.strip

    for i in 0 to p.patches.count
        let patch_file = p.patches.files[i]
        let patch_path = path.join_path(patches_dir, patch_file)

        println("  Applying: ${patch_file}")

        // patch -p<strip> -d <work_dir> < <patch_path>
        let cmd = "patch -p" + to_string(strip) + " -d " + work_dir + " < " + patch_path
        let pid = process.process_spawn_shell(cmd)
        let exit_code = process.process_wait(pid)

        if exit_code != 0
            print_error("Patch failed: ${patch_file}")
            return false
        end if
    end for
    return true
end apply_patches

fn create_package(p: Port): string
    let pkg_name = p.info.name + "-" + p.info.version + "-" +
                   to_string(p.info.release) + ".pkg.tar.xz"
    let pkg_path = path.join_path(PACKAGES_DIR, pkg_name)
    let pkg_staging = path.join_path(PKG_DIR, p.info.name)

    // Create .PKGINFO
    package.create_pkginfo(p, pkg_staging)

    // Create .FOOTPRINT
    package.create_footprint(pkg_staging)

    // Create tarball with xz compression
    let cmd = "cd " + pkg_staging + " && tar -Jcf " + pkg_path + " ."
    let pid = process.process_spawn_shell(cmd)
    let exit_code = process.process_wait(pid)

    if exit_code != 0
        return ""
    end if

    return pkg_path
end create_package
```

### 8. Install Command (commands/install.reef)

```reef
module commands.install

import sys.args
import core.resolver
import core.database
import core.port
import core.repository
import core.config
import util.color
import util.prompt

proc execute(opts: GlobalOptions)
    if args.count() < 3
        print_error("Usage: coral install <package...>")
        return
    end if

    // Collect package names from args
    let packages: [string] = new [string](50)
    mut pkg_count = 0
    for i in 2 to args.count()
        let arg = args.get(i)
        if not str.starts_with(arg, "-")
            packages[pkg_count] = arg
            pkg_count = pkg_count + 1
        end if
    end for

    // Resolve dependencies for all packages
    let to_install: [string] = new [string](100)
    mut install_count = 0

    for i in 0 to pkg_count
        install_count = resolver.resolve(packages[i], to_install, install_count)
    end for

    // Filter out already installed
    let needed: [string] = new [string](100)
    mut needed_count = 0
    for i in 0 to install_count
        if not database.is_installed(to_install[i])
            needed[needed_count] = to_install[i]
            needed_count = needed_count + 1
        end if
    end for

    if needed_count == 0
        print_info("All packages are already installed")
        return
    end if

    // Confirm with user
    if not prompt.confirm_packages("install", needed, needed_count)
        println("Aborted.")
        return
    end if

    let cfg = config.load()

    // Install each package
    for i in 0 to needed_count
        let pkg_name = needed[i]
        println("")
        println(bold("==> Installing ${pkg_name}"))

        // Try to get binary package from repository first
        let remote_pkg = repository.find_package(pkg_name)

        if str.length(remote_pkg.filename) > 0
            // Binary package available
            print_info("Downloading ${pkg_name}...")
            let pkg_path = repository.download_package(remote_pkg)

            if str.length(pkg_path) > 0
                print_info("Installing ${pkg_name}...")
                if install_package(pkg_path, pkg_name, opts)
                    print_success("Installed ${pkg_name}")
                    continue
                end if
            end if
        end if

        // Binary not available or download failed - try source
        let build_from_source = false
        if cfg.build.fallback_to_source == "yes"
            build_from_source = true
        elif cfg.build.fallback_to_source == "ask"
            build_from_source = prompt.ask_build_from_source(pkg_name)
        end if

        if not build_from_source
            print_error("Package not available: ${pkg_name}")
            return
        end if

        // Find port and build
        let p = port.find(pkg_name)
        if str.length(p.info.name) == 0
            print_error("Port not found: ${pkg_name}")
            return
        end if

        print_info("Building ${pkg_name}...")
        let build_result = build_package(p, opts)
        if not build_result.success
            print_error("Build failed: ${build_result.error_message}")
            return
        end if

        print_info("Installing ${pkg_name}...")
        if not install_package(build_result.package_path, pkg_name, opts)
            print_error("Installation failed")
            return
        end if

        print_success("Installed ${pkg_name}")
    end for

    println("")
    print_success("Installation complete!")
end run
```

---

## Directory Constants

```reef
// Zygaena/Coral paths
const PORTS_DIR: string = "/usr/ports"
const SOURCES_DIR: string = "/var/lib/coral/sources"
const PACKAGES_DIR: string = "/var/lib/coral/packages"
const REPOS_DIR: string = "/var/lib/coral/repos"
const WORK_DIR: string = "/var/tmp/coral/work"
const PKG_DIR: string = "/var/tmp/coral/pkg"
const DB_DIR: string = "/var/lib/coral/installed"
const KEYS_DIR: string = "/etc/coral/keys"
const CONFIG_FILE: string = "/etc/coral/coral.conf"
const REPOS_CONFIG_DIR: string = "/etc/coral/repos.d"
```

---

## Package Format

### Port Structure
```
/usr/ports/<category>/<package>/
├── package.toml          # Package metadata (TOML)
├── build.reef            # Build commands (Reef)
├── files/                # Additional files (optional)
│   └── config.example
├── patches/              # Source patches (optional)
│   └── fix-illumos.patch
└── scripts/              # Install scripts (optional)
    ├── pre-install.reef
    ├── post-install.reef
    ├── pre-remove.reef
    └── post-remove.reef
```

### package.toml Example
```toml
[package]
name = "vim"
version = "9.1.0"
release = 1
description = "Vi IMproved - enhanced vi editor"
url = "https://www.vim.org"
license = "Vim"
maintainer = "[email protected]"

# Alternative sets this package belongs to
alternatives = ["vi", "editor"]

# Packages this conflicts with (cannot be installed together)
conflicts = ["vim-minimal"]

[dependencies]
runtime = ["ncurses"]
build = ["ncurses-dev"]
optional = [
    "python:Python scripting support",
    "lua:Lua scripting support"
]

[sources]
urls = [
    "https://github.com/vim/vim/archive/v${version}.tar.gz"
]
checksums = [
    "abc123def456..."
]

[patches]
# Patches are applied in order listed, after source extraction
# Files are relative to the patches/ directory in the port
files = [
    "fix-illumos-termios.patch",
    "fix-ncurses-detection.patch"
]
# Strip level for patch command (default: 1)
strip = 1

[config]
# Config files to protect during upgrades
# These are installed as <path>.pkg, user's version preserved as <path>
files = [
    "/etc/vim/vimrc"
]

[scripts]
# Install scripts (Reef files in scripts/ directory)
pre_install = "pre-install.reef"
post_install = "post-install.reef"
pre_remove = "pre-remove.reef"
post_remove = "post-remove.reef"

[build]
parallel = true
jobs = 0
```

### build.reef Example
```reef
// Build script for vim

fn configure(): string
    return "./configure --prefix=/usr --with-features=huge"
end configure

fn build(): string
    return "gmake -j${JOBS}"
end build

fn install(): string
    return "gmake install DESTDIR=${PKG}"
end install
```

### Package Groups (Meta-packages)

Package groups are collections of packages that can be installed together as a bundle.
Useful for large products like desktop environments or curated package sets.

Groups are defined in `/usr/ports/groups/` as simple TOML files:

```toml
# /usr/ports/groups/kde6.toml
[group]
name = "kde6"
description = "KDE Plasma 6 Desktop Environment"

[packages]
# All packages installed when user runs: coral install @kde6
members = [
    "kde-plasma6",
    "kde-kwin",
    "kde-extras",
    "kde-apps-base",
    "sddm"
]
```

Usage:
```
$ coral install @kde6
Installing group 'kde6' (5 packages):
  kde-plasma6, kde-kwin, kde-extras, kde-apps-base, sddm

Proceed? [Y/n]
```

The `@` prefix indicates a group name rather than a package name.

### Alternatives (Dependency Options)

Alternatives allow a dependency to be satisfied by one of several packages.
Packages declare which alternative sets they belong to, and dependencies
can require any member of that set.

```toml
# In postfix/package.toml
[package]
name = "postfix"
alternatives = ["mta"]    # Member of the "mta" alternative set

# In opensmtpd/package.toml
[package]
name = "opensmtpd"
alternatives = ["mta"]    # Also a member of "mta"

# In mailman/package.toml - needs ANY mta
[dependencies]
runtime = ["mta:postfix,opensmtpd,sendmail"]  # Pick one from this list
```

When installing a package with alternatives:
```
$ coral install mailman

Resolving dependencies...
Package 'mailman' requires 'mta' (one of: postfix, opensmtpd, sendmail)

No provider for 'mta' is installed.
Available options:
  1) postfix
  2) opensmtpd
  3) sendmail

Select [1-3]: 2

Packages to install:
  opensmtpd
  mailman

Proceed? [Y/n]
```

If any member of the alternative set is already installed, the dependency is satisfied automatically.

### Conflict Detection

Coral prevents file clobbering through two mechanisms:

1. **Explicit conflicts** - Declared in package.toml:
   ```toml
   conflicts = ["vim-minimal", "vim-tiny"]
   ```

2. **File ownership tracking** - The installed package database tracks which package owns each file. During install:
   - If a file exists and is owned by another package → **error** (unless `--force`)
   - If a file exists and is not owned by any package → **warning** (orphan file)

### Config File Protection

Config files listed in `[config].files` are protected during install/upgrade:

| Scenario | Behavior |
|----------|----------|
| **Fresh install** | Package file installed as `/etc/foo.conf.pkg`, user copies to `/etc/foo.conf` |
| **Upgrade, user hasn't modified** | Replace both `.conf` and `.conf.pkg` |
| **Upgrade, user has modified** | Update `.conf.pkg` only, preserve user's `.conf` |
| **Remove** | Remove `.conf.pkg`, warn if `.conf` differs |

This ensures the user's customizations are never clobbered. The `.pkg` file always contains the package maintainer's version for reference.

### Install Scripts

Install scripts are Reef programs executed at specific points:

| Script | When | Use Cases |
|--------|------|-----------|
| `pre-install.reef` | Before files extracted | Create users/groups, stop services |
| `post-install.reef` | After files installed | Run ldconfig, enable service, print notes |
| `pre-remove.reef` | Before files removed | Stop services, backup data |
| `post-remove.reef` | After files removed | Remove users, cleanup state |

Example `post-install.reef`:
```reef
// Post-install script for nginx

proc main()
    // Run ldconfig to register shared libraries
    let pid = process.process_spawn_shell("/usr/sbin/ldconfig")
    process.process_wait(pid)

    // Print post-install notes
    println("")
    println("nginx installed successfully!")
    println("Enable with: svcadm enable nginx")
    println("Config file: /etc/nginx/nginx.conf.pkg")
    println("Copy to /etc/nginx/nginx.conf and customize.")
end main
```

### Binary Package (.pkg.tar.xz)
```
vim-9.1.0-1.pkg.tar.xz
├── .PKGINFO              # Package metadata (TOML)
├── .FOOTPRINT            # File listing with permissions
├── .SCRIPTS/             # Install scripts (if any)
│   ├── pre-install.reef
│   └── post-install.reef
├── usr/
│   ├── bin/vim
│   ├── share/vim/
│   └── share/man/
```

---

## Implementation Schedule

### Phase 1: Core Infrastructure (Week 1-2)
- [ ] Create directory structure
- [ ] Set up reef.toml
- [ ] Implement types.reef
- [ ] Implement util/color.reef
- [ ] Implement util/prompt.reef
- [ ] Implement core/config.reef (configuration loading)
- [ ] Implement main.reef (CLI dispatch)
- [ ] Test net.http on illumos, report issues

### Phase 2: Build System (Week 3-4)
- [ ] Implement core/port.reef (TOML parsing)
- [ ] Implement util/http.reef (downloads)
- [ ] Implement util/checksum.reef (SHA256)
- [ ] Implement util/archive.reef (tar/xz)
- [ ] Implement commands/build.reef
- [ ] Test with hello port

### Phase 3: Package Management (Week 5-6)
- [ ] Implement core/database.reef
- [ ] Implement core/package.reef
- [ ] Implement commands/install.reef
- [ ] Implement commands/remove.reef
- [ ] Implement commands/upgrade.reef
- [ ] Implement commands/cache.reef

### Phase 4: Query Commands (Week 7)
- [ ] Implement commands/info.reef
- [ ] Implement commands/list.reef
- [ ] Implement commands/files.reef
- [ ] Implement commands/owner.reef
- [ ] Implement commands/search.reef

### Phase 5: Repository System (Week 8-9)
- [ ] Implement core/repository.reef (manifest parsing)
- [ ] Implement core/signing.reef (Ed25519)
- [ ] Implement commands/sync.reef (auto-detect git vs hg)
- [ ] Implement commands/repo.reef (all subcommands)
- [ ] Implement commands/key.reef (all subcommands)

### Phase 6: Advanced Features (Week 10)
- [ ] Implement core/resolver.reef (dependency resolution)
- [ ] Implement commands/depends.reef
- [ ] Implement commands/diff.reef
- [ ] Error handling improvements
- [ ] Testing with multiple packages

### Phase 7: Polish & Documentation (Week 11-12)
- [ ] Comprehensive testing
- [ ] Man page generation
- [ ] User documentation
- [ ] Repository setup guide
- [ ] Third-party repo guide

---

## Test Packages

| Package | Category | Dependencies | Notes |
|---------|----------|--------------|-------|
| hello | core | None | GNU Hello, autotools |
| tree | base | None | Single C file |
| ncurses | devel | None | Library, needed by vim |
| vim | base | ncurses | Tests dependency resolution |
| jq | base | oniguruma | JSON processor |

---

## Success Criteria

### MVP (Minimum Viable Product)
- [ ] `coral build` creates a package from a port
- [ ] `coral install` installs packages with dependencies
- [ ] `coral remove` removes packages
- [ ] `coral list` shows installed packages
- [ ] `coral search` finds ports
- [ ] Colored output works
- [ ] Confirmation prompts work

### Full Release
- [ ] All subcommands functional (build, install, remove, upgrade, info, list, files, owner, search, depends, sync, diff, repo, key, cache)
- [ ] Repository manifest system working
- [ ] Package signing and verification working
- [ ] Binary package download from mirrors working
- [ ] Source fallback working with user prompts
- [ ] 10+ packages buildable
- [ ] Complex dependency chains work
- [ ] Native HTTP working (or issues reported)
- [ ] Clean error messages
- [ ] Ready for Stage 2 (Core Ports)

---

## Distribution Infrastructure

### Official Repository Structure

```
https://pkg.zygaena.org/
├── stable/
│   └── x86_64/
│       ├── repo.toml           # Repository manifest
│       ├── repo.toml.sig       # Manifest signature
│       └── packages/
│           ├── bash-5.2.21-1.pkg.tar.xz
│           ├── bash-5.2.21-1.pkg.tar.xz.sig
│           └── ...
├── testing/
│   └── x86_64/
│       └── ...
└── keys/
    ├── official-2026.pub       # Current signing key
    └── official-2025.pub       # Previous key (for verification)
```

### Mirror Sync Process

1. **Build Server** (CI/CD):
   - Builds packages from ports
   - Signs packages with official key
   - Generates repo.toml manifest
   - Signs manifest
   - Uploads to master server

2. **Master Server**:
   - Authoritative package repository
   - Accepts uploads from build server only
   - Serves rsync to mirrors

3. **Mirror Servers**:
   - Sync from master via rsync
   - Serve packages via HTTP/HTTPS
   - Listed in official mirror list

### ISO/USB Distribution

For offline installation or bootstrapping:

```bash
# Export repository for ISO image
coral repo export /srv/packages /mnt/iso/packages

# Creates:
# /mnt/iso/packages/
# ├── repo.toml
# ├── repo.toml.sig
# └── packages/
#     └── *.pkg.tar.xz
```

---

## Notes for Reef Team

### net.http Testing on illumos
Will test the following and report issues:
1. Basic HTTP GET request
2. HTTPS (TLS) support
3. Redirect handling (301, 302)
4. Large file downloads
5. Connection timeout handling
6. Progress callbacks (if supported)

### Potential illumos-specific issues
- Socket syscall constants
- DNS resolution paths
- TLS library linking

### crypto.ed25519 Requirements
If not available in Reef stdlib, we will use the `signify` command-line tool.
This is acceptable as signify is a standard tool on BSD/illumos systems.

### Shell-out Acceptability
The following shell-outs are acceptable and expected:
- `tar` / `xz` - Archive operations (standard system tools)
- `patch` - Apply source patches with configurable strip level
- `signify` - Ed25519 signing (until Reef adds native support)
- `git` / `hg` - Ports collection sync (auto-detect VCS type)

---

## Revision History

| Version | Date | Changes |
|---------|------|---------|
| 1.0 | January 2026 | Initial plan created |
| 2.0 | January 2026 | Added configuration, repository system, signing, key management, cache management |
| 2.1 | January 2026 | Clarified native vs shell-out; confirmed SHA256 is native via crypto.sha256; added Mercurial support for ports sync |
| 2.2 | January 2026 | Added [patches] section to package.toml with ordered patch files and strip level |
| 2.3 | January 2026 | Added conflict detection, config file protection (.pkg), and install scripts |
| 2.4 | January 2026 | Clarified package groups (meta-packages) vs alternatives (dependency options) |
| 2.5 | January 2026 | Added Reef language notes; Fixed syntax (run -> execute is reserved keyword); Updated module paths; Verified against Reef 0.1.3 spec |
