Current Version: v1.0
The Mirror-GUI Application provides a RESTful API for managing OpenShift Container Platform mirroring operations. The UI and API are served on the same port. Use the URL printed by the startup script and append /api.
- Archive Size Control: Optional
archiveSizeparameter to limit archive file sizes (in GiB) - Persistent Mirror Storage: Mirror archives are saved to host filesystem and survive container restarts
- Custom Mirror Destinations: Optional subdirectory specification for organized mirror storage
- Health Monitoring: Dedicated health check endpoint for container orchestration
- OCP Versions: Supports OpenShift Container Platform versions 4.16, 4.17, 4.18, 4.19, 4.20, and 4.21
http://localhost:<port>/api
The default port is 3000. All startup scripts (./mirror-gui.sh, ./local-build.sh) automatically select another free port if 3000 is already occupied and print the actual URL.
Currently, the API does not require authentication. All endpoints are accessible without credentials.
API responses are returned in JSON format. Most endpoints return domain-specific JSON directly (e.g., { "status": "healthy" }, { "registries": [...] }). Some endpoints use a wrapper with success/data fields. Error responses typically include an error field:
{
"error": "Error description"
}The application includes comprehensive validation for configuration parameters:
- Platform Channels: Validates that min/max versions are compatible with the selected channel
- Operator Channels: Validates version ranges against available operator versions
- Auto-correction: Automatically fixes invalid ranges (min > max scenarios)
- Channel Compatibility: Ensures versions match channel major.minor versions (e.g.,
stable-4.21requires4.21.xversions)
- Platform Channels: Validation triggers on
onBlurevents (when user finishes typing) - Operator Channels: Validation triggers after dropdown selection
- Real-time Feedback: Toast notifications provide immediate validation feedback
// Valid configuration for stable-4.21 channel
{
"channel": "stable-4.21",
"minVersion": "4.21.1",
"maxVersion": "4.21.9"
}
// Invalid configuration - version mismatch
{
"channel": "stable-4.21",
"minVersion": "4.20.1", // ❌ Wrong major.minor version
"maxVersion": "4.21.9"
}
// Invalid configuration - min > max
{
"channel": "stable-4.21",
"minVersion": "4.21.9", // ❌ Greater than max
"maxVersion": "4.21.1" // ❌ Less than min
}Health check endpoint for container orchestration and monitoring.
Response:
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00Z",
"service": "mirror-gui"
}Get system information including versions, disk space, architecture, and cache details.
Response:
{
"ocMirrorVersion": "4.21.0",
"systemArchitecture": "x86_64",
"availableDiskSpace": 480673603584,
"totalDiskSpace": 876538232832,
"hostDataDir": "/home/user/mirror-gui/data",
"cacheDir": "/app/data/cache",
"hostCacheDir": "/home/user/mirror-gui/data/cache",
"cacheSizeBytes": 2552543632
}Response Fields:
hostDataDir: The host-side data directory pathcacheDir: The cache directory inside the containerhostCacheDir: The cache directory mapped to the host pathcacheSizeBytes: Current cache size in bytes
Get system status including oc-mirror version, overall health, and pull secret detection.
Response:
{
"ocMirrorVersion": "2.0.0",
"systemHealth": "healthy",
"pullSecretDetected": true
}Response Fields:
ocMirrorVersion: The version of the oc-mirror binarysystemHealth: Overall system health indicator ("healthy"or"degraded")pullSecretDetected: Whether a valid pull secret file was found at the configured path
Get available system paths for mirror storage and other operations.
Response:
{
"paths": [
{
"path": "/app/data",
"label": "Data Directory",
"description": "Persistent - mounted volume, contains configs, operations, logs, cache, and mirrors",
"available": true
},
{
"path": "/app/data/mirrors",
"label": "Mirror Storage",
"description": "Persistent - base directory for all mirror archives",
"available": true
}
]
}Get list of existing subdirectories under the mirror base directory. Used by the Mirror Destination Folder typeahead to suggest existing folders.
Response:
{
"folders": ["default", "odf", "production"]
}folders: Sorted array of directory names underMIRROR_BASE_DIR. Returns an empty array if the base directory does not exist yet.
Create a new subdirectory under the mirror base directory. The folder name must contain only letters, numbers, dashes, and underscores.
Request:
{
"name": "group-sync"
}Response (201):
{
"created": "group-sync",
"path": "/app/data/mirrors/group-sync"
}Error (400):
{
"error": "Use only letters, numbers, dashes, and underscores"
}Get application statistics.
Response:
{
"success": true,
"data": {
"totalOperations": 10,
"successfulOperations": 8,
"failedOperations": 1,
"runningOperations": 1,
"stoppedOperations": 0
}
}Get recent operations for dashboard display.
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Operation Name",
"status": "running",
"startTime": "2024-01-15T10:30:00Z",
"endTime": null
}
]
}Get list of saved configurations.
Response:
{
"success": true,
"data": [
{
"id": "config-id",
"name": "Configuration Name",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
]
}Download a saved ImageSetConfiguration YAML file from the configs directory. The filename must be a basename ending in .yaml or .yml (path traversal is rejected).
Response: Raw file bytes with Content-Disposition: attachment and Content-Type: application/x-yaml.
Error (400): Invalid filename (wrong extension or path).
Error (404): File does not exist.
Save a new configuration.
Request Body:
{
"name": "Configuration Name",
"config": {
"kind": "ImageSetConfiguration",
"apiVersion": "mirror.openshift.io/v2alpha1",
"archiveSize": 4,
"mirror": { ... }
}
}Configuration Parameters:
archiveSize(number, optional): Maximum size in GiB for archive files when mirroring to disk. Leave empty/omit to use default behavior.
Response:
{
"success": true,
"data": {
"id": "config-id",
"message": "Configuration saved successfully"
}
}Upload a YAML configuration file.
Request Body:
{
"filename": "my-config.yaml",
"content": "kind: ImageSetConfiguration\napiVersion: mirror.openshift.io/v2alpha1\nmirror:\n operators:\n - catalog: registry.redhat.io/redhat/redhat-operator-index:v4.21\n packages:\n - name: advanced-cluster-management"
}Response:
{
"success": true,
"data": {
"message": "Configuration uploaded successfully",
"filename": "my-config.yaml"
}
}Error Response (File Already Exists):
{
"success": false,
"error": "File already exists",
"code": "FILE_EXISTS",
"data": {
"filename": "my-config.yaml"
}
}Delete a configuration file.
Parameters:
filename: Name of the configuration file to delete
Response:
{
"success": true,
"data": {
"message": "Configuration deleted successfully",
"filename": "my-config.yaml"
}
}Error Response (File Not Found):
{
"success": false,
"error": "Configuration file not found",
"code": "FILE_NOT_FOUND"
}Get available OpenShift Container Platform channels.
Response:
{
"success": true,
"data": [
{
"name": "stable-4.18",
"description": "Stable 4.18 channel"
}
]
}Get available operator catalogs.
Query Parameters:
version(optional): Filter by OCP version (4.16, 4.17, 4.18, 4.19, 4.20, 4.21)
Catalog Fetch Workflow:
- Catalog snapshots are produced by host-side sync workflows such as
./sync-catalogs.shor any./local-build.shbuild path - Each fetch run always performs a full pull of all supported catalogs; there is no freshness window or separate
--forcemode - The API serves the current local catalog snapshot available under
catalog-data/
Response:
[
{
"name": "redhat-operator-index",
"url": "registry.redhat.io/redhat/redhat-operator-index:v4.21",
"description": "Red Hat certified operators",
"operatorCount": 175,
"digest": "sha256:a1b2c3d4e5f6...",
"syncedAt": "2026-05-11T19:30:00Z"
},
{
"name": "certified-operator-index",
"url": "registry.redhat.io/redhat/certified-operator-index:v4.21",
"description": "Certified operators",
"operatorCount": 120,
"digest": null,
"syncedAt": null
}
]Response Fields:
name: Catalog type identifier (e.g.,redhat-operator-index)url: Full registry URL including OCP version tagdescription: Human-readable catalog descriptionoperatorCount: Number of operators available in this catalogdigest: SHA-256 image digest of the catalog (e.g.,sha256:a1b2...), ornullif not yet synced. Populated when catalogs are synced viaPOST /api/catalogs/syncor during image build.syncedAt: ISO 8601 timestamp of when the digest was captured, ornullif not yet synced.
Supported OCP Versions:
- 4.16
- 4.17
- 4.18
- 4.19
- 4.20
- 4.21
Get available operators from catalogs.
Query Parameters:
catalog(optional): Filter by specific catalog URL
Response:
{
"success": true,
"data": [
{
"name": "advanced-cluster-management",
"catalog": "registry.redhat.io/redhat/redhat-operator-index:v4.18",
"description": "Advanced Cluster Management for Kubernetes"
}
]
}Get available channels for a specific operator.
Parameters:
operator: Operator name
Response:
{
"success": true,
"data": [
{
"name": "release-2.8",
"description": "Release 2.8 channel"
}
]
}Get available versions for a specific operator.
Parameters:
operator: Operator name
Query Parameters:
catalog(optional): Filter by specific catalog URLchannel(optional): Filter by channel name
Response:
{
"versions": ["1.0.0", "1.1.0", "1.2.0"]
}Get channels for an operator from a specific catalog (batch query).
Query Parameters:
catalogUrl(required): Full catalog URL (e.g.,registry.redhat.io/redhat/redhat-operator-index:v4.21)operatorName(required): Operator package name
Response:
[
{
"name": "stable-v4.21",
"availableVersions": ["4.19.0", "4.19.1", "4.19.2"]
}
]Reload the in-memory operator cache from the current local catalog snapshot or fallback sources. This does not pull fresh catalogs from remote registries.
Response:
{
"message": "Operator cache refreshed successfully"
}Get dependencies for a specific operator.
Parameters:
operator: Operator name
Query Parameters:
catalogUrl(optional): Specific catalog URL to search. If omitted, searches all catalogs.
Example Request:
curl "http://localhost:3000/api/operators/odf-operator/dependencies?catalogUrl=registry.redhat.io/redhat/redhat-operator-index:v4.21"Response:
{
"operator": "odf-operator",
"catalogType": "redhat-operator-index",
"catalogVersion": "v4.18",
"dependencies": [
{
"packageName": "mcg-operator",
"versionRange": ">=4.9.0 <=4.17.0",
"requiredBy": "odf-operator",
"catalog": "registry.redhat.io/redhat/redhat-operator-index:v4.18"
}
],
"count": 1
}Response (no dependencies):
{
"operator": "some-operator",
"dependencies": [],
"message": "No dependencies found for this operator"
}Notes:
- Dependencies are pre-computed during catalog fetch for faster runtime lookups
- If
catalogUrlis omitted, searches all available catalogs and returns the first match
Get list of all operations.
Query Parameters:
status(optional): Filter by status (running, completed, failed, stopped)
Response:
{
"success": true,
"data": [
{
"id": "operation-id",
"name": "Operation Name",
"status": "success",
"startedAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:45:00Z",
"duration": 900,
"configFile": "my-config.yaml",
"mirrorDestination": "/app/data/mirrors/default"
}
]
}Operation Status Values:
running- Operation is currently executingsuccess- Operation completed successfullyfailed- Operation failed with errorsstopped- Operation was manually stopped
Get operation history (alias for /api/operations).
Start a new mirror operation.
Request Body:
{
"name": "Operation Name",
"configId": "config-id",
"mirrorDestinationSubdir": "default"
}Request Parameters:
name(string, required): Name for the operationconfigId(string, required): Configuration file name (e.g., "my-config.yaml")mirrorDestinationSubdir(string, optional): Subdirectory name within/app/data/mirrors/where mirror files will be saved.- If not provided or empty, defaults to
default - Must be alphanumeric with dashes and underscores only (no slashes or special characters)
- Examples:
default,odf,production,test-123
- If not provided or empty, defaults to
Response:
{
"success": true,
"data": {
"id": "operation-id",
"message": "Operation started successfully",
"mirrorDestination": "/app/data/mirrors/default"
}
}Error Response (Invalid Subdirectory):
{
"success": false,
"error": "Subdirectory name contains invalid characters",
"provided": "invalid/path",
"help": "Use only letters, numbers, dashes (-), and underscores (_)"
}Error Response (Permission Denied):
{
"success": false,
"error": "Mirror destination directory exists but is not writable",
"path": "/app/data/mirrors/custom",
"code": "EACCES",
"details": "Permission denied",
"help": "The directory exists but the container cannot write to it. Check permissions on the host."
}Notes:
- Mirror archives are saved persistently to the host filesystem at
data/mirrors/{subdirectory}/on the host - Files survive container restarts
- The full host path is displayed in the operation details after completion
Get detailed information about a specific operation.
Parameters:
id: Operation ID
Response:
{
"success": true,
"data": {
"id": "operation-id",
"name": "Operation Name",
"status": "success",
"startedAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:45:00Z",
"duration": 900,
"configFile": "my-config.yaml",
"mirrorDestination": "/app/data/mirrors/default",
"config": { ... },
"logs": "..."
}
}Response Fields:
mirrorDestination: The full container path where mirror files are saved (e.g.,/app/data/mirrors/default)- Host path is
{project-root}/data/mirrors/{subdirectory}/where{project-root}is typically the application directory
Get operation logs.
Parameters:
id: Operation ID
Response:
{
"success": true,
"data": {
"logs": "Operation log content..."
}
}Get real-time operation log stream (Server-Sent Events).
Parameters:
id: Operation ID
Response: Server-Sent Events stream
Note: Logs are persisted to data/logs/ directory and survive container restarts.
Stop a running operation.
Parameters:
id: Operation ID
Response:
{
"success": true,
"data": {
"message": "Operation stopped successfully"
}
}Delete an operation record and its associated log file.
Parameters:
id: Operation ID
Response:
{
"message": "Operation deleted successfully"
}Bulk delete: The UI supports selecting multiple operations and deleting them in parallel by issuing concurrent DELETE requests. There is no dedicated bulk-delete endpoint; the frontend calls DELETE /api/operations/:id for each selected operation.
Example (single):
curl -X DELETE http://localhost:3000/api/operations/abc123Check whether a pull secret is detected.
Response:
{
"detected": true,
"path": "/app/pull-secret.json"
}Get the current pull secret content for viewing/editing.
Response:
{
"content": "{\"auths\":{...}}"
}Save a pull secret. The content is validated as JSON and saved to the configured AUTHFILE_PATH.
Request Body:
{
"content": "{\"auths\":{\"registry.redhat.io\":{\"auth\":\"...\",\"email\":\"...\"}}}"
}Remove the pull secret file.
Response:
{
"message": "Pull secret removed successfully"
}Get registries parsed from the pull secret with authentication info and cached verification status. Non-registry hosts (e.g. cloud.openshift.com) are filtered out.
Response:
{
"registries": [
{
"registry": "registry.redhat.io",
"username": "user",
"hasAuth": true,
"status": "authenticated",
"error": null
}
]
}Response Fields:
status: Cached verification result --"not_verified"(default),"authenticated", or"failed". Persists in-memory until the server process restarts.error: Error details whenstatusis"failed", otherwisenull/undefined.
Verify authentication against a specific registry using Docker v2 auth flow. Results are cached in memory and reflected in subsequent GET /api/registries responses.
Request Body:
{
"registry": "registry.redhat.io"
}Response:
{
"registry": "registry.redhat.io",
"status": "authenticated"
}Response (failed):
{
"registry": "registry.redhat.io",
"status": "failed",
"error": "Authentication failed (401)"
}Delete all files in the cache directory.
Response:
{
"message": "Cache cleaned up successfully"
}Notes:
- The cache directory is set via the
OC_MIRROR_CACHE_DIRenvironment variable (default:/app/data/cache) - To override, set
CACHE_DIRwhen starting the app:CACHE_DIR=/tmp/cache ./mirror-gui.sh
Trigger a full operator catalog sync from registry.redhat.io. Requires a pull secret. Runs sync-catalogs.sh in the background and streams progress to the sync status endpoint.
Response:
{
"status": "started",
"message": "Catalog sync started"
}Response (no pull secret):
{
"error": "Pull secret not detected. Please configure a pull secret first."
}Get current catalog sync status, progress, logs, and diff.
Response:
{
"status": "running",
"syncStartTime": "2026-05-07T10:00:00.000Z",
"lastSyncTime": null,
"successCount": 5,
"failedCount": 0,
"totalCount": 18,
"completedCatalogs": 5,
"currentCatalog": "certified-operator-index:v4.18",
"logs": ["Extracting redhat-operator-index:v4.16 ...", "..."],
"diff": [],
"hasRuntimeSyncData": true
}Response Fields:
hasRuntimeSyncData:truewhen runtime-synced catalog data is present on disk (same probe asDELETE /api/catalogs/sync/data); use this to enable a “clear sync data” action in the UIsyncStartTime: ISO timestamp when the current/last sync startedcompletedCatalogs: Number of catalogs processed so farcurrentCatalog: Catalog currently being processed (while running)diff: Array of catalog change entries after sync completes, each withcatalog,newOperators,removedOperators, andupdatedOperators(with version details)
Clear runtime-synced catalog data and fall back to built-in catalog data baked into the container image. Does not require an app restart.
Response:
{
"message": "Runtime catalog data cleared. Reverted to built-in catalog data.",
"operatorCount": 243
}| Code | Description |
|---|---|
CONFIG_NOT_FOUND |
Configuration not found |
OPERATION_NOT_FOUND |
Operation not found |
INVALID_CONFIG |
Invalid configuration format |
OPERATION_FAILED |
Operation execution failed |
REGISTRY_AUTH_FAILED |
Registry authentication failed |
CATALOG_FETCH_FAILED |
Failed to fetch operator catalog |
FILE_EXISTS |
Configuration file already exists |
FILE_NOT_FOUND |
Configuration file not found |
INVALID_YAML |
Invalid YAML format in uploaded file |
INVALID_KIND |
Invalid ImageSetConfiguration kind |
INVALID_API_VERSION |
Invalid API version in uploaded file |
EACCES |
Permission denied (file system access error) |
INVALID_SUBDIRECTORY |
Invalid subdirectory name (contains path separators or invalid characters) |
SUBDIRECTORY_NOT_WRITABLE |
Mirror destination subdirectory exists but is not writable |
INVALID_PULL_SECRET |
Invalid pull secret format or content |
SYSTEM_ERROR |
Internal system error |
Currently, there are no rate limits implemented on the API endpoints.
The API supports CORS and can be accessed from web browsers. All origins are allowed in development mode.
The application provides multiple health check endpoints:
/api/health: Simple JSON health check endpoint for container orchestration (Docker HEALTHCHECK, Kubernetes liveness probes, etc.)/api/system/info: Detailed system information including versions, architecture, resource usage, and host data directory/api/system/status: System status summary including oc-mirror version, system health, and pull secret detection
All three endpoints can be used by load balancers, monitoring systems, and container orchestration platforms.
# Get system information
curl http://localhost:3000/api/system/info
# Health check
curl http://localhost:3000/api/health
# Get system paths
curl http://localhost:3000/api/system/paths
# List existing mirror destination folders
curl http://localhost:3000/api/mirror-folders
# Create a new mirror destination folder
curl -X POST http://localhost:3000/api/mirror-folders \
-H "Content-Type: application/json" \
-d '{"name": "group-sync"}'
# Download a saved configuration YAML (URL-encode the filename if needed)
curl -OJ "http://localhost:3000/api/config/download/my-config.yaml"
# Start an operation with default mirror destination
curl -X POST http://localhost:3000/api/operations/start \
-H "Content-Type: application/json" \
-d '{"name": "My Operation", "configId": "my-config.yaml"}'
# Start an operation with custom mirror destination subdirectory
curl -X POST http://localhost:3000/api/operations/start \
-H "Content-Type: application/json" \
-d '{"name": "My Operation", "configId": "my-config.yaml", "mirrorDestinationSubdir": "odf"}'
# Get operation logs
curl http://localhost:3000/api/operations/operation-123/logs
# Check pull secret status
curl http://localhost:3000/api/pull-secret/status
# Upload a pull secret
curl -X POST http://localhost:3000/api/pull-secret \
-H "Content-Type: application/json" \
-d '{"content": "{\"auths\":{\"registry.redhat.io\":{\"auth\":\"...\",\"email\":\"...\"}}}"}'
# List registries from pull secret
curl http://localhost:3000/api/registries
# Verify registry authentication
curl -X POST http://localhost:3000/api/registries/verify \
-H "Content-Type: application/json" \
-d '{"registry": "registry.redhat.io"}'
# Clean up cache
curl -X POST http://localhost:3000/api/cache/cleanup// Get available operators
const response = await fetch('/api/operators');
const data = await response.json();
// Start operation with default mirror destination
const startResponse = await fetch('/api/operations/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'My Operation',
configId: 'my-config.yaml'
})
});
// Start operation with custom mirror destination subdirectory
const startResponseCustom = await fetch('/api/operations/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'ODF Mirror Operation',
configId: 'odf-config.yaml',
mirrorDestinationSubdir: 'odf'
})
});
// Check health
const healthResponse = await fetch('/api/health');
const healthData = await healthResponse.json();
console.log('Health status:', healthData.status);