Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Go
on:
workflow_dispatch:
pull_request:
branches:
- master
- main
push:
branches:
- master
- main
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ 1.26.5 ]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
check-latest: true
- name: Check gofmt
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "not gofmt-clean:"
echo "$unformatted"
gofmt -d .
exit 1
fi
shell: bash
- name: Build
run: go build ./...
shell: bash
- name: Vet
run: go vet ./...
shell: bash
- name: Test
run: go test -v ./...
shell: bash
1 change: 1 addition & 0 deletions .github/workflows/vulncheck.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: VulnCheck
on:
workflow_dispatch:
pull_request:
branches:
- master
Expand Down
228 changes: 119 additions & 109 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,181 +4,191 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Overview

pkger is a packaging tool for MinIO projects that generates DEB, RPM, and APK packages along with installation metadata JSON files. It's built in Go as a single-file application (`main.go`) that uses the nfpm library for package generation.
pkger is a packaging tool for MinIO/AIStor projects that generates DEB, RPM, and APK packages along with the download metadata JSON consumed by min.io/download. It's built in Go as a single-file application (`main.go`) that uses the nfpm library for package generation.

## Building and Running

Build the project:
```bash
go build -o pkger main.go
go build -o pkger .
```

The binary is self-contained and uses command-line flags for all configuration.

## Core Architecture

### Single-file Design
The entire application is in `main.go` (~1000 lines). Key components:
- **Command-line parsing**: Uses `kingpin` for flag handling
- **Package generation**: Uses `goreleaser/nfpm/v2` library with support for deb, rpm, and apk formats
- **Template system**: Uses Go's `text/template` for generating nfpm config (lines 88-119)
- **JSON generation**: Creates download metadata files for different applications and platforms

### Application Types
pkger supports multiple MinIO applications, each with different versioning and architecture requirements:
The entire application is in `main.go`. Key components:

1. **minio/mc**: Date-based releases (e.g., `RELEASE.2025-03-12T00-00-00Z`)
- Supports: amd64, arm64, ppc64le
- Generates packages and cross-platform download metadata
- **Command-line parsing**: `kingpin` for flag handling
- **Package generation**: `goreleaser/nfpm/v2`, supporting deb, rpm and apk
- **Template system**: Go `text/template` renders an nfpm config per architecture (`const tmpl`)
- **JSON generation**: download metadata for min.io/download

2. **minio-enterprise/mc-enterprise**: Enterprise variants (date-based)
- Supports: amd64, arm64 only
- Generates AIStor-branded download URLs
### Supported Apps

3. **sidekick**: Load balancer (date-based releases)
- Supports: amd64, arm64 only
- Generates package-only metadata (no binary downloads)
Every app packages for **`linux/amd64` and `linux/arm64` only** — see `pkgArches`. There is no ppc64le, and no community `minio`/`mc` app; those were removed along with the last ppc64le build.

4. **warp**: Benchmarking tool (semantic versioning, e.g., `v0.4.3`)
- Supports: amd64, arm64 only
- Strips 'v' prefix from package filenames
- Cross-platform: Linux, macOS (arm64), Windows (amd64)
| `--appName` | Release dir | Binary read | Package name | Versioning | Metadata |
| ----------- | ------------------ | ----------- | ------------ | ---------- | -------------------------------------------- |
| `aistor` | `minio-release` | `minio` | `minio` | date-based | `downloads-aistor.json` (AIStor + MinIO KMS) |
| `ac` | `mc-release` | `mc` | `mcli` | date-based | `downloads-ac.json` (AIStor Client) |
| `sidekick` | `sidekick-release` | `sidekick` | `sidekick` | date-based | `downloads-sidekick.json` (Linux + Windows) |
| `warp` | `warp-release` | `warp` | `warp` | semver | `downloads-warp.json` (cross-platform) |
| `memkv` | `memkv-release` | `memkv` | `memkv` | date-based | empty document |
| `aimem` | `aimem-release` | `aimem` | `aimem` | date-based | empty document |
| `minfs` | `minfs-release` | `minfs` | `minfs` | date-based | empty document |

`aistor` and `ac` were renamed from `minio-enterprise` and `mc-enterprise`. **Only the app name changed.** The release directory, the binary read from it, the package name, and every `dl.min.io` path segment are deliberately unchanged, so published artifacts are byte-identical across the rename. When touching `releaseDirName`, `defaultPkgName` or `defaultBinarySrcName`, re-key the switch on the new app name but keep the returned on-disk name.

`memkv`, `aimem` and `minfs` fall through to `generateDownloadsJSON`, which intentionally returns an empty document — the packages ship, but these apps have no dl.min.io download page to link.

### Version Handling
- **Date-based** (`RELEASE.2025-03-12T00-00-00Z`): Converted to semver format `20250312000000.0.0` via `semVerRelease()` (lines 793-806)
- **Semantic** (`v0.4.3` for warp): Validated and 'v' prefix stripped (lines 731-744)

- **Date-based** (`RELEASE.2025-03-12T00-00-00Z`): converted to semver `20250312000000.0.0` via `semVerRelease()`
- **Semantic** (`v0.4.3`, warp only): validated against `vX.Y.Z` and the `v` prefix stripped for package filenames

### Package Generation Flow
1. Parse release tag and convert to appropriate version format
2. For each architecture (filtered by app requirements):
- Generate nfpm config from template (lines 865-920)
- Create packages in `{appName}-release/linux-{arch}/` directory
- Generate SHA256 checksums for each package
- Create symlinks for latest package
3. Generate `downloads-{appName}.json` metadata file

1. Parse the release tag and convert it to the appropriate version format
2. For each architecture in `pkgArches`:
- Render the nfpm config from the template
- Write packages to `{releaseDir}/linux-{arch}/`
- Write a `.sha256sum` beside each package
- Symlink a stable "latest" alias (e.g. `minio.deb`), plus the legacy filenames when `--package-name` renamed the package
3. Write `downloads-{appName}.json` (or `downloads-{appName}-edge.json` with `--edge`)

## Common Commands

Run pkger for minio (date-based release):
```bash
# Requires: minio.service file and binaries in dist/linux-{arch}/
pkger -r RELEASE.2025-03-12T00-00-00Z --appName minio --releaseDir=dist
```
# aistor (date-based); needs minio.service and binaries in dist/linux-{arch}/
pkger -r RELEASE.2025-03-12T00-00-00Z --appName aistor --releaseDir=dist

Run pkger for sidekick:
```bash
# Requires: binaries in sidekick-release/linux-{arch}/
# ac client
pkger -r RELEASE.2025-03-12T00-00-00Z --appName ac

# sidekick
pkger -r RELEASE.2025-03-12T00-00-00Z --appName sidekick
```

Run pkger for warp (semantic versioning):
```bash
# Requires: binaries in warp-release/linux-{arch}/
# warp (semantic versioning)
pkger -r v0.4.3 --appName warp
```

Build specific package formats only:
```bash
# specific package formats only
pkger -r <release> --appName <app> --packager deb,rpm
```

Ignore missing architectures (continue on errors):
```bash
# continue past missing architectures
pkger -r <release> --appName <app> --ignore
```

Skip package building (only generate JSON):
```bash
# JSON metadata only
pkger -r <release> --appName <app> --no-pkg
```

Generate EDGE release (uses /edge/ path instead of /release/):
```bash
pkger -r RELEASE.2025-03-12T00-00-00Z --appName minio-enterprise --edge --no-pkg
# EDGE release (uses /edge/ instead of /release/)
pkger -r EDGE.2025-03-12T00-00-00Z --appName aistor --edge --no-pkg

# rename the installed binary/package (see README)
pkger -r <release> --appName aistor --binary-name aistor --package-name aistor
```

## Key Flags

- `-r, --release`: Release tag (required). Format depends on app type
- `-a, --appName`: Application name (default: "minio")
- `-d, --releaseDir`: Directory containing binaries (default: "{appName}-release")
- `-p, --packager`: Package formats to build (default: "deb,rpm,apk")
- `-i, --ignore`: Ignore missing architecture errors
- `-n, --no-pkg`: Skip package generation
- `-e, --edge`: Generate EDGE release URLs (uses /edge/ path)
- `-s, --scriptsDir`: Directory with package scripts (preinstall.sh, postinstall.sh, etc.)
- `-l, --license`: Package license (default: "AGPLv3")
- `--deps`: JSON file with package dependencies
- `-r, --release`: release tag (required); format depends on the app
- `-a, --appName`: application name (default: `aistor`)
- `-d, --releaseDir`: directory containing binaries (default: per-app, see table)
- `-p, --packager`: formats to build (default: `deb,rpm,apk`)
- `-i, --ignore`: ignore missing architecture errors
- `-n, --no-pkg`: skip package generation
- `-j, --no-json`: skip JSON metadata generation
- `-e, --edge`: EDGE release URLs (`/edge/` path)
- `-s, --scriptsDir`: directory with package scripts (preinstall.sh, postinstall.sh, preremove.sh, postremove.sh)
- `-l, --license`: package license (default: `AGPLv3`)
- `-c, --contents`: YAML file with extra nfpm content entries (supports `${ARCH}`)
- `--deps`: JSON file with per-format package dependencies
- `--binary-name` / `--package-name`: override the source binary base name and the package/installed-command name; see README

## Important Implementation Details

### Architecture Mapping
- RPM uses x86_64/aarch64 (see `rpmArchMap`, lines 150-153)
- DEB uses amd64/arm64 (see `debArchMap`, lines 155-158)

- RPM uses x86_64/aarch64 (`rpmArchMap`)
- DEB uses amd64/arm64 (`debArchMap`)

### Download URL Patterns
The JSON generators create download metadata with different URL structures:
- **Community**: `dl.min.io/{server|client}/{appName}/release/...`
- **Enterprise Release**: `dl.min.io/aistor/{appName}/release/...`
- **Enterprise EDGE**: `dl.min.io/aistor/{appName}/edge/...` (with `--edge` flag)
- **Sidekick/Warp**: Always use `dl.min.io/aistor/` path

`dl.min.io` path segments are **hardcoded literals**, never interpolated from `--appName`. Renaming an app must not change a published URL.

- **AIStor server**: `dl.min.io/aistor/minio/{release,edge}/...`
- **AIStor client**: `dl.min.io/aistor/mc/{release,edge}/...`
- **MinIO KMS**: `dl.min.io/aistor/minkms/{release,edge}/...` (emitted alongside aistor)
- **Sidekick / warp**: `dl.min.io/aistor/{sidekick,warp}/release/...`
- One macOS Homebrew entry still points at `dl.min.io/server/minio/...`

### EDGE Release Support
- Use `--edge` flag to generate EDGE release metadata
- Changes URL path from `/release/` to `/edge/`
- Generates separate JSON file: `downloads-{appName}-edge.json`
- Docker/Podman instructions use release tag (not `:latest`)
- Package building (RPM/DEB) works the same for both release and EDGE

- `--edge` switches the URL path from `/release/` to `/edge/` and writes `downloads-{appName}-edge.json`
- An `EDGE.`-prefixed release tag requires `--edge`, and `--edge` rejects a `RELEASE.`-prefixed tag
- Package building works identically for release and EDGE
- Docker/Podman instructions use the actual release tag, not `:latest`

### Special Cases
- **minio/aistor**: Includes `minio.service` systemd file in packages (lines 103-106)
- **mc packages**: Binary named "mc" but package name is "mcli" (lines 870-872)
- **warp**: Version validation enforces `vX.Y.Z` format (lines 734-742)
- **Docker tags**: All Docker/Podman instructions now use actual release tags instead of `:latest`

## Testing Changes
- **aistor**: includes a `minio.service` systemd unit, gated on the _binary_ name being `minio` or `aistor`. The file is **not** in this repo — supply it in the working directory (q downloads it via the `service_file` config).
- **sidekick**: includes `sidekick.service` from this repo
- **ac**: binary `mc`, package `mcli`
- **warp**: enforces `vX.Y.Z`
- **`--package-name` rename**: when it differs from the app default, the old name becomes a legacy name. Inside the package that means a `/usr/local/bin/<old> -> <new>` symlink plus `provides`/`replaces`/`conflicts`. In the release dir it also means the old package filename, the old "latest" alias and the old `.sha256sum` are symlinked onto the new package, so already-published download URLs keep resolving. `TestPackageRenameKeepsOldLinks` pins this; `TestPackageDefaultsEmitNoLegacyLinks` pins that the non-rename path emits none of it.

## Testing

```bash
go test ./...
```

`main_test.go` covers:

- Version conversion (date-based and semantic)
- The nfpm template under both default and renamed (`--binary-name`/`--package-name`) paths
- JSON generation for aistor, ac, sidekick and warp, plus the empty fallback document
- EDGE URL structure and Docker tag usage
- Architecture mapping and the `pkgArches` set
- Per-app release directory resolution

When modifying version handling or JSON generation:
1. Test with all app types: minio, sidekick, warp, minio-enterprise
2. Verify package filenames match conventions (no 'v' prefix for warp)
3. Check generated JSON URLs point to correct dl.min.io paths
4. Ensure architecture filtering works correctly for each app

1. Test every app in the table above
2. Verify package filenames match convention (no `v` prefix for warp; `minio`/`mcli` for aistor/ac)
3. Check generated JSON URLs still point at the correct dl.min.io paths
4. Confirm no new architecture leaked into `pkgArches`

## Downstream Consumers

`--appName`, the release directory layout, and the `downloads-{appName}.json` filename are a contract with the `q` release automation repo:

- `q/v3/internal/pipeline/stage_build.go` builds the pkger argv from each product's `.qreleaser.yml` `packaging.app_name`
- `q/lib/copy-aistor-release-assets.sh` and `q/community/binary-releases/copy-release-assets.sh` look the JSON up by filename
- The product repos' `.qreleaser.yml` (`aistor`, `ac`, …) set `app_name`

Renaming an app or changing the JSON filename requires coordinated edits there. `q/legacy-v1/` is archived and intentionally left on the old names.

## File Structure

```
```text
pkger/
├── main.go # Single-file application
├── go.mod # Go 1.25+ required
├── minio.service # Systemd service file (included in minio packages)
├── main_test.go # Unit tests
├── go.mod # Go 1.26+ required
├── sidekick.service # Systemd unit shipped in sidekick packages
├── dist/ # GoReleaser output for pkger itself
└── {app}-release/ # Input/output directories for packaging
└── linux-{arch}/
├── {binary}.{release} # Input binary
├── {package}.rpm # Output package
├── {package}.deb
├── {package}.apk
├── {package}.{rpm,deb,apk} # Output packages
├── {alias}.{rpm,deb,apk} # "Latest" symlink
└── *.sha256sum # Checksums
```

## Testing

Run unit tests:
```bash
go test -v
```

The test suite (`main_test.go`) covers:
- Version conversion (date-based and semantic)
- JSON generation for all app types
- EDGE release URL validation
- Docker tag usage verification
- Architecture mapping correctness
- URL path structure validation

## Development Notes

- Comprehensive unit tests exist in `main_test.go` covering all JSON generation functions
- Package scripts (preinstall.sh, etc.) are optional and loaded from `--scriptsDir`
- The template system expects specific directory structures; paths are not validated upfront
- JSON generation happens regardless of package build success/failure
- Avoid citing `main.go` line numbers in docs — they rot; name the function or variable instead
Loading
Loading