You've already forked hubmanager
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a59e416789 |
50
CLAUDE.md
50
CLAUDE.md
@@ -10,11 +10,48 @@ authentication detection (bearer token or HTTP basic auth).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## What Changed (Session 2026-02-21)
|
||||||
|
|
||||||
|
### Encrypted config values (`--encrypt`)
|
||||||
|
|
||||||
|
Added `openssl` AES-256-CBC encryption for passwords stored in the config file.
|
||||||
|
`openssl` is an **optional** dependency — it is only required when `enc:` prefixed values
|
||||||
|
are present in the config, or when `login --encrypt` is used.
|
||||||
|
|
||||||
|
**New functions** (Encryption helpers section):
|
||||||
|
|
||||||
|
| Function | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `_require_openssl()` | Die with a clear message if `openssl` is not installed |
|
||||||
|
| `_prompt_master_pass()` | Prompt once per session via `/dev/tty`; cache in `HM_MASTER_PASS` |
|
||||||
|
| `_prompt_set_master_pass()` | Prompt + confirm a new passphrase (used by `login --encrypt`) |
|
||||||
|
| `_encrypt_value(plaintext)` | AES-256-CBC encrypt → base64 ciphertext (no newlines) |
|
||||||
|
| `_decrypt_value(ciphertext)` | Decrypt base64 ciphertext → plaintext; die on wrong passphrase |
|
||||||
|
|
||||||
|
**Passphrase security**: passed to `openssl` via a `mktemp` file (`-pass file:`) to avoid
|
||||||
|
exposure in the process argument list (`ps aux`). The temp file is registered in
|
||||||
|
`HM_TMPFILES` and removed on exit.
|
||||||
|
|
||||||
|
**Config format**: encrypted values use an `enc:` prefix, e.g.:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
PASSWORD=enc:U2FsdGVkX1+...base64ciphertext...
|
||||||
|
REGISTRY_PROD_PASSWORD=enc:U2FsdGVkX1+...
|
||||||
|
```
|
||||||
|
|
||||||
|
Both `load_config()` and `resolve_registry_alias()` detect the prefix and call
|
||||||
|
`_decrypt_value` transparently.
|
||||||
|
|
||||||
|
**Version bump**: `0.1.0` → `0.2.0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## What Was Built (Session 2025-02-21)
|
## What Was Built (Session 2025-02-21)
|
||||||
|
|
||||||
### Primary file
|
### Primary file
|
||||||
|
|
||||||
`hubmanager` — executable Bash script, ~600 lines, no dependencies beyond `curl`, `jq`, Bash 4+.
|
`hubmanager` — executable Bash script, ~680 lines, no mandatory dependencies beyond `curl`, `jq`, Bash 4+.
|
||||||
|
`openssl` is required only when encrypted config values are used.
|
||||||
|
|
||||||
### Subcommands implemented
|
### Subcommands implemented
|
||||||
|
|
||||||
@@ -47,6 +84,7 @@ set -euo pipefail
|
|||||||
# --- Global state ---
|
# --- Global state ---
|
||||||
# --- Output / Formatting helpers ---
|
# --- Output / Formatting helpers ---
|
||||||
# --- Dependency check ---
|
# --- Dependency check ---
|
||||||
|
# --- Encryption helpers --- _encrypt_value(), _decrypt_value(), _prompt_master_pass()
|
||||||
# --- Config loading ---
|
# --- Config loading ---
|
||||||
# --- HTTP helpers --- raw_http(), get_response_header()
|
# --- HTTP helpers --- raw_http(), get_response_header()
|
||||||
# --- Authentication --- probe_registry_auth(), get_bearer_token(), make_auth_header()
|
# --- Authentication --- probe_registry_auth(), get_bearer_token(), make_auth_header()
|
||||||
@@ -96,6 +134,15 @@ main "$@"
|
|||||||
- Blob already at destination (`HEAD` returns 200) → skip
|
- Blob already at destination (`HEAD` returns 200) → skip
|
||||||
- Otherwise → download to temp file → `POST` initiate upload → `PUT` with digest
|
- Otherwise → download to temp file → `POST` initiate upload → `PUT` with digest
|
||||||
|
|
||||||
|
9. **Encrypted config values** (v0.2.0):
|
||||||
|
- `login --save --encrypt` prompts for a master passphrase (with confirmation), encrypts
|
||||||
|
the password with `openssl enc -aes-256-cbc -pbkdf2 -a`, and writes `PASSWORD=enc:<b64>`.
|
||||||
|
- Passphrase is passed to `openssl` via a temp file (`-pass file:`) — never via argv or env.
|
||||||
|
- `load_config` and `resolve_registry_alias` both check for the `enc:` prefix and call
|
||||||
|
`_decrypt_value`, which triggers `_prompt_master_pass` (once per session, cached in
|
||||||
|
`HM_MASTER_PASS`).
|
||||||
|
- `openssl` is an optional dependency: not checked at startup, only on first `enc:` encounter.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Global Variables (key ones)
|
## Global Variables (key ones)
|
||||||
@@ -110,6 +157,7 @@ main "$@"
|
|||||||
| `HM_AUTH_REALM["registry"]` | Bearer token endpoint URL |
|
| `HM_AUTH_REALM["registry"]` | Bearer token endpoint URL |
|
||||||
| `HM_TOKEN_CACHE["registry\|scope"]` | Cached bearer token |
|
| `HM_TOKEN_CACHE["registry\|scope"]` | Cached bearer token |
|
||||||
| `HM_TOKEN_EXPIRY["registry\|scope"]` | Token expiry (epoch seconds) |
|
| `HM_TOKEN_EXPIRY["registry\|scope"]` | Token expiry (epoch seconds) |
|
||||||
|
| `HM_MASTER_PASS` | Master passphrase for `enc:` config values (session-cached) |
|
||||||
| `HM_LAST_HTTP_CODE` | HTTP status of most recent request |
|
| `HM_LAST_HTTP_CODE` | HTTP status of most recent request |
|
||||||
| `HM_LAST_HEADERS_FILE` | Temp file path with response headers |
|
| `HM_LAST_HEADERS_FILE` | Temp file path with response headers |
|
||||||
| `HM_TMPFILES` | Array of temp files, cleaned up via `trap EXIT` |
|
| `HM_TMPFILES` | Array of temp files, cleaned up via `trap EXIT` |
|
||||||
|
|||||||
67
README.md
67
README.md
@@ -7,6 +7,7 @@ A Bash CLI tool to manage Docker Registry images remotely. Supports Docker Hub a
|
|||||||
- **Bash** 4.0+
|
- **Bash** 4.0+
|
||||||
- **curl**
|
- **curl**
|
||||||
- **jq**
|
- **jq**
|
||||||
|
- **openssl** *(optional — required only when using encrypted config values)*
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -25,6 +26,10 @@ install -m 755 hubmanager ~/bin/hubmanager
|
|||||||
hubmanager login --registry https://registry.example.com \
|
hubmanager login --registry https://registry.example.com \
|
||||||
--user admin --password secret --save
|
--user admin --password secret --save
|
||||||
|
|
||||||
|
# Save with password encrypted at rest
|
||||||
|
hubmanager login --registry https://registry.example.com \
|
||||||
|
--user admin --password secret --save --encrypt
|
||||||
|
|
||||||
# List all repositories
|
# List all repositories
|
||||||
hubmanager list
|
hubmanager list
|
||||||
|
|
||||||
@@ -74,9 +79,37 @@ hubmanager --registry staging list
|
|||||||
hubmanager --registry hub tags myuser/myapp
|
hubmanager --registry hub tags myuser/myapp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Encrypted credentials
|
||||||
|
|
||||||
|
Use `login --save --encrypt` to store the password encrypted with AES-256-CBC.
|
||||||
|
A master passphrase is prompted at save time (with confirmation) and on every subsequent
|
||||||
|
invocation that reads the config file. Requires `openssl`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hubmanager login --registry https://registry.example.com \
|
||||||
|
--user admin --password secret --save --encrypt
|
||||||
|
# New master passphrase: ****
|
||||||
|
# Confirm master passphrase: ****
|
||||||
|
# Login Succeeded — bearer auth, registry: https://registry.example.com
|
||||||
|
# Credentials saved to /home/user/.hubmanager.conf
|
||||||
|
# Password stored encrypted (AES-256-CBC). Master passphrase required on each use.
|
||||||
|
```
|
||||||
|
|
||||||
|
The config file stores an `enc:` prefixed ciphertext:
|
||||||
|
|
||||||
|
```text
|
||||||
|
REGISTRY=https://registry.example.com
|
||||||
|
USERNAME=admin
|
||||||
|
PASSWORD=enc:U2FsdGVkX1+...base64ciphertext...
|
||||||
|
```
|
||||||
|
|
||||||
|
The `enc:` prefix also works for named alias passwords (`REGISTRY_<ALIAS>_PASSWORD`).
|
||||||
|
On every command that reads the config, the master passphrase is prompted once and cached
|
||||||
|
for the duration of the session.
|
||||||
|
|
||||||
## Global Options
|
## Global Options
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager [OPTIONS] <command> [COMMAND OPTIONS]
|
hubmanager [OPTIONS] <command> [COMMAND OPTIONS]
|
||||||
|
|
||||||
-r, --registry <url> Registry base URL
|
-r, --registry <url> Registry base URL
|
||||||
@@ -98,11 +131,12 @@ hubmanager [OPTIONS] <command> [COMMAND OPTIONS]
|
|||||||
|
|
||||||
### `login` — Test and save credentials
|
### `login` — Test and save credentials
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager login [--registry URL] [--user USER] [--password PASS] [--save]
|
hubmanager login [--registry URL] [--user USER] [--password PASS] [--save] [--encrypt]
|
||||||
```
|
```
|
||||||
|
|
||||||
Validates credentials against the registry. Use `--save` to write them to the config file.
|
Validates credentials against the registry. Use `--save` to write them to the config file.
|
||||||
|
Add `--encrypt` to store the password encrypted with AES-256-CBC (requires `openssl`).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hubmanager login --registry https://registry.example.com \
|
hubmanager login --registry https://registry.example.com \
|
||||||
@@ -115,7 +149,7 @@ hubmanager login --registry https://registry.example.com \
|
|||||||
|
|
||||||
### `list` — List repositories
|
### `list` — List repositories
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager list [--limit N] [--last REPO]
|
hubmanager list [--limit N] [--last REPO]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -139,7 +173,7 @@ hubmanager list --json | jq '.repositories[]'
|
|||||||
|
|
||||||
### `tags` — List tags for an image
|
### `tags` — List tags for an image
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager tags <image> [--limit N] [--last TAG]
|
hubmanager tags <image> [--limit N] [--last TAG]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -162,7 +196,7 @@ hubmanager tags myuser/myapp --json | jq '.tags[]'
|
|||||||
|
|
||||||
### `inspect` — Show image details
|
### `inspect` — Show image details
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager inspect <image>:<tag|digest> [--platform OS/ARCH]
|
hubmanager inspect <image>:<tag|digest> [--platform OS/ARCH]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -201,7 +235,7 @@ hubmanager inspect myuser/myapp:latest --json | jq .
|
|||||||
|
|
||||||
### `delete` — Delete a tag or manifest
|
### `delete` — Delete a tag or manifest
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager delete <image>:<tag|digest> [--yes]
|
hubmanager delete <image>:<tag|digest> [--yes]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -225,9 +259,11 @@ hubmanager delete myuser/myapp@sha256:abc123... --yes
|
|||||||
|
|
||||||
### `copy` — Copy or retag an image
|
### `copy` — Copy or retag an image
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager copy <src-image>:<tag> <dst-image>:<tag> [options]
|
hubmanager copy <src-image>:<tag> <dst-image>:<tag> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
Options:
|
Options:
|
||||||
--src-registry URL Source registry (default: global --registry)
|
--src-registry URL Source registry (default: global --registry)
|
||||||
--dst-registry URL Destination registry (default: global --registry)
|
--dst-registry URL Destination registry (default: global --registry)
|
||||||
@@ -239,11 +275,13 @@ Options:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Same-registry retag** — attempts cross-repo blob mount (no data transfer):
|
**Same-registry retag** — attempts cross-repo blob mount (no data transfer):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hubmanager copy myuser/myapp:v1.2.3 myuser/myapp:stable
|
hubmanager copy myuser/myapp:v1.2.3 myuser/myapp:stable
|
||||||
```
|
```
|
||||||
|
|
||||||
**Cross-registry copy** — streams blobs from source to destination:
|
**Cross-registry copy** — streams blobs from source to destination:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hubmanager copy myuser/myapp:latest \
|
hubmanager copy myuser/myapp:latest \
|
||||||
--src-registry https://registry-1.docker.io \
|
--src-registry https://registry-1.docker.io \
|
||||||
@@ -252,6 +290,7 @@ hubmanager copy myuser/myapp:latest \
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Copy specific platform** from a multi-arch image:
|
**Copy specific platform** from a multi-arch image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hubmanager copy nginx:latest myuser/nginx-amd64:latest --platform linux/amd64
|
hubmanager copy nginx:latest myuser/nginx-amd64:latest --platform linux/amd64
|
||||||
```
|
```
|
||||||
@@ -260,7 +299,7 @@ hubmanager copy nginx:latest myuser/nginx-amd64:latest --platform linux/amd64
|
|||||||
|
|
||||||
### `prune` — Delete outdated tags
|
### `prune` — Delete outdated tags
|
||||||
|
|
||||||
```
|
```text
|
||||||
hubmanager prune <image> [options]
|
hubmanager prune <image> [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@@ -311,7 +350,7 @@ Bearer tokens are cached in memory for the duration of the session and refreshed
|
|||||||
### Docker Hub notes
|
### Docker Hub notes
|
||||||
|
|
||||||
- `list` uses the Docker Hub REST API (`hub.docker.com`) because the `_catalog` endpoint is restricted on Docker Hub.
|
- `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.
|
- `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.
|
- `prune` is not supported on Docker Hub for the same reason.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -341,7 +380,7 @@ hubmanager prune myapp --keep 3 --yes --json
|
|||||||
## Exit Codes
|
## Exit Codes
|
||||||
|
|
||||||
| Code | Meaning |
|
| Code | Meaning |
|
||||||
|------|---------|
|
| --- | --- |
|
||||||
| 0 | Success |
|
| 0 | Success |
|
||||||
| 1 | General / usage error |
|
| 1 | General / usage error |
|
||||||
| 2 | Authentication failure |
|
| 2 | Authentication failure |
|
||||||
@@ -380,12 +419,12 @@ hubmanager copy myuser/myapp:staging myuser/myapp:production
|
|||||||
## Configuration reference
|
## Configuration reference
|
||||||
|
|
||||||
| Key | Description |
|
| Key | Description |
|
||||||
|-----|-------------|
|
| --- | --- |
|
||||||
| `REGISTRY` | Default registry URL |
|
| `REGISTRY` | Default registry URL |
|
||||||
| `USERNAME` | Default username |
|
| `USERNAME` | Default username |
|
||||||
| `PASSWORD` | Default password or token |
|
| `PASSWORD` | Default password or token; prefix with `enc:` for encrypted values |
|
||||||
| `REGISTRY_<ALIAS>_URL` | URL for a named registry alias |
|
| `REGISTRY_<ALIAS>_URL` | URL for a named registry alias |
|
||||||
| `REGISTRY_<ALIAS>_USERNAME` | Username for a named alias |
|
| `REGISTRY_<ALIAS>_USERNAME` | Username for a named alias |
|
||||||
| `REGISTRY_<ALIAS>_PASSWORD` | Password for a named alias |
|
| `REGISTRY_<ALIAS>_PASSWORD` | Password for a named alias (supports `enc:` prefix) |
|
||||||
|
|
||||||
Aliases are case-insensitive and treat `-` as `_`. For example, alias `my-reg` maps to `REGISTRY_MY_REG_URL`.
|
Aliases are case-insensitive and treat `-` as `_`. For example, alias `my-reg` maps to `REGISTRY_MY_REG_URL`.
|
||||||
|
|||||||
107
hubmanager
107
hubmanager
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# hubmanager - Manage Docker Registry images remotely
|
# hubmanager - Manage Docker Registry images remotely
|
||||||
# Version: 0.1.0
|
# Version: 0.2.0
|
||||||
# Dependencies: curl, jq, bash 4+
|
# Dependencies: curl, jq, bash 4+
|
||||||
# Usage: hubmanager --help
|
# Usage: hubmanager --help
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ set -euo pipefail
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# --- Constants ---
|
# --- Constants ---
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
readonly HM_VERSION="0.1.0"
|
readonly HM_VERSION="0.2.0"
|
||||||
readonly HM_DEFAULT_REGISTRY="https://registry-1.docker.io"
|
readonly HM_DEFAULT_REGISTRY="https://registry-1.docker.io"
|
||||||
readonly HM_DEFAULT_CONFIG="${HOME}/.hubmanager.conf"
|
readonly HM_DEFAULT_CONFIG="${HOME}/.hubmanager.conf"
|
||||||
readonly HM_DOCKERHUB_API="https://hub.docker.com"
|
readonly HM_DOCKERHUB_API="https://hub.docker.com"
|
||||||
@@ -34,6 +34,7 @@ declare -A HM_TOKEN_EXPIRY=() # "registry|scope" -> epoch seconds
|
|||||||
declare -A HM_CONFIG_VARS=() # raw config key/value pairs
|
declare -A HM_CONFIG_VARS=() # raw config key/value pairs
|
||||||
|
|
||||||
HM_HUB_TOKEN="" # Docker Hub REST API token (JWT)
|
HM_HUB_TOKEN="" # Docker Hub REST API token (JWT)
|
||||||
|
HM_MASTER_PASS="" # Master passphrase for encrypted config values (session-scoped)
|
||||||
HM_TMPFILES=()
|
HM_TMPFILES=()
|
||||||
HM_LAST_HTTP_CODE=""
|
HM_LAST_HTTP_CODE=""
|
||||||
HM_LAST_HEADERS_FILE=""
|
HM_LAST_HEADERS_FILE=""
|
||||||
@@ -98,6 +99,66 @@ check_deps() {
|
|||||||
(( BASH_VERSINFO[0] >= 4 )) || die "Bash 4.0+ is required (found: $BASH_VERSION)."
|
(( BASH_VERSINFO[0] >= 4 )) || die "Bash 4.0+ is required (found: $BASH_VERSION)."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# --- Encryption helpers ---
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Verify openssl is available (only needed when enc: values are present)
|
||||||
|
_require_openssl() {
|
||||||
|
command -v openssl &>/dev/null || \
|
||||||
|
die "openssl is required for encrypted config values. Install openssl and retry."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt once per session; result cached in HM_MASTER_PASS
|
||||||
|
_prompt_master_pass() {
|
||||||
|
[[ -n "$HM_MASTER_PASS" ]] && return 0
|
||||||
|
_require_openssl
|
||||||
|
printf "hubmanager master passphrase: " >/dev/tty
|
||||||
|
read -rs HM_MASTER_PASS </dev/tty
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
[[ -n "$HM_MASTER_PASS" ]] || die "Master passphrase cannot be empty."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt and confirm a new master passphrase (used by login --encrypt)
|
||||||
|
_prompt_set_master_pass() {
|
||||||
|
_require_openssl
|
||||||
|
local pass1 pass2
|
||||||
|
printf "New master passphrase: " >/dev/tty
|
||||||
|
read -rs pass1 </dev/tty; printf "\n" >/dev/tty
|
||||||
|
printf "Confirm master passphrase: " >/dev/tty
|
||||||
|
read -rs pass2 </dev/tty; printf "\n" >/dev/tty
|
||||||
|
[[ "$pass1" == "$pass2" ]] || die "Passphrases do not match."
|
||||||
|
[[ -n "$pass1" ]] || die "Master passphrase cannot be empty."
|
||||||
|
HM_MASTER_PASS="$pass1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Encrypt PLAINTEXT using AES-256-CBC; outputs base64 ciphertext (no newlines)
|
||||||
|
_encrypt_value() {
|
||||||
|
local plaintext="$1"
|
||||||
|
_prompt_master_pass
|
||||||
|
local pass_file result
|
||||||
|
pass_file=$(mktemp); HM_TMPFILES+=("$pass_file")
|
||||||
|
printf '%s' "$HM_MASTER_PASS" > "$pass_file"
|
||||||
|
result=$(printf '%s' "$plaintext" | \
|
||||||
|
openssl enc -aes-256-cbc -pbkdf2 -a -pass "file:${pass_file}" 2>/dev/null | tr -d '\n')
|
||||||
|
[[ -n "$result" ]] || die "Encryption failed. Check that openssl is installed."
|
||||||
|
printf '%s' "$result"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Decrypt base64 CIPHERTEXT produced by _encrypt_value; outputs plaintext
|
||||||
|
_decrypt_value() {
|
||||||
|
local ciphertext="$1"
|
||||||
|
_prompt_master_pass
|
||||||
|
local pass_file result
|
||||||
|
pass_file=$(mktemp); HM_TMPFILES+=("$pass_file")
|
||||||
|
printf '%s' "$HM_MASTER_PASS" > "$pass_file"
|
||||||
|
if ! result=$(printf '%s' "$ciphertext" | \
|
||||||
|
openssl enc -d -aes-256-cbc -pbkdf2 -a -pass "file:${pass_file}" 2>/dev/null); then
|
||||||
|
die "Failed to decrypt config value. Wrong master passphrase?"
|
||||||
|
fi
|
||||||
|
printf '%s' "$result"
|
||||||
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# --- Config loading ---
|
# --- Config loading ---
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -138,7 +199,14 @@ load_config() {
|
|||||||
# Apply config defaults (CLI flags already set take precedence)
|
# Apply config defaults (CLI flags already set take precedence)
|
||||||
[[ -z "$HM_REGISTRY" && -v 'HM_CONFIG_VARS[REGISTRY]' ]] && HM_REGISTRY="${HM_CONFIG_VARS[REGISTRY]}"
|
[[ -z "$HM_REGISTRY" && -v 'HM_CONFIG_VARS[REGISTRY]' ]] && HM_REGISTRY="${HM_CONFIG_VARS[REGISTRY]}"
|
||||||
[[ -z "$HM_USERNAME" && -v 'HM_CONFIG_VARS[USERNAME]' ]] && HM_USERNAME="${HM_CONFIG_VARS[USERNAME]}"
|
[[ -z "$HM_USERNAME" && -v 'HM_CONFIG_VARS[USERNAME]' ]] && HM_USERNAME="${HM_CONFIG_VARS[USERNAME]}"
|
||||||
[[ -z "$HM_PASSWORD" && -v 'HM_CONFIG_VARS[PASSWORD]' ]] && HM_PASSWORD="${HM_CONFIG_VARS[PASSWORD]}"
|
if [[ -z "$HM_PASSWORD" && -v 'HM_CONFIG_VARS[PASSWORD]' ]]; then
|
||||||
|
local pw="${HM_CONFIG_VARS[PASSWORD]}"
|
||||||
|
if [[ "$pw" == enc:* ]]; then
|
||||||
|
HM_PASSWORD=$(_decrypt_value "${pw#enc:}")
|
||||||
|
else
|
||||||
|
HM_PASSWORD="$pw"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_registry_alias() {
|
resolve_registry_alias() {
|
||||||
@@ -153,7 +221,14 @@ resolve_registry_alias() {
|
|||||||
local pass_key="REGISTRY_${alias_upper}_PASSWORD"
|
local pass_key="REGISTRY_${alias_upper}_PASSWORD"
|
||||||
HM_REGISTRY="${HM_CONFIG_VARS[$url_key]}"
|
HM_REGISTRY="${HM_CONFIG_VARS[$url_key]}"
|
||||||
[[ -z "$HM_USERNAME" && -v "HM_CONFIG_VARS[$user_key]" ]] && HM_USERNAME="${HM_CONFIG_VARS[$user_key]}"
|
[[ -z "$HM_USERNAME" && -v "HM_CONFIG_VARS[$user_key]" ]] && HM_USERNAME="${HM_CONFIG_VARS[$user_key]}"
|
||||||
[[ -z "$HM_PASSWORD" && -v "HM_CONFIG_VARS[$pass_key]" ]] && HM_PASSWORD="${HM_CONFIG_VARS[$pass_key]}"
|
if [[ -z "$HM_PASSWORD" && -v "HM_CONFIG_VARS[$pass_key]" ]]; then
|
||||||
|
local pw="${HM_CONFIG_VARS[$pass_key]}"
|
||||||
|
if [[ "$pw" == enc:* ]]; then
|
||||||
|
HM_PASSWORD=$(_decrypt_value "${pw#enc:}")
|
||||||
|
else
|
||||||
|
HM_PASSWORD="$pw"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Strip trailing slash
|
# Strip trailing slash
|
||||||
@@ -537,7 +612,7 @@ ${C_BOLD}Commands:${C_RESET}
|
|||||||
delete <image>:<ref> Delete an image tag or manifest
|
delete <image>:<ref> Delete an image tag or manifest
|
||||||
copy <src> <dst> Copy/retag an image
|
copy <src> <dst> Copy/retag an image
|
||||||
prune <image> Delete outdated tags for an image
|
prune <image> Delete outdated tags for an image
|
||||||
login Test credentials and optionally save to config
|
login Test credentials and optionally save to config (--save [--encrypt])
|
||||||
|
|
||||||
Run ${C_BOLD}hubmanager <command> --help${C_RESET} for command-specific options.
|
Run ${C_BOLD}hubmanager <command> --help${C_RESET} for command-specific options.
|
||||||
EOF
|
EOF
|
||||||
@@ -555,18 +630,24 @@ cmd_version() { echo "hubmanager $HM_VERSION"; }
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
cmd_login() {
|
cmd_login() {
|
||||||
local do_save=false
|
local do_save=false
|
||||||
|
local do_encrypt=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--save) do_save=true; shift ;;
|
--save) do_save=true; shift ;;
|
||||||
|
--encrypt) do_encrypt=true; shift ;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: hubmanager login [--registry URL] [--user USER] [--password PASS] [--save]
|
Usage: hubmanager login [--registry URL] [--user USER] [--password PASS] [--save] [--encrypt]
|
||||||
|
|
||||||
Test credentials against the registry.
|
Test credentials against the registry.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--save Write validated credentials to the config file (~/.hubmanager.conf)
|
--save Write validated credentials to the config file (~/.hubmanager.conf)
|
||||||
|
--encrypt Encrypt the password in the config file with AES-256-CBC.
|
||||||
|
A master passphrase will be prompted; you must enter the same
|
||||||
|
passphrase on every subsequent hubmanager invocation that reads
|
||||||
|
the config file.
|
||||||
EOF
|
EOF
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
*) die "Unknown option: $1. Run 'hubmanager login --help'." ;;
|
*) die "Unknown option: $1. Run 'hubmanager login --help'." ;;
|
||||||
@@ -601,16 +682,28 @@ EOF
|
|||||||
|
|
||||||
if [[ "$do_save" == true ]]; then
|
if [[ "$do_save" == true ]]; then
|
||||||
local config="$HM_CONFIG_FILE"
|
local config="$HM_CONFIG_FILE"
|
||||||
|
local pw_entry=""
|
||||||
|
if [[ -n "$HM_PASSWORD" ]]; then
|
||||||
|
if [[ "$do_encrypt" == true ]]; then
|
||||||
|
_prompt_set_master_pass
|
||||||
|
local ciphertext
|
||||||
|
ciphertext=$(_encrypt_value "$HM_PASSWORD")
|
||||||
|
pw_entry="PASSWORD=enc:${ciphertext}"
|
||||||
|
else
|
||||||
|
pw_entry="PASSWORD=${HM_PASSWORD}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
{
|
{
|
||||||
echo "# hubmanager configuration"
|
echo "# hubmanager configuration"
|
||||||
echo "# Generated by: hubmanager login"
|
echo "# Generated by: hubmanager login"
|
||||||
echo ""
|
echo ""
|
||||||
echo "REGISTRY=${HM_REGISTRY}"
|
echo "REGISTRY=${HM_REGISTRY}"
|
||||||
[[ -n "$HM_USERNAME" ]] && echo "USERNAME=${HM_USERNAME}"
|
[[ -n "$HM_USERNAME" ]] && echo "USERNAME=${HM_USERNAME}"
|
||||||
[[ -n "$HM_PASSWORD" ]] && echo "PASSWORD=${HM_PASSWORD}"
|
[[ -n "$pw_entry" ]] && echo "$pw_entry"
|
||||||
} > "$config"
|
} > "$config"
|
||||||
chmod 600 "$config"
|
chmod 600 "$config"
|
||||||
info "Credentials saved to $config"
|
info "Credentials saved to $config"
|
||||||
|
[[ "$do_encrypt" == true ]] && info "Password stored encrypted (AES-256-CBC). Master passphrase required on each use."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user