π Overview
MirrorURL mirrors public HTTP(S) directory trees and downloads explicitly selected files. Mirror mode discovers a directory listing, downloads missing or changed files, and can preview, move, or delete obsolete local files. Download mode accepts file paths or URL lists without discovery or freshness checks. The default cleanup policy preserves obsolete files.
πͺ Incremental Mirroring
Repeat the same command to check for new or changed files using ETags, sizes, and modification times when available.
π Parallel Downloads
HTTPX and aiohttp support parallel files and verified chunks in both mirror and download modes. Completed files are published atomically.
π Known-File Downloads
Download one file, several root-relative paths, or an exact URL list using --mode download, without requiring a directory listing.
π Optional SHA-256 Receipts
Enable --verify-content to record local file hashes and verify an existing mirror against saved receipts.
π Destination Ownership
Cooperating processes lock destinations and shared state. The next owner reclaims recorded abandoned chunk work after an interrupted run.
ποΈ Explicit Cleanup
safe preserves obsolete files, preview reports actions, move archives them, and delete removes them.
π Security Protections
Transport IP restrictions, URL-scope checks, filename sanitization, request pacing, and circuit breakers are enabled by default.
π Discover Before Syncing
--list-dirs and --list-files inspect the remote selection. CLI-only listing runs require only --url.
π CLI and Python API
Use mirror-url, python -m mirror_url, or the MirrorURL context manager in your own Python program.
This page documents v3.8.0. See the User Guide, Developer Guide, and changelog for the full project documentation.
π¦ Installation
Python 3.10 or newer is required. CI covers Python 3.10β3.14. Pip installs httpx[http2], aiohttp, pydantic v2, PyYAML, portalocker (with pywin32 on Windows), and tqdm. Both transfer backends and progress bars work in a standard installation.
Install from PyPI
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install mirror-url
# Verify the package and command
python -m pip check
mirror-url --version
mirror-url --help
On Windows, activate the environment with .venv\Scripts\Activate.ps1 in PowerShell.
Optional packages and upgrades
# Optional acceleration and monitoring packages
python -m pip install "mirror-url[all]"
# Upgrade the installed package
python -m pip install --upgrade mirror-url
Extras: fast (stringzilla + lxml), monitor (psutil), and all (all optional runtime packages). The progress and aiohttp extras remain compatibility aliases; their features are included in the base package. Use dev for contributor tools.
Install the documented release from Git, build a wheel, or use pipx
# Install this documented release in an activated environment
python -m pip install "git+https://github.com/bpodlipnik/mirror-url.git@v3.8.0"
# Or build a wheel from a checkout
git clone https://github.com/bpodlipnik/mirror-url.git
cd mirror-url
git checkout v3.8.0
python -m pip install build
python -m build
python -m pip install "dist/mirror_url-3.8.0-py3-none-any.whl[all]"
# Or install an isolated command with pipx
pipx install mirror-url
Build a Docker image from the wheel
Place the built wheel in dist/ next to your Dockerfile:
FROM python:3.12-slim
COPY dist/mirror_url-3.8.0-py3-none-any.whl /tmp/
RUN pip install --no-cache-dir "/tmp/mirror_url-3.8.0-py3-none-any.whl[all]"
ENTRYPOINT ["mirror-url"]
Allow space for the mirrored data and staging files. See download modes for temporary storage requirements.
β‘ Quick Start
Replace https://example.com/data/ with your archive's directory-listing URL. Keep logs outside the destination tree.
1. Inspect the archive
# Immediate child directories; no destination or log path required
mirror-url --url https://example.com/data/ --list-dirs
# Last five filenames in each directory, sorted lexicographically
mirror-url --url https://example.com/data/ --list-files 5
2. Preview a run
mirror-url --url https://example.com/data/ \
--dest-path ./mirror/data --log-path ./logs --dry-run
3. Mirror using the default cleanup policy
mirror-url --url https://example.com/data/ \
--dest-path ./mirror/data --log-path ./logs
Re-run the same command for incremental synchronization. Obsolete local files are preserved by default; files identified as changed may still be replaced.
4. Choose cleanup explicitly
# Preview obsolete-file actions without changing mirrored files
mirror-url --url https://example.com/data/ \
--dest-path ./mirror/data --log-path ./logs --cleanup preview --dry-run
# Archive obsolete files in a sibling data_obsolete directory
mirror-url --url https://example.com/data/ \
--dest-path ./mirror/data --log-path ./logs --cleanup move
For repeatable jobs, create a configuration file and run mirror-url --config mirror.yaml.
πͺ Mirroring & Cleanup
Cleanup reconciles obsolete local files within the current remote selection. Filters, directory exclusions, depth limits, and skipped duplicate subtrees define that selection; files outside it are preserved. This is one-way synchronization from the remote archive to local storage.
| Policy | Obsolete local files | Downloads and updates |
|---|---|---|
--cleanup safe (default) | Preserved | Run normally |
--cleanup preview | Actions reported; no move or deletion | Still run; add --dry-run to prevent mirrored-file changes |
--cleanup move | Moved to the sibling <dest>_obsolete/ directory | Run normally |
--cleanup delete | Deleted; --confirm-delete adds an interactive prompt | Run normally |
# Explicit deletion after reviewing the selected scope
mirror-url --config mirror.yaml --cleanup delete --confirm-delete
--cleanup preview previews only cleanup. --dry-run prevents downloads and changes to mirrored files, but still makes listing/metadata requests and may create log/cache bookkeeping outside the mirrored tree.Cleanup preserves local symlinks and the reserved .mirror-url-state/ directory. An incomplete scan skips cleanup and fails the run; a complete empty scan can clean the selected local files. Cleanup failures also fail the run. Failed moves preserve their source files and never fall back to deletion.
Freshness checks use available ETags, sizes, and timestamps. They do not compare downloaded files with a remote cryptographic digest, and size/time checks can miss changes that preserve those values.
π Download Known Files
Use --mode download when you already know the files to fetch. It downloads every selected file without listing directories, checking freshness, or cleaning obsolete files. Choose positional files or one --url-list, not both.
One file with shortcut defaults
mirror-url --mode download https://example.com/data/a.fits
# Choose persistent paths and show progress
mirror-url --mode download https://example.com/data/large.fits \
--dest-path ./downloads --log-path ./logs \
--backend aiohttp --streaming-parallel --print-logs --progress-bar
Without an explicit root, absolute file URLs sharing one parent infer that parent as their scope. Positional files default to the current directory and a destination-specific log folder under the system temporary directory. Explicit --url, --dest-path, and --log-path override those defaults.
Several files relative to the remote root
mirror-url --mode download --url https://example.com/data/ \
a.fits nested/b.fits --dest-path ./downloads --log-path ./logs
This writes ./downloads/a.fits and ./downloads/nested/b.fits. Inputs are literal names, without glob expansion. --dir-suffix narrows selection; file inputs still include that suffix relative to --url.
A UTF-8 list of paths or absolute URLs
# Save as files.txt; blank lines and comments are ignored
a.fits
nested/b.fits
https://example.com/data/c.fits
mirror-url --mode download --url https://example.com/data/ \
--url-list files.txt --dest-path ./downloads --log-path ./logs \
--backend httpx --concurrency 6 --parallel-downloads --verify-content
All files must stay within the selected root and map to unambiguous local paths. Existing files require a matching ownership receipt or explicit --overwrite; symlinks remain forbidden. A matching receipt permits replacement and does not skip the download.
Both backends support verified chunks. Explicit chunk modes add range-metadata HEAD probes; ineligible files fall back to whole-file GETs. Known-file downloads restart interrupted files and do not resume partials across runs. Chunk scratch stays inside the destination, so omit --chunk-assembly-dir.
Download mode rejects discovery filters, --missing-files, --check-files, --auto-concurrency, and cleanup policies other than safe. Use mirror mode for incremental synchronization. See the known-URL guide.
π― Download Modes & Resume
Mirror mode selects a transfer method automatically unless you choose an explicit flag. Download mode uses whole-file GETs by default; either parallel flag enables eligible chunks. HTTPX (default) and aiohttp support both chunk methods in both modes. Aiohttp uses HTTP/1.1; --http2 applies to HTTPX.
| Mode | Flag | Current behavior |
|---|---|---|
| Automatic | No flag | Mirror mode selects a method; download mode uses whole-file GETs |
| Sequential | --sequential-downloads | Downloads one file at a time; metadata and size probes may still run concurrently |
| Traditional parallel | --parallel-downloads | Parallel files; eligible files use temporary chunks, verification, assembly, and atomic replacement |
| Streaming parallel | --streaming-parallel | Parallel files; eligible chunks write to a pre-allocated staging file and publish atomically after verification |
--max-concurrent-downloads (alias --concurrency) limits files, --max-chunks limits chunks per file, and --max-parallel-chunks limits chunk work across files. --auto-concurrency adjusts admitted files for HTTPX mirror mode; it is rejected with aiohttp and in download mode.
--min-chunk-size, byte Range support, and a strong ETag. Each chunk must return matching HTTP 206, Content-Range, length, and ETag before atomic publication. Ineligible files use whole-file transfers; the log explains the fallback reason.Temporary storage
Traditional mode can require roughly two additional file-sized copies for temporary chunks and assembly; streaming mode roughly one staging allocation. Existing destination files remain in place until verified replacement. In mirror mode, --chunk-assembly-dir selects a parent for owned chunk workspaces; otherwise they use reserved destination state. Final assembly and streaming staging stay on the destination filesystem. Download mode requires chunk scratch inside the destination.
Interrupted transfers
HTTPX mirror whole-file partials live in owned .mirror-url-state/ storage and can resume when the recorded URL, size, and strong ETag match, including whole-file fallbacks from parallel modes. Partials without a strong validator restart. Resume uses enable_resume: true in configuration (the default); there is no --enable-resume CLI flag. Known-file downloads and aiohttp whole-file transfers restart interrupted files.
Chunk state does not resume across runs. After acquiring ownership, the next run reclaims recorded abandoned chunk work and staging. Live writers and unrecognized files are preserved. Staging is never published under the final filename until verified, and obsolete-file cleanup excludes reserved state.
βοΈ Configuration Files
Use YAML or JSON for repeatable runs. Explicit CLI flags override file settings, even when their values equal the built-in defaults. Omitted flags preserve the file's settings. Mirror and listing configs require base_url, dest_path, and log_path. CLI download configs with download_files or download_url can use shortcut defaults; standalone model validation still requires those three fields. Unknown keys are rejected.
# Save as mirror.yaml
base_url: https://example.com/data/
dest_path: ./mirror/data
log_path: ./logs
cleanup_policy: safe
mode: mirror
backend: httpx
verify_content: false
# Optional selection and download settings
file_filters: [".fits", ".txt"]
exclude_dirs: ["old_versions"]
max_depth: 50
streaming_parallel: true
max_concurrent_downloads: 6
max_chunks_per_file: 8
min_chunk_size_mb: 10
enable_resume: true
# Networking and diagnostics
workers: 8
timeout: 30
request_delay: 0.05
requests_per_second: 20
http2: true
security_validation: true
metrics_json: ./logs/run.json
health_check_port: 8080
# Preview, then run
mirror-url --config mirror.yaml --cleanup preview --dry-run
mirror-url --config mirror.yaml
# Override a setting explicitly
mirror-url --config mirror.yaml --max-concurrent-downloads 4
Config keys can differ from CLI flags: base_url, file_filters, exclude_dirs, cleanup_policy, and max_chunks_per_file are the model field names. dir_suffix is a single string in a config file; the CLI accepts several suffixes. Pass logging-handler controls such as --print-logs or --quiet on the CLI.
String values support ${VAR} environment placeholders. Unset variables leave placeholders unchanged. See the full configuration reference.
π CLI Argument Reference
Expand a group or search for an option. Descriptions and default settings below come from the v3.8.0 parser. Boolean defaults describe the underlying setting before CLI overrides; configuration files can supply different values. The table includes option aliases and positional file inputs. Compatibility settings are accepted but may not affect the current pipeline. Run mirror-url --help for the installed version.
Help
| Argument | Current behavior | Default setting |
|---|---|---|
-h, --help | show this help message and exit | β |
Target Options
| Argument | Current behavior | Default setting |
|---|---|---|
[FILE ...] | Files relative to --url or absolute URLs; requires --mode download; mutually exclusive with --url-list | Not set |
--url URL | Remote root for discovery and download paths; absolute download URLs sharing one parent can infer it | Not set |
--dest-path DEST_PATH | Destination directory (positional download files default to the current directory) | Not set |
--log-path LOG_PATH | Logs/cache directory (positional download files default to system temp; keep outside destination) | Not set |
--config CONFIG | YAML/JSON config; explicit CLI flags override file values, even when equal to defaults | Not set |
--mode {mirror,download} | Mirror a listing, or download supplied files without discovery/freshness checks | mirror |
--backend {httpx,aiohttp} | Transfer backend (HTTPX default; both support whole-file and chunk transfers; aiohttp uses HTTP/1.1) | httpx |
--url-list URL_LIST | UTF-8 list of file paths relative to --url or absolute URLs, one per line; requires --mode download | Not set |
--overwrite | Permit replacing existing regular files in download mode; links remain forbidden | Disabled |
Download Modes (omit for auto-selection)
| Argument | Current behavior | Default setting |
|---|---|---|
--parallel-downloads | Mirror/download with either backend: parallel files with verified temporary chunks. HTTPX mirror whole-file fallbacks can resume; chunks do not resume across runs | Disabled |
--streaming-parallel | Mirror/download with either backend: parallel files; eligible chunks write to a staging file and publish atomically after verification | Disabled |
--sequential-downloads | Download one file at a time; metadata and size probes may still run concurrently | Disabled |
Parallel Download Options
| Argument | Current behavior | Default setting |
|---|---|---|
--max-chunks N | Maximum chunks per file (default: 8) | 8 |
--min-chunk-size MB | Minimum chunk size in MB (default: 10MB) | 10 |
--max-parallel-chunks N | Maximum total parallel chunks (default: 50) | 50 |
--max-concurrent-downloads N, --concurrency N | Maximum concurrent file downloads (default: 10) | 10 |
--auto-concurrency | Automatically tune parallel download concurrency based on throughput | Disabled |
--chunk-assembly-dir DIR | Parent for owned chunk workspaces (default: reserved destination state); staging uses the destination filesystem | Not set |
--chunk-timeout-multiplier MULT | Timeout multiplier for chunks (default: 1.5). Currently has no effect; accepted for backward compatibility. | 1.5 |
Filter Options
| Argument | Current behavior | Default setting |
|---|---|---|
--filter [PATTERN ...] | Case-insensitive filename patterns, OR'd: extensions (.fits), substrings (_fe_), or regexes ('2024.*\.fits$'). Does not match directory paths | [] |
Directory Options
| Argument | Current behavior | Default setting |
|---|---|---|
--dir-suffix [SUFFIX ...] | Directory suffixes to mirror (e.g., L1/v1 L2/v2) | [] |
--exclude-dir [DIR ...] | Exclude exact paths relative to --url, including with --dir-suffix. Use quoted '*' globs for nested matches: lasco matches the root child; '*/lasco' matches nested children. Use both to cover both | [] |
--list-dirs [N] | List directories and exit without file checks/downloads/cleanup. Respects exclusions/depth; ignores filters. Optional N selects the lexicographically last N paths across this target, excluding the root. CLI-only runs need no destination/log paths | Not set |
--list-files [N] | List matching files and exit without freshness checks/downloads/cleanup. Respects exclusions/depth/filters. Optional N selects the lexicographically last N filenames per directory, not timestamps. CLI-only runs need no destination/log paths | Not set |
Performance & Worker Options
| Argument | Current behavior | Default setting |
|---|---|---|
--workers N | Sync workers (default: 8) | 8 |
--timeout SECS | Base request timeout (default: 30s; range: 3-300); some paths use fixed limits/multiples, not a whole-run deadline | 30 |
--max-retries N | Connection-request retry budget (default: 3); chunk retries also have their own fixed budget | 3 |
--retry-delay SECS | Base retry-backoff delay (default: 2s) | 2 |
--trusted-server | Relax chunk concurrency/scaling limits; request delay stays controlled by --request-delay | Disabled |
--request-delay SECS | Minimum request spacing (default: 0.05s; range: 0-1.0). Use 0 with --requests-per-second 0 for no pacing | 0.05 |
--requests-per-second N | Request rate ceiling (default: 20; 0 removes this ceiling, subject to --request-delay) | 20 |
--bandwidth-limit MB/S | Limit download bandwidth (MB/s) | Not set |
Cache Options
| Argument | Current behavior | Default setting |
|---|---|---|
--no-cache | Bypass saved metadata and process-local parsed-listing caches; existing files are still checked for freshness | Disabled |
--refresh-cache | Ignore saved metadata and cached listings for this run | Disabled |
--cache-max-age DAYS | Cache max age (default: 7 days) | 7 |
--cache-html | Cache parsed listings in memory within this process (default: enabled); not restored on a new launch | Enabled |
--no-cache-html | Disable process-local parsed-listing caches | Enabled |
--html-cache-max-age HOURS | Process-local parsed-listing cache age (default: 24h) | 24 |
--hash-algorithm {md5,sha256,blake2b} | Hash for directory/cache signatures (default: md5); no comparison with a remote cryptographic file digest | md5 |
--no-rget-list | Disable RGET-LIST usage. Currently has no effect; accepted for backward compatibility. | Disabled |
--rget-list-max-age DAYS | RGET-LIST max age (default: 7 days). Currently has no effect; accepted for backward compatibility. | 7 |
--force-rget-list | Force RGET-LIST use even if old. Currently has no effect; accepted for backward compatibility. | Disabled |
--no-etag | Disable ETag-based freshness checks; range transfers still require strong ETags for integrity | Disabled |
--missing-files | Download absent files and skip freshness checks for existing files, except paths selected by --check-files. Unselected in-place changes will be missed; use occasional full runs when needed. | Disabled |
--check-files PATH [PATH ...] | With --missing-files, check selected existing files for updates. Exact paths relative to --url, including any --dir-suffix; use @FILE for a UTF-8 list (one path per line) | [] |
Async & Adaptive Options
| Argument | Current behavior | Default setting |
|---|---|---|
--async-metadata | Enable async metadata checks (default: enabled); normal sync uses them only for more than 80 remote files, outside dry runs | Enabled |
--no-async-metadata | Disable async metadata checks (use for throttled servers) | Enabled |
--async-workers N | Async metadata admission limit (default: 50); adaptive admission is also capped at 50 | 50 |
--adaptive-async | Enable adaptive async concurrency (default: enabled) | Enabled |
--no-adaptive-async | Use fixed --async-workers admission instead of adaptive metadata concurrency | Enabled |
--adaptive-start-concurrency N | Starting async metadata concurrency (default: 5), bounded by --async-workers and 50 | 5 |
--adaptive-error-threshold RATE | Adaptive metadata fallback error rate (range: 0-1; default: 0.05) | 0.05 |
Cleanup & Safety Options
| Argument | Current behavior | Default setting |
|---|---|---|
--cleanup {safe,preview,delete,move} | Obsolete-file policy (default: safe): safe preserves obsolete files; preview reports actions but downloads still run; move archives; delete removes. Changed files may still be replaced | safe (without --config) |
--confirm-delete | Require confirmation before deletion (delete mode only) | Disabled |
--dry-run | Scan/check without downloading or changing mirrored files; still makes requests and may create log/cache bookkeeping directories | Disabled |
--quick | Refresh an existing JSON cache's expiry timestamp only; does not verify the mirror or create a missing cache. Connection setup may still make requests | Disabled |
Security Options
| Argument | Current behavior | Default setting |
|---|---|---|
--security-validation | Enable extra URL validation and per-IP pacing (default: enabled); transport IP restrictions are independent | Enabled |
--no-security-validation | Disable extra URL validation and per-IP pacing; secure transport still rejects private/loopback targets | Enabled |
--circuit-breaker-enabled | Enable circuit breaker for failing services (default: enabled) | Enabled |
--no-circuit-breaker | Disable circuit breaker | Enabled |
Symlink Handling Options
| Argument | Current behavior | Default setting |
|---|---|---|
--handle-symlinks | Report possible duplicate directory subtrees by matching non-empty entry names (default: disabled). This heuristic can flag unrelated directories and does not detect file symlinks. Start with --symlink-mode detect --print-logs and review the paths | Disabled |
--symlink-mode {detect,follow,skip,treat-as-file} | Requires --handle-symlinks. detect reports and keeps scanning, implying --dry-run; skip omits detected duplicates (default); follow mirrors within scope/tracker limits; treat-as-file behaves like skip | skip |
--max-symlink-depth N | Maximum symlink depth (default: 10) | 10 |
--max-symlinks-per-dir N | Maximum symlinks per directory (default: 100) | 100 |
--symlink-bomb-threshold N | Symlink bomb threshold (default: 1000) | 1000 |
--circuit-breaker-downloads | Compatibility setting (default: enabled); has no effect on sync. --circuit-breaker-enabled controls download breakers | Enabled |
--no-circuit-breaker-downloads | Compatibility setting; has no effect on sync. Use --no-circuit-breaker to disable download breakers | Enabled |
Logging & Output Options
| Argument | Current behavior | Default setting |
|---|---|---|
--debug | Enable debug logging | Disabled |
--print-logs | Print logs to console | Disabled |
--log-file NAME | Custom log prefix: NAME_SUFFIX_TIMESTAMP.log, with suffixes joined by underscores (or all) and YYYYMMDD_HHMMSS. Unsafe characters are replaced and long names are shortened. Multiple suffixes share one log file | Not set |
--quiet | Quiet mode (WARNING+ only) | Disabled |
--verbose | Verbose mode (DEBUG) | Disabled |
--progress-bar, --progress | Enable tqdm progress bar | Disabled |
--stats | Compatibility setting; no effect. The normal completed-sync path emits the full metrics summary; early exits use shorter summaries | Disabled |
--metrics-json PATH | Export metrics to JSON and enable the localhost health/metrics server; both are disabled in dry runs | Not set |
Scan & Path Options
| Argument | Current behavior | Default setting |
|---|---|---|
--scan-mode {sequential,parallel,adaptive,async} | Directory discovery: parallel uses up to min(--workers, 4) listing workers; adaptive (default), sequential and async use sequential discovery. File metadata concurrency is configured separately | adaptive |
--parallel-threshold N | Parallel scan threshold (default: 10). Currently has no effect; accepted for backward compatibility. | 10 |
--max-depth N | Target-root recursion depth (root: 0; default: 50). CLI-only --list-dirs defaults to 1; --config uses its file/model depth unless explicitly overridden | 50; CLI-only --list-dirs: 1 |
--max-filename-len N | Local filename sanitization/truncation limit (default: 255); colliding sanitized paths fail before downloads | 255 |
--download-queue-size N | Compatibility queue capacity (default: 1000); sync materializes the remote file list and does not use the bounded queue | 1000 |
Advanced Performance Options
| Argument | Current behavior | Default setting |
|---|---|---|
--adaptive-batch-processing | Compatibility setting (default: enabled); does not change sync batching | Enabled |
--no-adaptive-batch-processing | Compatibility setting; does not change sync batching | Enabled |
--initial-batch-size N | Compatibility initial batch size (default: 200); does not change sync batching | 200 |
--max-batch-size N | Compatibility maximum batch size (default: 1000); does not change sync batching | 1000 |
--target-batch-time SECS | Compatibility target batch time (default: 1.0s); does not change sync batching | 1.0 |
--memory-cache-size N | Optional tracking-component threshold (default: 100000); does not bound sync's remote file list or active metadata-cache capacities | 100000 |
--use-disk-backed-sets | Configure optional disk-backed tracking; sync does not populate it or spill its remote file list to disk | Disabled |
--disk-cache-dir DIR | Cache/tracking component directory; does not spill sync's remote file list | Not set |
--fast-parsing-fallback | Allow lightweight-parser fallback after lxml fails (default: enabled); large listings/no lxml select the lightweight parser independently | Enabled |
--no-fast-parsing-fallback | Disable fallback after lxml failure; large listings/no lxml still select the lightweight parser | Enabled |
--http2 | Enable HTTP/2 (default: enabled); overrides http2: false in a config file | Enabled |
--no-http2 | Disable HTTP/2 | Enabled |
--http2-pipelining | Compatibility setting (default: enabled); the HTTP/2 client does not read this setting | Enabled |
--no-http2-pipelining | Disable HTTP/2 pipelining. Currently has no effect; accepted for backward compatibility. | Enabled |
--connection-pool-prewarm | Pre-warm connection pools (default: enabled) | Enabled |
--no-connection-pool-prewarm | Disable connection pool pre-warming | Enabled |
--fs-cache-ttl SECS | Standalone filesystem-cache TTL (default: 5.0s); sync freshness checks use direct filesystem stats | 5.0 |
--no-content-hash | Compatibility setting; has no effect on file freshness checks | Enabled |
--verify-content | Verify local files against saved SHA-256 receipts before freshness checks | Disabled |
--no-verify-content | Disable content verification (overrides a config file) | Disabled |
Other Options
| Argument | Current behavior | Default setting |
|---|---|---|
--benchmark | Run performance benchmark | Disabled |
--version | show program's version number and exit | β |
--health-check-port PORT | Localhost health/metrics port (default: 8080); enabled only with --metrics-json outside dry runs | 8080 |
Directory discovery uses up to min(--workers, 4) listing workers with --scan-mode parallel. Other scan modes use sequential discovery; async worker settings control metadata checks separately. Discovery keeps the full remote file list in memory, including with --use-disk-backed-sets.
π‘ Practical Examples
These commands use placeholder URLs. Substitute your archive's directory-listing URL and choose paths with adequate free space.
Mirror only FITS files
mirror-url --url https://example.com/data/ \
--dest-path ./fits-data --log-path ./logs \
--filter .fits --exclude-dir old_versions
Filename filters are case-insensitive. Extensions match suffixes, plain text matches substrings, and patterns with regex metacharacters use regular expressions. Multiple filters are OR'd.
Mirror several subdirectories
mirror-url --url https://example.com/product/ \
--dest-path ./mirror --log-path ./logs --dir-suffix L1/v2 L2/v1
Directory exclusions match exact paths relative to --url. To exclude a name at both root level and nested levels, use --exclude-dir old '*/old'.
Parallel transfers with metrics
mirror-url --config mirror.yaml --streaming-parallel \
--max-concurrent-downloads 6 --max-chunks 8 --metrics-json ./logs/run.json
Conservative settings for a throttled server
mirror-url --config mirror.yaml --sequential-downloads \
--no-async-metadata --workers 2 --request-delay 0.2
List a channel without downloading
mirror-url --url https://example.com/mission/ \
--list-files 5 --filter _fe_
--list-files N ranks per directory; --list-dirs N ranks directory paths across the selected tree. Query channels separately when differing filename prefixes would dominate the ranking.π Performance Tuning
Start with automatic selection in mirror mode or whole-file GETs in download mode, then adjust limits after observing your server, disk, and run metrics. Throughput depends on the archive and workload.
- Eligible large files: try
--streaming-parallelwith a modest file limit. Range support and a strong ETag are still required for chunking. - Many small files: tune
--max-concurrent-downloads; chunk limits usually do not help files below the chunk-size threshold. - Throttled servers: reduce file and metadata worker counts, increase
--request-delay, and consider--no-async-metadata. - Bandwidth sharing: set
--bandwidth-limitin MB/s. - Mostly static archives:
--missing-filesskips freshness checks except for paths selected by--check-files. Run occasional full checks for changes elsewhere. - Large directory trees: limit scope with
--dir-suffix,--exclude-dir, or--max-depth.--scan-mode paralleluses up tomin(--workers, 4)directory-listing workers. Other scan modes use sequential discovery. Discovery keeps the remote file list in memory. - Request pacing: the default budget is 20 requests/second with 50 ms spacing. Set both
--requests-per-second 0and--request-delay 0to remove pacing.--trusted-serverrelaxes chunk limits; request spacing still follows your explicit settings.
Cache behavior
Keep --log-path stable to reuse the JSON metadata cache. --no-cache bypasses caches but still checks existing files for freshness; it does not force every file to download. Parsed HTML caches are process-local and are not restored on a new CLI launch.
--quick refreshes an existing cache timestamp without scanning or verifying files. It does not establish that a mirror is current.
Refresh selected existing files
mirror-url --config mirror.yaml --missing-files \
--check-files L1/v2/a.fits L1/v2/nested/b.fits
# Or read root-relative paths from a UTF-8 list
mirror-url --config mirror.yaml --missing-files --check-files @checks.txt
Selected existing files receive normal freshness checks; all missing files in scope are still downloaded. Paths remain relative to --url, including any --dir-suffix. Without --missing-files, the run checks all selected existing files normally.
Verify local content against saved receipts
mirror-url --config mirror.yaml --verify-content
SHA-256 is opt-in. Each downloaded staging file is hashed before publication, and existing local files are hashed before trusting freshness metadata. Missing, expired, malformed, or mismatched receipts require a new download, so the first verified run of an existing mirror can download files again. Keep the log/cache path stable. Hashing adds disk I/O; it does not check a server-provided digest. --verify-content cannot be combined with --missing-files; --no-verify-content overrides configuration.
π Security & Scope
- Transport IP restrictions: private, loopback, link-local, and direct-IP targets are rejected. Resolved addresses are validated before connection.
- URL scope: discovery and redirects are checked against the configured host and path.
- Filesystem protection: traversal rejection, filename sanitization, reserved-name handling, and checks for ambiguous local-path mappings. Distinct case-only names are preserved on confirmed case-sensitive destinations and rejected before downloads on case-insensitive destinations.
- Cooperative ownership: overlapping destination trees and shared log/cache/state paths reject competing runs. OS locks are released on process termination; external writers and network filesystems require separate coordination.
- Transfer consistency: eligible byte ranges are verified before atomic publication; no remote cryptographic digest comparison is performed.
- Resilience: request pacing, retry backoff, and circuit breakers handle failing or overloaded services.
--no-security-validation disables the extra URL-validation layer and per-IP pacing. It does not disable the secure transport's IP restrictions. There is no production option for mirroring localhost or private-network servers.Optional duplicate-subtree detection
--handle-symlinks compares non-empty directory entry names to report possible duplicate subtrees. This heuristic can flag unrelated directories and cannot identify individual file symlinks. Start with detection and review the reported paths:
mirror-url --config mirror.yaml --handle-symlinks --symlink-mode detect --print-logs
detect implies --dry-run. skip omits detected duplicates; follow remains subject to scope and tracker limits; treat-as-file behaves like skip. See the symlink-handling guide before enabling automatic skipping.
π Monitoring & Exit Codes
- The normal completed-sync path logs a metrics summary at INFO level.
--statsis an inactive compatibility setting; early exits produce shorter summaries. --metrics-json FILEexports JSON metrics and enables a localhost health/metrics server for non-dry-run instances.--health-check-portselects the port (default 8080); it does not enable the server on its own.--progress-bar(alias--progress) works in a standard installation. Both modes show per-file byte/stage progress; chunked transfers include active and completed chunks.--print-logsenables console logs.
Shared transfer reporting
Mirror and download modes use the same startup header, with mode, start time, sanitized command, remote root, destination, log file, backend, and SHA-256 setting. Transfer logs show the actual file/worker plan, explain whole-file fallbacks, and give every published file one Downloaded: line. A completed batch reports File download batch completed: N downloaded, M failed, including sequential transfers and partial failures.
The shared SUMMARY reports this run's published bytes, successful and failed files, timings, throughput, selected and validated chunks, files finalized from chunks, observed peak chunk workers, and SHA-256 setting/hash count. Completion output does not recursively size the destination. With --verify-content, each downloaded file's completion line includes its computed SHA-256. Local receipts do not establish agreement with a remote checksum.
Transfer-stage timings show range metadata probes, data transfer/writes, temporary chunk assembly, payload disk flushing and opt-in SHA-256 hashing. Each is labelled as a worker sum: concurrent work overlaps, so these sums can exceed elapsed download time. Both modes and backends use the same fields; elapsed download time remains the throughput denominator.
In download mode, --print-logs without --quiet prints readable logs and the summary to stderr, leaving stdout empty. Otherwise the CLI prints exactly one JSON result to stdout for scripts. Progress bars work independently of console logs; --quiet suppresses bars and INFO-level console output.
# Capture one JSON result for scripting
mirror-url --mode download https://example.com/data/a.fits >result.json
# During a run with --metrics-json enabled
curl http://localhost:8080/health
curl http://localhost:8080/metrics
Both endpoints return JSON. Metrics export and the health server are disabled during dry runs; the server is cleaned up when the instance closes.
| Exit code | Meaning |
|---|---|
0 | No recorded suffix failure, or graceful shutdown after a signal |
1 | Recorded sync/download/scan/cleanup failure, failed built-in benchmark, or forced shutdown after the cleanup timeout |
2 | CLI parsing or config-file validation error reported by the argument parser |
A graceful SIGINT/SIGTERM shutdown currently exits with code 0 even when synchronization was interrupted. Check the completion summary for interrupted scheduled jobs. The Python API returns a boolean from sync().
π Python API
from pathlib import Path
from mirror_url import MirrorConfig, MirrorURL
config = MirrorConfig(
base_url="https://example.com/data/",
dest_path=Path("./mirror/data"),
log_path=Path("./logs"),
file_filters=[".fits"],
parallel_downloads=True,
)
with MirrorURL(config) as mirror:
ok = mirror.sync()
print("sync succeeded" if ok else "sync failed")
Use the context manager to clean up connection pools, workers, async components, and any health server. sync() blocks and returns True or False. Use MirrorConfig.from_yaml(Path("mirror.yaml")) to load a file.
For model validation, use MirrorConfig.model_validate(data). To obtain informational warnings for an existing config, use MirrorConfig.validation_warnings(config). Code that used MirrorConfig.validate(config) for warning strings must use the new warning helper.
π§ Troubleshooting
| Symptom | What to check |
|---|---|
| Private-IP or localhost target rejected | Secure transport blocks these targets. --no-security-validation does not bypass transport IP restrictions. |
| 403 / 429 / overloaded server | Check access policies; reduce worker/file counts, increase request delay, and disable async metadata when appropriate. |
| Nothing downloaded | Mirror mode requires a parseable HTML directory listing; check filters, exclusions, and depth. For known files, choose --mode download. Use --debug --print-logs. |
| Parallel mode falls back to whole-file transfers | Read the logged reason. Chunking needs enough bytes for at least two chunks, compatible configured limits, Range support, and a strong ETag. |
| No progress bar or backend dependency | Upgrade with python -m pip install --upgrade mirror-url and run python -m pip check. Both backends and tqdm are core dependencies. Pass --progress-bar and avoid --quiet; optional psutil monitoring uses the monitor extra. |
| No health server | Set --metrics-json FILE during a non-dry-run instance; the port option alone does not enable it. |
| Changed files skipped | Remove --missing-files for full freshness checks or use --check-files for selected paths. Opt-in --verify-content detects changes against saved local receipts; remote freshness still depends on HTTP metadata. |
| Reserved state or filename collision | Review the reported paths. .mirror-url-state is reserved. Unsafe, rewritten, or Unicode-aliased collisions fail preflight; case-only names require a confirmed case-sensitive destination. |
| Destination already owned | Stop the overlapping MirrorURL run or choose independent destination and state paths. Locks release when their process ends; do not delete lock files while a run is active. |
| Existing known-file destination rejected | Use the matching ownership receipt or explicit --overwrite for intended replacements. Links remain forbidden. Download mode fetches all selected files even when receipts match. |
Inspect a run
mirror-url --config mirror.yaml --debug --print-logs --dry-run
Uninstall
python -m pip uninstall mirror-url
# For a pipx installation:
pipx uninstall mirror-url
Uninstalling leaves mirrored files, logs, and cache data in place. For more help, read the User Guide or report a reproducible problem on GitHub Issues.
π Choosing a Mirroring Tool
Choose according to the source protocol and the workflow you need. The links below point to each project's own documentation.
| Tool | Typical use |
|---|---|
| MirrorURL | Public HTTP(S) directory mirroring or known-file downloads, selected-tree cleanup, filename filtering, verified chunks with either backend, optional local SHA-256 receipts, JSON metrics, and a Python API |
| rsync | Local or remote file copies with its delta-transfer algorithm; remote operation uses a shell transport or an rsync daemon |
| lftp | An interactive transfer client with mirroring and FTP, SFTP, and HTTP support |
| rclone | A multi-backend workflow; its read-only HTTP backend parses directory listings and can sync them to local storage |
For MirrorURL, the current runtime discovers the remote file list in memory and performs no remote cryptographic digest comparison. Check the archive's listing format, metadata, and Range support when choosing a tool.