A complete solution for hosting and managing Command Launcher remote registries, featuring both a REST API server and a powerful CLI client (cola-regctl) for seamless registry management.
- Multi-Registry Support: Isolated namespaces for different teams
- Package & Version Management: Full CRUD operations via REST API
- Gradual Rollout: Partition-based version distribution (0-9 range)
- Command Launcher Compatible: Serves index.json in CDT-compatible format
- Authentication: Optional HTTP Basic Auth for write operations
- Pluggable Storage: File-based JSON, OCI registry (ghcr.io, Docker Hub), or S3-compatible storage (AWS S3, MinIO, etc.)
- Operational Ready: Health checks, metrics, structured logging
- Full CRUD Operations: Manage registries, packages, and versions from the command line
- Secure Credential Storage: OS-native keychains (macOS Keychain, Windows Credential Manager, Linux protected files)
- Environment Variable Support:
COLA_REGISTRY_URLandCOLA_REGISTRY_SESSION_TOKEN - Multiple Output Formats: Human-readable tables and machine-parseable JSON
- Interactive Workflows: Password prompts and deletion confirmations
- Cross-Platform: Works on macOS, Linux, and Windows
- Go 1.24 or later
- Git
# Clone and build
git clone <repository-url>
cd command-launcher-registry
make build # Build server
make build-cli # Build CLI client# Start server with default configuration
./bin/cola-registry server
# With explicit storage URI
./bin/cola-registry server --storage-uri file://./data/registry.json
# With path only (auto-prefixed with file://)
./bin/cola-registry server --storage-uri ./data/registry.json
# Full CLI configuration
./bin/cola-registry server \
--storage-uri file://./data/registry.json \
--port 8080 \
--host 0.0.0.0 \
--log-level info \
--log-format json \
--auth-type none
# Or with environment variables
export COLA_REGISTRY_STORAGE_URI=file://./data/registry.json
export COLA_REGISTRY_SERVER_PORT=8080
./bin/cola-registry server
# With OCI storage (GitHub Container Registry)
./bin/cola-registry server \
--storage-uri oci://ghcr.io/yourusername/cola-registry-data \
--storage-token ghp_your_github_token \
--port 8080
# Or with environment variables for OCI
export COLA_REGISTRY_STORAGE_URI=oci://ghcr.io/yourusername/cola-registry-data
export COLA_REGISTRY_STORAGE_TOKEN=ghp_your_github_token
./bin/cola-registry server
# With S3 storage (AWS S3)
./bin/cola-registry server \
--storage-uri s3://s3.us-east-1.amazonaws.com/mybucket/registry.json \
--storage-token ACCESS_KEY:SECRET_KEY
# With S3 storage (MinIO for local development)
./bin/cola-registry server \
--storage-uri s3+http://localhost:9000/mybucket/registry.json \
--storage-token minioadmin:minioadminServer starts at http://localhost:8080 by default.
# Set server URL and credentials via environment variables
export COLA_REGISTRY_URL=http://localhost:8080
export COLA_REGISTRY_SESSION_TOKEN=admin:admin
# Create a registry
./bin/cola-regctl registry create my-tools --description "My tools registry"
# List registries
./bin/cola-regctl registry list
# Create a package
./bin/cola-regctl package create my-tools my-cli \
--description "My CLI tool" \
--maintainer "you@example.com"
# Publish a version
./bin/cola-regctl version create my-tools my-cli 1.0.0 \
--checksum "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" \
--url "https://downloads.example.com/my-cli-1.0.0.zip" \
--start-partition 0 \
--end-partition 9
# List versions
./bin/cola-regctl version list my-tools my-cli
# Get JSON output (for scripting)
./bin/cola-regctl registry list --jsonUsing curl (direct API calls):
# Populate sample registry with packages and versions
./scripts/populate-test-data.sh
# Verify data
curl http://localhost:8080/api/v1/registry/company-tools/index.json | jq '.'
# Clean up test data
./scripts/clean-test-data.shUsing CLI client:
# Populate sample registry with packages and versions
./scripts/populate-test-data-cli.sh
# Verify data using CLI
export COLA_REGISTRY_URL=http://localhost:8080
export COLA_REGISTRY_SESSION_TOKEN=admin:admin
./bin/cola-regctl registry list
./bin/cola-regctl package list company-tools
./bin/cola-regctl version list company-tools deployment-cli
# Clean up test data
./scripts/clean-test-data-cli.sh# Health check
curl http://localhost:8080/api/v1/health | jq '.'
# Metrics
curl http://localhost:8080/api/v1/metrics | jq '.'
# Create a registry (no auth required by default)
curl -X POST http://localhost:8080/api/v1/registry \
-H "Content-Type: application/json" \
-d '{"name":"build","description":"Build tools"}' | jq '.'
# Add a package
curl -X POST http://localhost:8080/api/v1/registry/build/package \
-H "Content-Type: application/json" \
-d '{"name":"hotfix","description":"Hotfix tool"}' | jq '.'
# Publish a version
curl -X POST http://localhost:8080/api/v1/registry/build/package/hotfix/version \
-H "Content-Type: application/json" \
-d '{
"name":"hotfix",
"version":"1.0.0",
"checksum":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"url":"https://example.com/hotfix-1.0.0.zip",
"startPartition":0,
"endPartition":9
}' | jq '.'
# Fetch registry index (Command Launcher format)
curl http://localhost:8080/api/v1/registry/build/index.json | jq '.'Configuration follows 12-factor app principles:
- CLI flags (highest priority)
- Environment variables
- Defaults (lowest priority)
No configuration file is required.
cola-registry server [flags]
Flags:
--storage-uri string Storage URI (file:// for local, oci:// for OCI registry, s3:// for S3)
Default: file://./data/registry.json
--storage-token string Storage authentication token (required for OCI, optional for S3)
Default: (empty)
--port int Server port
Default: 8080
--host string Bind address
Default: 0.0.0.0
--log-level string Log level (debug|info|warn|error)
Default: info
--log-format string Log format (json|text)
Default: json
--auth-type string Authentication type (none|basic|ldap|custom_jwt)
Default: none
--auth-ldap-server string LDAP server URL (e.g., ldap://ldap.example.com)
--auth-ldap-bind-dn string LDAP bind DN for service account
--auth-ldap-user-base-dn string LDAP base DN for user searches
--auth-ldap-timeout int LDAP connection timeout in seconds
Default: 30All CLI flags have corresponding environment variables with COLA_REGISTRY_ prefix:
export COLA_REGISTRY_STORAGE_URI=file://./data/registry.json
export COLA_REGISTRY_STORAGE_TOKEN=my-token # Required for OCI storage
export COLA_REGISTRY_SERVER_PORT=8080
export COLA_REGISTRY_SERVER_HOST=0.0.0.0
export COLA_REGISTRY_LOGGING_LEVEL=info
export COLA_REGISTRY_LOGGING_FORMAT=json
export COLA_REGISTRY_AUTH_TYPE=basic
export COLA_REGISTRY_AUTH_USERS_FILE=./users.yaml # Environment-only (no CLI flag)Custom JWT authentication delegates token validation to an external script. This allows integration with any JWT provider.
Configuration:
export COLA_REGISTRY_AUTH_TYPE=custom_jwt
export COLA_REGISTRY_AUTH_CUSTOM_JWT_SCRIPT=/path/to/jwt-validator.sh
export COLA_REGISTRY_AUTH_CUSTOM_JWT_REQUIRED_GROUP=my-admin-group # OptionalCustom JWT configuration options:
| Setting | Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|---|
| Script | COLA_REGISTRY_AUTH_CUSTOM_JWT_SCRIPT |
--auth-custom-jwt-script |
(required) | Path to JWT validator script |
| Required Group | COLA_REGISTRY_AUTH_CUSTOM_JWT_REQUIRED_GROUP |
--auth-custom-jwt-required-group |
- | Group required to access registry |
Script requirements:
- Must be executable and in PATH or specified as absolute path
- Receives JWT token as first argument
- Exit code 0 = valid token, non-zero = invalid token
- Output format: one group per line (optionally
username:valueon first line)
Example script output:
username:john.doe
admin-group
developers
readonly-users
Priority order: CLI flags > Environment variables > Defaults
LDAP authentication validates user credentials against an LDAP directory (e.g., Active Directory, OpenLDAP).
Minimal configuration:
export COLA_REGISTRY_AUTH_TYPE=ldap
export COLA_REGISTRY_AUTH_LDAP_SERVER=ldap://ldap.example.com
export COLA_REGISTRY_AUTH_LDAP_BIND_DN=cn=serviceaccount,dc=example,dc=com
export COLA_REGISTRY_AUTH_LDAP_BIND_PASSWORD=your-bind-password
export COLA_REGISTRY_AUTH_LDAP_USER_BASE_DN=ou=users,dc=example,dc=com
./bin/cola-registry serverWith group restriction:
# Only allow members of the "registry-admins" group
export COLA_REGISTRY_AUTH_LDAP_REQUIRED_GROUP=cn=registry-admins,ou=groups,dc=example,dc=com
export COLA_REGISTRY_AUTH_LDAP_GROUP_BASE_DN=ou=groups,dc=example,dc=comLDAP configuration options:
| Setting | Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|---|
| Server | COLA_REGISTRY_AUTH_LDAP_SERVER |
--auth-ldap-server |
(required) | LDAP server URL |
| Bind DN | COLA_REGISTRY_AUTH_LDAP_BIND_DN |
--auth-ldap-bind-dn |
(required) | Service account DN |
| Bind Password | COLA_REGISTRY_AUTH_LDAP_BIND_PASSWORD |
- | (required) | Service account password |
| User Base DN | COLA_REGISTRY_AUTH_LDAP_USER_BASE_DN |
--auth-ldap-user-base-dn |
(required) | Base DN for user searches |
| Timeout | COLA_REGISTRY_AUTH_LDAP_TIMEOUT |
- | 30 |
Connection timeout in seconds |
| User Filter | COLA_REGISTRY_AUTH_LDAP_USER_FILTER |
- | (uid=%s) |
LDAP filter for users |
| Group Base DN | COLA_REGISTRY_AUTH_LDAP_GROUP_BASE_DN |
- | (empty) | Base DN for group searches |
| Group Filter | COLA_REGISTRY_AUTH_LDAP_GROUP_FILTER |
- | (member=%s) |
LDAP filter for groups |
| Required Group | COLA_REGISTRY_AUTH_LDAP_REQUIRED_GROUP |
- | (empty) | DN of required group |
The storage backend is configured via URI:
# File storage
--storage-uri file://./data/registry.json # Relative path
--storage-uri file:///var/data/registry.json # Absolute path (Unix)
--storage-uri ./data/registry.json # Auto-prefixed with file://
# OCI storage (GitHub Container Registry)
--storage-uri oci://ghcr.io/myorg/cola-registry-data
--storage-token ghp_xxxxxxxxxxxxxxxxxxxx
# OCI storage (Docker Hub)
--storage-uri oci://docker.io/myuser/cola-registry-data
--storage-token dckr_pat_xxxxxxxxx
# OCI storage (Azure Container Registry)
--storage-uri oci://myregistry.azurecr.io/cola-registry-data
--storage-token <acr-token>
# S3 storage (AWS S3)
--storage-uri s3://s3.us-east-1.amazonaws.com/mybucket/registry.json
--storage-token ACCESS_KEY:SECRET_KEY
# S3 storage with explicit region
--storage-uri s3://s3.amazonaws.com/mybucket/registry.json?region=us-east-1
--storage-token ACCESS_KEY:SECRET_KEY
# S3 storage (MinIO - local development)
--storage-uri s3+http://localhost:9000/mybucket/registry.json
--storage-token minioadmin:minioadmin
# S3 storage (DigitalOcean Spaces)
--storage-uri s3://nyc3.digitaloceanspaces.com/mybucket/registry.json
--storage-token ACCESS_KEY:SECRET_KEY
# S3 storage (Backblaze B2)
--storage-uri s3://s3.us-west-004.backblazeb2.com/mybucket/registry.json
--storage-token ACCESS_KEY:SECRET_KEYOCI Storage Notes:
- OCI storage requires
--storage-tokenorCOLA_REGISTRY_STORAGE_TOKENenvironment variable - The registry data is stored as an OCI artifact with
latesttag (overwritten on each write) - Supports any OCI Distribution-compliant registry
- Token format is registry-specific (e.g., GitHub PAT for ghcr.io)
S3 Storage Notes:
- S3 storage uses
s3://for HTTPS ors3+http://for HTTP connections - Token format:
ACCESS_KEY:SECRET_KEY - Falls back to
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYenvironment variables if no token provided - Supports IAM role authentication (leave token empty)
- Region is auto-detected from AWS endpoints or can be specified via
?region=query parameter - Compatible with any S3-compatible storage: AWS S3, MinIO, DigitalOcean Spaces, Backblaze B2, Wasabi, etc.
# Build the image
docker build -t cola-registry -f docker/Dockerfile .
# Run with file storage
docker run -p 8080:8080 \
-e COLA_REGISTRY_STORAGE_URI=file:///data/registry.json \
-v /host/data:/data \
cola-registry
# Run with OCI storage
docker run -p 8080:8080 \
-e COLA_REGISTRY_STORAGE_URI=oci://ghcr.io/myorg/cola-registry-data \
-e COLA_REGISTRY_STORAGE_TOKEN=ghp_xxxxxxxxxxxx \
cola-registry
# Run with S3 storage
docker run -p 8080:8080 \
-e COLA_REGISTRY_STORAGE_URI=s3://s3.us-east-1.amazonaws.com/mybucket/registry.json \
-e COLA_REGISTRY_STORAGE_TOKEN=ACCESS_KEY:SECRET_KEY \
cola-registryTo enable basic authentication:
# Generate password hash
./bin/cola-registry auth hash-password
# Enter password when prompted
# Copy the bcrypt hash
# Create users.yaml
cat > users.yaml <<EOF
users:
- username: admin
password_hash: "$2a$10$..." # paste bcrypt hash here
roles: ["admin"]
EOF
export COLA_REGISTRY_AUTH_TYPE=basic
export COLA_REGISTRY_AUTH_USERS_FILE=./users.yaml
# Start server with authentication enabled
./bin/cola-registry serverNow write operations require authentication:
# Create registry with auth
curl -u admin:yourpassword -X POST http://localhost:8080/api/v1/registry \
-H "Content-Type: application/json" \
-d '{"name":"secure","description":"Secure registry"}'The cola-regctl CLI provides a user-friendly interface for managing registries, packages, and versions.
# Build the CLI
make build-cli
# Optionally install to system PATH
sudo cp bin/cola-regctl /usr/local/bin/The CLI supports multiple authentication methods with the following precedence:
--tokenflag (highest priority)COLA_REGISTRY_SESSION_TOKENenvironment variable- Stored credentials from
logincommand (lowest priority)
# Login interactively (stores credentials securely)
./bin/cola-regctl login http://localhost:8080
# Enter username: admin
# Enter password: ****
# Check authentication status
./bin/cola-regctl whoami
# Logout (removes stored credentials)
./bin/cola-regctl logout# Set once, use everywhere
export COLA_REGISTRY_URL=http://localhost:8080
export COLA_REGISTRY_SESSION_TOKEN=admin:admin
# Now all commands use these credentials automatically
./bin/cola-regctl registry list
./bin/cola-regctl package create my-registry my-package# Override with flags
./bin/cola-regctl --url http://localhost:8080 --token admin:admin registry list# Create a registry
cola-regctl registry create <name> \
--description "Description" \
--admin "admin@example.com" \
--custom-value "key=value"
# List all registries
cola-regctl registry list
cola-regctl registry list --json # JSON output
# Get registry details
cola-regctl registry get <name>
# Update registry
cola-regctl registry update <name> \
--description "New description" \
--admin "new-admin@example.com" \
--clear-admins # Clear all admins
# Delete registry (with confirmation)
cola-regctl registry delete <name>
cola-regctl registry delete <name> --yes # Skip confirmation# Create a package
cola-regctl package create <registry> <package> \
--description "Package description" \
--maintainer "maintainer@example.com" \
--custom-value "language=go" \
--custom-value "repo=https://github.com/..."
# List packages
cola-regctl package list <registry>
cola-regctl package list <registry> --json
# Get package details
cola-regctl package get <registry> <package>
# Update package
cola-regctl package update <registry> <package> \
--description "New description" \
--maintainer "new@example.com" \
--clear-maintainers # Clear all maintainers
# Delete package
cola-regctl package delete <registry> <package># Publish a version
cola-regctl version create <registry> <package> <version> \
--checksum "abc123..." \
--url "https://downloads.example.com/package-1.0.0.zip" \
--start-partition 0 \
--end-partition 9
# List versions
cola-regctl version list <registry> <package>
cola-regctl version list <registry> <package> --json
# Get version details
cola-regctl version get <registry> <package> <version>
# Delete version
cola-regctl version delete <registry> <package> <version>All commands support these global flags:
--url <url>- Server URL (or useCOLA_REGISTRY_URLenv var)--token <user:pass>- Authentication token (or useCOLA_REGISTRY_SESSION_TOKENenv var)--json- Output in JSON format (for scripting)--verbose- Enable verbose logging--timeout <duration>- HTTP request timeout (default: 30s)--yes/-y- Skip confirmation prompts
# Set up environment
export COLA_REGISTRY_URL=http://localhost:8080
export COLA_REGISTRY_SESSION_TOKEN=admin:admin
# Create a complete registry
cola-regctl registry create build-tools \
--description "Build and deployment tools" \
--admin "devops@example.com"
# Add a package
cola-regctl package create build-tools deployer \
--description "Deployment automation tool" \
--maintainer "devops@example.com" \
--custom-value "language=go"
# Publish version 1.0.0 (for partitions 0-4 = 50% rollout)
cola-regctl version create build-tools deployer 1.0.0 \
--checksum "abc123..." \
--url "https://cdn.example.com/deployer-1.0.0.tar.gz" \
--start-partition 0 \
--end-partition 4
# Publish version 1.1.0 (for partitions 5-9 = 50% rollout)
cola-regctl version create build-tools deployer 1.1.0 \
--checksum "def456..." \
--url "https://cdn.example.com/deployer-1.1.0.tar.gz" \
--start-partition 5 \
--end-partition 9
# List all versions
cola-regctl version list build-tools deployer#!/bin/bash
# Get all registries and process with jq
registries=$(cola-regctl registry list --json | jq -r '.data[].name')
for registry in $registries; do
echo "Packages in $registry:"
cola-regctl package list "$registry" --json | jq -r '.data[].name'
doneThe CLI stores credentials securely using OS-native mechanisms:
- macOS: Keychain (token) + file (URL)
- Windows: Credential Manager (token) + file (URL)
- Linux: Protected file (0600 permissions)
Credentials are stored at: ~/.config/cola-registry/credentials.yaml
- Complete Specification - Full specification covering both server and CLI client
- OpenAPI Contract - REST API specification
GET /api/v1/health- Health checkGET /api/v1/metrics- Server metrics
GET /api/v1/registry- List all registries (auth required)POST /api/v1/registry- Create registry (auth required)GET /api/v1/registry/:name- Get registry detailsPUT /api/v1/registry/:name- Update registry (auth required)DELETE /api/v1/registry/:name- Delete registry (auth required, cascade)GET /api/v1/registry/:name/index.json- Get registry index (CDT format)
GET /api/v1/registry/:name/package- List packagesPOST /api/v1/registry/:name/package- Create package (auth required)GET /api/v1/registry/:name/package/:package- Get package detailsPUT /api/v1/registry/:name/package/:package- Update package (auth required)DELETE /api/v1/registry/:name/package/:package- Delete package (auth required, cascade)
GET /api/v1/registry/:name/package/:package/version- List versionsPOST /api/v1/registry/:name/package/:package/version- Create version (auth required)GET /api/v1/registry/:name/package/:package/version/:version- Get version detailsDELETE /api/v1/registry/:name/package/:package/version/:version- Delete version (auth required)
make build # Build server binary
make build-cli # Build CLI client binary
make build-all # Build both server and CLI
make clean # Remove build artifacts
make test # Run all tests
make test-cli # Run CLI tests only
make run # Build and run server
make fmt # Format code
make fmt-check # Check formatting (for CI)
make lint # Run linter (go vet)
make install-cli # Install CLI to $GOPATH/bin
make help # Show all targetscmd/
├── cola-registry/ # Server binary entry point
└── cola-regctl/ # CLI client binary entry point
internal/
├── server/ # HTTP server
│ ├── handlers/ # HTTP handlers (health, index, registry, package, version)
│ └── middleware/ # Middleware (auth, cors, logging, ratelimit)
├── client/ # CLI client
│ ├── commands/ # Cobra commands (registry, package, version, login, etc.)
│ ├── auth/ # Credential storage (keyring, file)
│ ├── config/ # URL resolution
│ ├── output/ # Output formatters (table, JSON)
│ ├── prompts/ # Interactive prompts
│ ├── validation/ # Client-side validation
│ └── errors/ # Exit codes
├── storage/ # Storage backends (file, OCI, S3)
├── models/ # Shared data models
├── auth/ # Server authentication (basic, none)
├── cli/ # Server CLI commands
├── config/ # Server configuration
└── apierrors/ # API error types
tests/
└── integration/ # Integration tests (OCI, S3, server)
scripts/ # Test data scripts
docker/
└── Dockerfile # Multi-stage Docker build
docs/
├── openapi.yaml # OpenAPI specification
└── quickstart.md # Quick start guide
.github/
└── workflows/go.yml # CI/CD pipeline