You've already forked hubmanager
feat: initial implementation of hubmanager v0.1.0
Add a Bash CLI tool to manage Docker Registry images remotely. Supports Docker Hub and self-hosted Docker Registry v2 API with automatic auth detection (bearer token or HTTP basic auth). Subcommands: login, list, tags, inspect, delete, copy, prune Dependencies: curl, jq, bash 4+ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
391
README.md
Normal file
391
README.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# hubmanager
|
||||
|
||||
A Bash CLI tool to manage Docker Registry images remotely. Supports Docker Hub and any self-hosted Docker Registry v2 API, with flexible authentication.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Bash** 4.0+
|
||||
- **curl**
|
||||
- **jq**
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# System-wide
|
||||
sudo install -m 755 hubmanager /usr/local/bin/hubmanager
|
||||
|
||||
# User-local
|
||||
install -m 755 hubmanager ~/bin/hubmanager
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Test your credentials and save them
|
||||
hubmanager login --registry https://registry.example.com \
|
||||
--user admin --password secret --save
|
||||
|
||||
# List all repositories
|
||||
hubmanager list
|
||||
|
||||
# List tags for an image
|
||||
hubmanager tags myuser/myapp
|
||||
|
||||
# Inspect an image
|
||||
hubmanager inspect myuser/myapp:latest
|
||||
|
||||
# Delete an old tag
|
||||
hubmanager delete myuser/myapp:v1.0.0
|
||||
|
||||
# Prune old tags (keep the 3 most recent)
|
||||
hubmanager prune myuser/myapp --keep 3 --dry-run
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Credentials and registry settings are stored in `~/.hubmanager.conf`.
|
||||
The file uses a simple `KEY=VALUE` format:
|
||||
|
||||
```bash
|
||||
# ~/.hubmanager.conf
|
||||
# chmod 600 ~/.hubmanager.conf
|
||||
|
||||
# Default registry and credentials
|
||||
REGISTRY=https://registry.example.com
|
||||
USERNAME=admin
|
||||
PASSWORD=mysecretpassword
|
||||
|
||||
# Named registry aliases (use with --registry <alias>)
|
||||
REGISTRY_STAGING_URL=https://staging-registry.example.com
|
||||
REGISTRY_STAGING_USERNAME=deploy
|
||||
REGISTRY_STAGING_PASSWORD=deploytoken
|
||||
|
||||
REGISTRY_HUB_URL=https://registry-1.docker.io
|
||||
REGISTRY_HUB_USERNAME=myuser
|
||||
REGISTRY_HUB_PASSWORD=myhubtoken
|
||||
```
|
||||
|
||||
> The file must be readable only by the owner (`chmod 600`). hubmanager will warn if permissions are too open.
|
||||
|
||||
Named aliases let you switch registries with a short name:
|
||||
|
||||
```bash
|
||||
hubmanager --registry staging list
|
||||
hubmanager --registry hub tags myuser/myapp
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
```
|
||||
hubmanager [OPTIONS] <command> [COMMAND OPTIONS]
|
||||
|
||||
-r, --registry <url> Registry base URL
|
||||
Default: https://registry-1.docker.io
|
||||
-u, --user <username> Username (overrides config file)
|
||||
-p, --password <pass> Password or token (overrides config file)
|
||||
--config <file> Config file path (default: ~/.hubmanager.conf)
|
||||
--json Output raw JSON (pipe-friendly)
|
||||
--no-color Disable ANSI color
|
||||
-v, --verbose Show HTTP request details (with auth redacted)
|
||||
-q, --quiet Suppress all non-error output
|
||||
-h, --help Show help
|
||||
--version Show version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### `login` — Test and save credentials
|
||||
|
||||
```
|
||||
hubmanager login [--registry URL] [--user USER] [--password PASS] [--save]
|
||||
```
|
||||
|
||||
Validates credentials against the registry. Use `--save` to write them to the config file.
|
||||
|
||||
```bash
|
||||
hubmanager login --registry https://registry.example.com \
|
||||
--user admin --password secret --save
|
||||
# Login Succeeded — bearer auth, registry: https://registry.example.com
|
||||
# Credentials saved to /home/user/.hubmanager.conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `list` — List repositories
|
||||
|
||||
```
|
||||
hubmanager list [--limit N] [--last REPO]
|
||||
```
|
||||
|
||||
Lists all repositories in the registry. On **Docker Hub**, lists repositories for the authenticated user (Docker Hub restricts the `_catalog` endpoint).
|
||||
|
||||
```bash
|
||||
hubmanager list
|
||||
# REPOSITORY
|
||||
# myuser/myapp
|
||||
# myuser/myapi
|
||||
# myuser/nginx-custom
|
||||
|
||||
# Paginate
|
||||
hubmanager list --limit 50 --last myuser/myapi
|
||||
|
||||
# JSON output
|
||||
hubmanager list --json | jq '.repositories[]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `tags` — List tags for an image
|
||||
|
||||
```
|
||||
hubmanager tags <image> [--limit N] [--last TAG]
|
||||
```
|
||||
|
||||
```bash
|
||||
hubmanager tags myuser/myapp
|
||||
# TAG
|
||||
# latest
|
||||
# v1.2.3
|
||||
# v1.2.2
|
||||
# develop
|
||||
|
||||
# Official Docker Hub images
|
||||
hubmanager tags nginx
|
||||
|
||||
# JSON
|
||||
hubmanager tags myuser/myapp --json | jq '.tags[]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `inspect` — Show image details
|
||||
|
||||
```
|
||||
hubmanager inspect <image>:<tag|digest> [--platform OS/ARCH]
|
||||
```
|
||||
|
||||
Shows manifest digest, size, OS/arch, creation date, labels, and layer breakdown.
|
||||
|
||||
```bash
|
||||
hubmanager inspect myuser/myapp:latest
|
||||
# Image: myuser/myapp:latest
|
||||
# Digest: sha256:abc123...
|
||||
# MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
# CompressedSize: 32.7 MB (34299597 bytes)
|
||||
# OS/Arch: linux/amd64
|
||||
# Created: 2024-01-15T10:30:00.000000000Z
|
||||
# Labels:
|
||||
# maintainer=dev@example.com
|
||||
# version=1.2.3
|
||||
# Layers: 3
|
||||
# [0] sha256:1111... (27.8 MB)
|
||||
# [1] sha256:2222... (4.4 MB)
|
||||
# [2] sha256:3333... (1000.0 KB)
|
||||
|
||||
# Multi-arch image — shows all platforms
|
||||
hubmanager inspect nginx:latest
|
||||
|
||||
# Multi-arch — drill into a specific platform
|
||||
hubmanager inspect nginx:latest --platform linux/arm64
|
||||
|
||||
# Inspect by digest
|
||||
hubmanager inspect myuser/myapp@sha256:abc123...
|
||||
|
||||
# JSON output (includes _digest field)
|
||||
hubmanager inspect myuser/myapp:latest --json | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `delete` — Delete a tag or manifest
|
||||
|
||||
```
|
||||
hubmanager delete <image>:<tag|digest> [--yes]
|
||||
```
|
||||
|
||||
Deletes a manifest by resolving the tag to its content-addressable digest, then issuing a `DELETE`. Requires `REGISTRY_STORAGE_DELETE_ENABLED=true` on self-hosted registries. Not supported on Docker Hub.
|
||||
|
||||
```bash
|
||||
hubmanager delete myuser/myapp:v1.0.0
|
||||
# About to delete: myuser/myapp @ sha256:abc123...
|
||||
# Registry: https://registry.example.com
|
||||
# Type 'yes' to confirm: yes
|
||||
# Deleted: myuser/myapp @ sha256:abc123...
|
||||
|
||||
# Skip confirmation
|
||||
hubmanager delete myuser/myapp:v1.0.0 --yes
|
||||
|
||||
# Delete by digest directly
|
||||
hubmanager delete myuser/myapp@sha256:abc123... --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `copy` — Copy or retag an image
|
||||
|
||||
```
|
||||
hubmanager copy <src-image>:<tag> <dst-image>:<tag> [options]
|
||||
|
||||
Options:
|
||||
--src-registry URL Source registry (default: global --registry)
|
||||
--dst-registry URL Destination registry (default: global --registry)
|
||||
--src-user USER Source username
|
||||
--src-password PASS Source password
|
||||
--dst-user USER Destination username
|
||||
--dst-password PASS Destination password
|
||||
--platform OS/ARCH Copy only one platform from a multi-arch image
|
||||
```
|
||||
|
||||
**Same-registry retag** — attempts cross-repo blob mount (no data transfer):
|
||||
```bash
|
||||
hubmanager copy myuser/myapp:v1.2.3 myuser/myapp:stable
|
||||
```
|
||||
|
||||
**Cross-registry copy** — streams blobs from source to destination:
|
||||
```bash
|
||||
hubmanager copy myuser/myapp:latest \
|
||||
--src-registry https://registry-1.docker.io \
|
||||
--dst-registry https://registry.example.com \
|
||||
mycompany/myapp:latest
|
||||
```
|
||||
|
||||
**Copy specific platform** from a multi-arch image:
|
||||
```bash
|
||||
hubmanager copy nginx:latest myuser/nginx-amd64:latest --platform linux/amd64
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `prune` — Delete outdated tags
|
||||
|
||||
```
|
||||
hubmanager prune <image> [options]
|
||||
|
||||
Options:
|
||||
--keep N Number of recent tags to keep (default: 3)
|
||||
--older-than DAYS Delete tags older than N days (overrides --keep)
|
||||
--exclude PATTERN Extended regex of tags to never delete
|
||||
Default: "^(latest|stable|main|master|release)$"
|
||||
--no-exclude Disable the default exclusion pattern
|
||||
-n, --dry-run Show what would be deleted without deleting
|
||||
-y, --yes Skip confirmation prompt
|
||||
```
|
||||
|
||||
Tags are sorted by image creation date (newest first). The newest N are kept; the rest are deleted. Tags matching the exclusion pattern are always preserved.
|
||||
|
||||
```bash
|
||||
# Preview: keep 5 most recent, protect default tags
|
||||
hubmanager prune myuser/myapp --keep 5 --dry-run
|
||||
|
||||
# Keep 3 most recent, auto-confirm
|
||||
hubmanager prune myuser/myapp --keep 3 --yes
|
||||
|
||||
# Delete anything older than 30 days
|
||||
hubmanager prune myuser/myapp --older-than 30 --dry-run
|
||||
|
||||
# Custom exclusion: never delete latest or any semver tag
|
||||
hubmanager prune myuser/myapp --keep 5 \
|
||||
--exclude "^(latest|v[0-9]+\.[0-9]+(\.[0-9]+)?)$"
|
||||
|
||||
# Prune everything (no exclusions)
|
||||
hubmanager prune myuser/myapp --keep 1 --no-exclude --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
hubmanager automatically detects the authentication method by probing the registry's `/v2/` endpoint:
|
||||
|
||||
| Registry type | Auth method |
|
||||
|---|---|
|
||||
| Docker Hub | Bearer tokens via `auth.docker.io` |
|
||||
| Harbor, self-hosted with token server | Bearer tokens via registry-configured realm |
|
||||
| Basic-auth self-hosted | HTTP Basic Auth on every request |
|
||||
| Public/anonymous registries | No auth |
|
||||
|
||||
Bearer tokens are cached in memory for the duration of the session and refreshed automatically when they expire.
|
||||
|
||||
### Docker Hub notes
|
||||
|
||||
- `list` uses the Docker Hub REST API (`hub.docker.com`) because the `_catalog` endpoint is restricted on Docker Hub.
|
||||
- `delete` is not supported via the v2 API on Docker Hub. Use the web UI at https://hub.docker.com.
|
||||
- `prune` is not supported on Docker Hub for the same reason.
|
||||
|
||||
---
|
||||
|
||||
## JSON output
|
||||
|
||||
All commands support `--json` for machine-readable output:
|
||||
|
||||
```bash
|
||||
# Get all tags as a JSON array
|
||||
hubmanager tags myapp --json | jq '.tags'
|
||||
|
||||
# Get digest of latest
|
||||
hubmanager inspect myapp:latest --json | jq '._digest'
|
||||
|
||||
# Delete and capture result
|
||||
hubmanager delete myapp:old --yes --json
|
||||
# {"deleted":true,"digest":"sha256:..."}
|
||||
|
||||
# Prune and capture counts
|
||||
hubmanager prune myapp --keep 3 --yes --json
|
||||
# {"deleted":5,"failed":0}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Success |
|
||||
| 1 | General / usage error |
|
||||
| 2 | Authentication failure |
|
||||
| 3 | Resource not found (404) |
|
||||
| 4 | Permission denied (403) |
|
||||
| 5 | Registry server error (5xx) |
|
||||
| 6 | Network / connectivity error |
|
||||
| 7 | Operation not supported by registry |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Mirror all tags of an image to a private registry
|
||||
for tag in $(hubmanager tags nginx --json | jq -r '.tags[]'); do
|
||||
hubmanager copy nginx:$tag \
|
||||
--src-registry https://registry-1.docker.io \
|
||||
--dst-registry https://registry.example.com \
|
||||
mycompany/nginx:$tag
|
||||
done
|
||||
|
||||
# List images older than 60 days (dry run)
|
||||
hubmanager prune myuser/myapp --older-than 60 --dry-run
|
||||
|
||||
# Get the SHA256 digest of the production image
|
||||
DIGEST=$(hubmanager inspect myuser/myapp:production --json | jq -r '._digest')
|
||||
echo "Production image: $DIGEST"
|
||||
|
||||
# Promote staging image to production (retag)
|
||||
hubmanager copy myuser/myapp:staging myuser/myapp:production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration reference
|
||||
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `REGISTRY` | Default registry URL |
|
||||
| `USERNAME` | Default username |
|
||||
| `PASSWORD` | Default password or token |
|
||||
| `REGISTRY_<ALIAS>_URL` | URL for a named registry alias |
|
||||
| `REGISTRY_<ALIAS>_USERNAME` | Username for a named alias |
|
||||
| `REGISTRY_<ALIAS>_PASSWORD` | Password for a named alias |
|
||||
|
||||
Aliases are case-insensitive and treat `-` as `_`. For example, alias `my-reg` maps to `REGISTRY_MY_REG_URL`.
|
||||
Reference in New Issue
Block a user