offline-docker-cli 2.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. offline_docker/__init__.py +3 -0
  2. offline_docker/__main__.py +4 -0
  3. offline_docker/actions.py +173 -0
  4. offline_docker/build.py +245 -0
  5. offline_docker/cli.py +486 -0
  6. offline_docker/config.py +692 -0
  7. offline_docker/configure.py +282 -0
  8. offline_docker/context.py +111 -0
  9. offline_docker/defaults.py +48 -0
  10. offline_docker/discover.py +139 -0
  11. offline_docker/docker_cmd.py +136 -0
  12. offline_docker/download_debs.py +142 -0
  13. offline_docker/download_images.py +26 -0
  14. offline_docker/download_npm.py +106 -0
  15. offline_docker/download_trivy.py +90 -0
  16. offline_docker/download_wheels.py +363 -0
  17. offline_docker/download_yarn.py +208 -0
  18. offline_docker/ensure_base.py +61 -0
  19. offline_docker/hooks.py +84 -0
  20. offline_docker/init_cmd.py +54 -0
  21. offline_docker/load_images.py +34 -0
  22. offline_docker/oc_discovery.py +206 -0
  23. offline_docker/resolve.py +306 -0
  24. offline_docker/scan.py +496 -0
  25. offline_docker/sources/__init__.py +18 -0
  26. offline_docker/sources/base.py +18 -0
  27. offline_docker/sources/filesystem.py +17 -0
  28. offline_docker/sources/nexus.py +61 -0
  29. offline_docker/sources/nexus_client.py +67 -0
  30. offline_docker/sources/proget.py +49 -0
  31. offline_docker/sources/proget_client.py +48 -0
  32. offline_docker/sources/upstream.py +36 -0
  33. offline_docker/status.py +247 -0
  34. offline_docker/sync_cache.py +125 -0
  35. offline_docker/trivy_scan.py +86 -0
  36. offline_docker/tui/__init__.py +19 -0
  37. offline_docker/tui/app.py +56 -0
  38. offline_docker/tui/runner.py +87 -0
  39. offline_docker/tui/screens.py +333 -0
  40. offline_docker/user_config.py +89 -0
  41. offline_docker/utils.py +73 -0
  42. offline_docker/validate.py +53 -0
  43. offline_docker/wheel_prune.py +141 -0
  44. offline_docker_cli-2.2.2.dist-info/METADATA +417 -0
  45. offline_docker_cli-2.2.2.dist-info/RECORD +49 -0
  46. offline_docker_cli-2.2.2.dist-info/WHEEL +5 -0
  47. offline_docker_cli-2.2.2.dist-info/entry_points.txt +2 -0
  48. offline_docker_cli-2.2.2.dist-info/licenses/LICENSE +21 -0
  49. offline_docker_cli-2.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
1
+ """Remove stale wheels that conflict with exact pins in a requirements file.
2
+
3
+ When offline-cache/wheels still has protobuf-6.32.0.whl but the lock asks for
4
+ protobuf==7.36.2, pip --find-links may report only the local version if the
5
+ index is unreachable. Deleting mismatched package wheels forces a fresh fetch.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ import sys
12
+ from pathlib import Path
13
+
14
+
15
+ _REQ_PIN = re.compile(
16
+ r"^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*==\s*([^\\\s;#]+)",
17
+ )
18
+
19
+
20
+ def normalize_name(name: str) -> str:
21
+ return re.sub(r"[-_.]+", "-", name).lower()
22
+
23
+
24
+ def parse_exact_pins(requirements_text: str) -> dict[str, str]:
25
+ """Return {normalized_name: version} for name==version lines only."""
26
+ pins: dict[str, str] = {}
27
+ for line in requirements_text.splitlines():
28
+ raw = line.strip()
29
+ if not raw or raw.startswith("#") or raw.startswith("-"):
30
+ continue
31
+ # Strip env markers: pkg==1.0 ; python_version >= "3.10"
32
+ raw = raw.split(";", 1)[0].strip()
33
+ m = _REQ_PIN.match(raw)
34
+ if not m:
35
+ continue
36
+ pins[normalize_name(m.group(1))] = m.group(2).strip()
37
+ return pins
38
+
39
+
40
+ def parse_wheel_name_version(filename: str) -> tuple[str, str] | None:
41
+ """Best-effort PEP 427 parse: name and version from a .whl filename."""
42
+ if not filename.endswith(".whl"):
43
+ return None
44
+ stem = filename[:-4]
45
+ parts = stem.split("-")
46
+ ver_idx = None
47
+ for i, part in enumerate(parts):
48
+ if part and part[0].isdigit():
49
+ ver_idx = i
50
+ break
51
+ if ver_idx is None or ver_idx == 0:
52
+ return None
53
+ name = normalize_name("-".join(parts[:ver_idx]))
54
+ version = parts[ver_idx]
55
+ return name, version
56
+
57
+
58
+ def parse_sdist_name_version(filename: str) -> tuple[str, str] | None:
59
+ for ext in (".tar.gz", ".zip"):
60
+ if filename.endswith(ext):
61
+ stem = filename[: -len(ext)]
62
+ break
63
+ else:
64
+ return None
65
+ # name-version.tar.gz — version starts at last '-' segment that looks like ver
66
+ parts = stem.rsplit("-", 1)
67
+ if len(parts) != 2 or not parts[1] or not parts[1][0].isdigit():
68
+ return None
69
+ return normalize_name(parts[0]), parts[1]
70
+
71
+
72
+ def artifact_name_version(filename: str) -> tuple[str, str] | None:
73
+ if filename.endswith(".whl"):
74
+ return parse_wheel_name_version(filename)
75
+ return parse_sdist_name_version(filename)
76
+
77
+
78
+ def prune_stale_wheels(requirements_path: Path, wheels_dir: Path) -> list[str]:
79
+ """Delete wheels/sdists for pinned packages whose version is not the pin.
80
+
81
+ Returns list of deleted file names.
82
+ """
83
+ if not requirements_path.is_file() or not wheels_dir.is_dir():
84
+ return []
85
+ pins = parse_exact_pins(requirements_path.read_text(encoding="utf-8", errors="replace"))
86
+ if not pins:
87
+ return []
88
+
89
+ by_pkg: dict[str, list[tuple[Path, str]]] = {}
90
+ for path in wheels_dir.iterdir():
91
+ if not path.is_file():
92
+ continue
93
+ parsed = artifact_name_version(path.name)
94
+ if parsed is None:
95
+ continue
96
+ name, version = parsed
97
+ if name not in pins:
98
+ continue
99
+ by_pkg.setdefault(name, []).append((path, version))
100
+
101
+ deleted: list[str] = []
102
+ for name, wanted in pins.items():
103
+ artifacts = by_pkg.get(name) or []
104
+ if not artifacts:
105
+ continue
106
+ if any(ver == wanted for _, ver in artifacts):
107
+ # Keep matching version; drop other versions of the same package
108
+ for path, ver in artifacts:
109
+ if ver != wanted:
110
+ path.unlink(missing_ok=True)
111
+ deleted.append(path.name)
112
+ continue
113
+ # No matching version — remove all stale copies so pip must fetch
114
+ for path, _ver in artifacts:
115
+ path.unlink(missing_ok=True)
116
+ deleted.append(path.name)
117
+ return sorted(deleted)
118
+
119
+
120
+ def main(argv: list[str] | None = None) -> int:
121
+ args = list(sys.argv[1:] if argv is None else argv)
122
+ if len(args) != 2:
123
+ print(
124
+ "usage: prune_stale_wheels.py <requirements.txt> <wheels_dir>",
125
+ file=sys.stderr,
126
+ )
127
+ return 2
128
+ req = Path(args[0])
129
+ wheels = Path(args[1])
130
+ deleted = prune_stale_wheels(req, wheels)
131
+ if deleted:
132
+ print(f"prune-stale: removed {len(deleted)} artifact(s):")
133
+ for name in deleted:
134
+ print(f" - {name}")
135
+ else:
136
+ print("prune-stale: nothing to remove")
137
+ return 0
138
+
139
+
140
+ if __name__ == "__main__":
141
+ raise SystemExit(main())
@@ -0,0 +1,417 @@
1
+ Metadata-Version: 2.4
2
+ Name: offline-docker-cli
3
+ Version: 2.2.2
4
+ Summary: Universal Docker offline cache, build and Trivy scan CLI
5
+ Author-email: "D.Shapovalov" <d.shapovalov@vitebsk.energo.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/DzmitrySha/offline-docker-cli
8
+ Project-URL: Repository, https://github.com/DzmitrySha/offline-docker-cli
9
+ Project-URL: Issues, https://github.com/DzmitrySha/offline-docker-cli/issues
10
+ Project-URL: Changelog, https://github.com/DzmitrySha/offline-docker-cli/blob/main/CHANGELOG.md
11
+ Keywords: docker,offline,trivy,cli
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Topic :: System :: Software Distribution
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: yaml
24
+ Requires-Dist: pyyaml>=6.0; extra == "yaml"
25
+ Provides-Extra: tui
26
+ Requires-Dist: textual>=0.80; extra == "tui"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pyyaml>=6.0; extra == "dev"
29
+ Requires-Dist: textual>=0.80; extra == "dev"
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ <div align="center">
34
+
35
+ <h1>offdock</h1>
36
+
37
+ Универсальная Python CLI для portable offline-cache, офлайн-сборки Docker-образов и Trivy-сканирования Docker-проектов.<br>
38
+ Весь конфиг живёт в целевом репозитории (`.offline-docker.json`); CLI не содержит настроек конкретных проектов.
39
+
40
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2dd4bf?style=flat-square" alt="License: MIT"></a>
41
+ <img src="https://img.shields.io/badge/python-3.11%2B-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python 3.11+">
42
+ <img src="https://img.shields.io/badge/Docker-ready-2496ED?style=flat-square&logo=docker&logoColor=white" alt="Docker">
43
+ <img src="https://img.shields.io/badge/Trivy-scan-1904DA?style=flat-square" alt="Trivy">
44
+ <img src="https://img.shields.io/badge/uv-locked-DE5FE1?style=flat-square" alt="uv">
45
+ <img src="https://img.shields.io/badge/i18n-RU%20%2F%20EN-22c55e?style=flat-square" alt="RU / EN">
46
+
47
+ <sub><a href=".github/README.md">English</a> · Русская версия · <a href="docs/ru/CONTRIBUTING.md">Участие</a> · <a href="docs/ru/SECURITY.md">Безопасность</a> · <a href="docs/ru/CHANGELOG.md">Changelog</a></sub>
48
+
49
+ </div>
50
+
51
+ ---
52
+
53
+ ## Содержание
54
+
55
+ - [Как это работает](#как-это-работает)
56
+ - [Возможности](#возможности)
57
+ - [Установка](#установка)
58
+ - [Быстрый старт](#быстрый-старт)
59
+ - [Команды](#команды)
60
+ - [Общие флаги](#общие-флаги)
61
+ - [Примеры команд](#примеры-команд)
62
+ - [Конфигурация целевого проекта](#конфигурация-целевого-проекта)
63
+ - [Источники пакетов (local / ProGet / Nexus)](#источники-пакетов-local--proget--nexus)
64
+ - [Env-переменные](#env-переменные)
65
+ - [Требования](#требования)
66
+ - [Разработка и тесты](#разработка-и-тесты)
67
+ - [Структура репозитория](#структура-репозитория)
68
+ - [Ограничения](#ограничения)
69
+ - [Участие](#участие)
70
+ - [Лицензия](#лицензия)
71
+ - [Changelog](#changelog)
72
+
73
+ ## Как это работает
74
+
75
+ ```mermaid
76
+ flowchart LR
77
+ proj[целевой репозиторий<br/>.offline-docker.json] -->|discover| plan[cache plan]
78
+ net[(PyPI / npm / apt / registry)] -->|cache| bundle[(offline-cache/)]
79
+ plan --> bundle
80
+ bundle -->|перенос вручную| offline[офлайн-машина]
81
+ offline -->|cache load| images[docker images]
82
+ offline -->|build| images
83
+ images -->|up / down| stack[Docker Compose]
84
+ bundle -->|scan| trivy[Trivy offline DB]
85
+ ```
86
+
87
+ Бандл-папка `offline-cache/` переносится на офлайн-машину вручную — постоянного зеркала/registry-сервиса нет. Compose-файлы склеиваются из `compose_build` / `compose_up_offline` конфига проекта.
88
+
89
+ ## Возможности
90
+
91
+ | Возможность | Что даёт |
92
+ |-------------|----------|
93
+ | Project-scoped конфиг | Всё в целевом репозитории: `.offline-docker.json` (или `.yaml`); CLI не хранит проектные настройки |
94
+ | Portable offline-cache | debs, wheels, npm, yarn, docker images, Trivy DB в одной переносимой папке |
95
+ | Источники local → ProGet → Nexus | `resolve.order`, user-level `sources.json`, project override |
96
+ | `cache wheels` | Python берётся из базового образа + ABI-проверка версии |
97
+ | `cache debs` | Кэш привязан к дистрибутиву гостевого образа (`debs/.base-os`) |
98
+ | `hooks.after_cache` + `validation.critical_vendor` | Проектные хуки после кэша и проверка критичных vendor-артефактов |
99
+ | `build` | Офлайн-сборка с merge-файлами и списком сервисов |
100
+ | `up` / `down` | Offline / lab / online режимы Compose |
101
+ | Trivy-скан | Офлайн-БД и лог в `{log_dir}` |
102
+ | Опциональный Textual TUI | Статус кэша, меню команд и live-лог поверх тех же действий CLI |
103
+
104
+ ## Установка
105
+
106
+ ### Вариант A — без pip (рекомендуется в corp-сети)
107
+
108
+ ```powershell
109
+ # из корня репозитория offline-docker-cli
110
+ .\offdock.cmd discover -p ..\my-project
111
+ ```
112
+
113
+ ### Вариант B — pip (если PyPI доступен)
114
+
115
+ ```powershell
116
+ cd offline-docker-cli
117
+ pip install -e .
118
+ pip install -e ".[yaml]" # опционально: .offline-docker.yaml
119
+ pip install -e ".[tui]" # опционально: Textual TUI
120
+ offdock configure # ProGet/Nexus URL (Enter = только offline-cache)
121
+ ```
122
+
123
+ ### Опциональный TUI
124
+
125
+ Терминальный UI поверх тех же команд (статус кэша, меню, live-лог). CLI остаётся основным интерфейсом.
126
+
127
+ ```powershell
128
+ # Установка (для использования — без -e, чтобы пакет жил в site-packages)
129
+ pip install .\[tui]
130
+
131
+ # Запуск
132
+ offdock tui -p .
133
+ ```
134
+
135
+ **Важно:** `pip install -e ".[tui]"` (editable) привязывает пакет к папке исходников — если папку переместить/удалить, CLI сломается. Для использования рекомендуется **обычная установка** (без `-e`).
136
+ Для разработки самого CLI — тогда `-e`.
137
+
138
+ ## Быстрый старт
139
+
140
+ Сгенерировать черновик конфига (рекурсивный scan) или скопировать шаблон из `examples/`:
141
+
142
+ ```powershell
143
+ cd ..\my-project
144
+ offdock init -p . # → .offline-docker.json (или .generated.json)
145
+ offdock discover -p .
146
+ offdock cache -p .
147
+ offdock cache check -p . # проверка полноты offline-cache
148
+ offdock build -p .
149
+ offdock up -p .
150
+ ```
151
+
152
+ ### `init` — черновик конфига
153
+
154
+ Рекурсивно ищет compose, Dockerfile, python/frontend lock-файлы и пишет JSON по схеме example.
155
+
156
+ | Ситуация | Файл |
157
+ |----------|------|
158
+ | Нет `.offline-docker.json` / `.yaml` | создаётся `.offline-docker.json` |
159
+ | Канон уже есть | пишется `.offline-docker.generated.json` (канон **не** затирается) |
160
+
161
+ В JSON есть секция `_init.gaps` и в stdout — блок `Missing / to fix:` (нет `Dockerfile.*.offline`, offline-compose и т.д.).
162
+ **Не** генерируются сами offline-Dockerfile / compose — только пути и список доработок.
163
+
164
+ ```powershell
165
+ offdock init -p . --dry-run # только отчёт
166
+ ```
167
+
168
+ ## Команды
169
+
170
+ | Команда | Описание |
171
+ |---------|----------|
172
+ | `configure` | User-level ProGet/Nexus (или local-only) |
173
+ | `tui` | Интерактивный Textual TUI (нужен `pip install …[tui]`) |
174
+ | `init` | Рекурсивный scan → черновик `.offline-docker.json` (+ gaps) |
175
+ | `discover` | Compose, Python/npm lock, Dockerfile, APT-пакеты, sources, Missing |
176
+ | `cache` | Полный download offline-cache |
177
+ | `cache wheels\|debs\|npm\|yarn\|images\|trivy` | Частичный download |
178
+ | `cache check` | Проверка полноты кэша |
179
+ | `cache load` | `docker load` из `offline-cache/docker/` |
180
+ | `build [base backend frontend]` | Офлайн-сборка |
181
+ | `up [--lab\|--online] […]` | Compose up (offline / lab / online) |
182
+ | `down [--lab\|--online] [services]` | Compose down того же стека |
183
+ | `scan [trivy] <backend\|frontend> [image]` | Скан образа (default: trivy) |
184
+
185
+ ## Общие флаги
186
+
187
+ - `--project PATH` / `-p` — корень целевого проекта (default: текущий каталог)
188
+ - `--config PATH` — явный путь к конфигу (default: `.offline-docker.json` в корне проекта)
189
+
190
+ Справка и примеры — в `--help` (общий и по команде):
191
+
192
+ ```powershell
193
+ offdock --help
194
+ offdock scan --help
195
+ offdock up --help
196
+ ```
197
+
198
+ ## Примеры команд
199
+
200
+ Пути относительны: команды запускаются из корня `offline-docker-cli`, целевой проект — соседний каталог `my-project`.
201
+
202
+ ### Подготовка и сборка
203
+
204
+ ```powershell
205
+ # Полный цикл подготовки кэша (на машине с интернетом)
206
+ offdock cache -p ..\my-project
207
+
208
+ # На офлайн-машине: загрузить образы и собрать
209
+ offdock cache load -p .
210
+ offdock build -p .
211
+ offdock cache check -p .
212
+
213
+ # Собрать только backend (без base/frontend)
214
+ offdock build -p . backend
215
+ ```
216
+
217
+ ### `up` / `down` и режимы `--lab` / `--online`
218
+
219
+ Обычный offline-стек (merge-файлы из `compose_up_offline` в конфиге):
220
+
221
+ ```powershell
222
+ offdock up -p .
223
+ offdock down -p .
224
+ ```
225
+
226
+ Home lab — дополнительно подключается `docker-compose.lab.yml`:
227
+
228
+ ```powershell
229
+ offdock up -p . --lab
230
+ offdock down -p . --lab
231
+ ```
232
+
233
+ Online — только `compose_online`:
234
+
235
+ ```powershell
236
+ offdock up -p . --online
237
+ offdock down -p . --online
238
+ ```
239
+
240
+ Что нужно в **целевом проекте** для `--lab`:
241
+
242
+ 1. Файл `docker-compose.lab.yml` в корне репозитория (override для домашней среды).
243
+ 2. В `.offline-docker.json` — секция `compose_up_offline_lab` со списком compose-файлов (см. `examples/offline-docker.json.example`). Если секция не задана, CLI сам добавит `docker-compose.lab.yml` к списку из `compose_up_offline`.
244
+
245
+ Подробнее о полях конфига: [`examples/README.md`](examples/README.md).
246
+
247
+ Дополнительные флаги:
248
+
249
+ ```powershell
250
+ # Не пересобирать образы (уже собраны)
251
+ offdock up -p . --no-build
252
+
253
+ # Поднять только backend, без пересоздания зависимостей
254
+ offdock up -p . --no-deps backend
255
+
256
+ # Lab + только frontend
257
+ offdock up -p . --lab frontend
258
+
259
+ # Остановить отдельные сервисы
260
+ offdock down -p . backend frontend
261
+ ```
262
+
263
+ ### `scan`
264
+
265
+ **Обязательный** target: `backend` или `frontend`.
266
+ Опционально — сканер (`trivy`, default) и тег образа.
267
+
268
+ ```powershell
269
+ # Скан backend-образа из конфига (images.backend)
270
+ offdock scan backend
271
+
272
+ # Явно указать сканер
273
+ offdock scan trivy frontend
274
+
275
+ # Явный тег образа
276
+ offdock scan trivy backend myapp-backend:cuda12
277
+ ```
278
+
279
+ Перед сканом:
280
+
281
+ 1. Образ должен существовать локально (`docker images`) — обычно после `build`.
282
+ 2. БД Trivy в `offline-cache/trivy/` — `cache trivy` или `cache` (на машине с интернетом); на офлайн-машине CLI попытается использовать кэш и предупредит, если БД устарела.
283
+ 3. Образ сканера `aquasec/trivy:latest` — в `docker_images` конфига, загружается через `cache load`.
284
+
285
+ Параметры сканирования (`severity`, `scanners`, `db_max_age_days`) — в секции `trivy` конфига:
286
+
287
+ ```json
288
+ "trivy": {
289
+ "scanner_image": "aquasec/trivy:latest",
290
+ "db_max_age_days": 7,
291
+ "severity": "HIGH,CRITICAL",
292
+ "scanners": "vuln"
293
+ }
294
+ ```
295
+
296
+ Лог пишется в `{log_dir}/trivy-scan-{backend|frontend}.log` (default: `storage/logs/`).
297
+
298
+ ## Конфигурация целевого проекта
299
+
300
+ 1. **`.offline-docker.json`** (или `.offline-docker.yaml`) в корне репозитория — **канон**
301
+ 2. **Auto-discovery** — fallback, если конфига нет (для `discover`; для build рекомендуется конфиг)
302
+
303
+ Пример: `examples/offline-docker.json.example` — скопируйте в корень проекта как `.offline-docker.json`.
304
+
305
+ ## Источники пакетов (local / ProGet / Nexus)
306
+
307
+ По умолчанию CLI использует **только локальный** `offline-cache/`.
308
+
309
+ После установки настройте корпоративные реестры (один раз на машину):
310
+
311
+ ```powershell
312
+ offdock configure
313
+ # заполнение кэша с интернета (local + PyPI/npmjs):
314
+ offdock configure --with-upstream
315
+ # только offline-cache (корп/офлайн VM):
316
+ offdock configure --local-only
317
+ offdock configure --proget-url https://proget.example.com --nexus-url http://nexus.example.com:8081
318
+ ```
319
+
320
+ Конфиг пишется в user-level файл (не в example проекта):
321
+
322
+ - Linux/macOS: `~/.config/offline-docker/sources.json`
323
+ - Windows: `%APPDATA%\offline-docker\sources.json`
324
+ - override каталога: `OFFLINE_DOCKER_CONFIG_DIR`
325
+
326
+ Приоритет: **project `.offline-docker.json` → секция `cache`** перекрывает user-config; иначе user; иначе local-only.
327
+
328
+ | Режим | Когда |
329
+ |-------|--------|
330
+ | `--with-upstream` | Машина с сетью: `cache` / `cache wheels` тянет с PyPI/npmjs в `offline-cache/` |
331
+ | `--local-only` | Офлайн/корп VM: сборка только из уже скачанного кэша |
332
+ | ProGet/Nexus | Корп. зеркала; порядок в `resolve.order` |
333
+
334
+ Порядок поиска задаётся `resolve.order` (например `local,upstream` или `local,proget,nexus`). `cache wheels` / `cache npm` ходят по цепочке index URL и дописывают артефакты в локальную папку (`fill_local`).
335
+
336
+ Если в `sources.json` стоит **local-only**, `cache wheels` всё равно временно добавляет PyPI/PyTorch (с WARN) и перед скачиванием удаляет **устаревшие** wheels, чья версия не совпадает с pin в lock/requirements (типичный случай `protobuf==7.x` при старом `6.x` в кэше).
337
+
338
+ Auth (опционально, через env, не в JSON): `PROGET_API_KEY`, `NEXUS_AUTH` (`user:pass` или Bearer).
339
+
340
+ Для Nexus по HTTP pip автоматически получает `--trusted-host`; npm — `strict-ssl false`.
341
+
342
+ Env: `OFFLINE_RESOLVE_ORDER=local,proget,nexus` — временный override порядка.
343
+
344
+ Шаблон проекта (`examples/offline-docker.json.example`) **нейтральный** — без корпоративных URL.
345
+
346
+ ## Env-переменные
347
+
348
+ | Переменная | Назначение |
349
+ |------------|------------|
350
+ | `OFFLINE_CACHE` | Путь к offline-cache (override) |
351
+ | `{APP_NAME}_OFFLINE_CACHE` | Alias из `app_name` в конфиге |
352
+ | `OFFLINE_DOCKER_CONFIG_DIR` | Каталог user-config (`sources.json`) |
353
+ | `OFFLINE_RESOLVE_ORDER` | Порядок sources через запятую |
354
+ | `PROGET_API_KEY` / `NEXUS_AUTH` | Auth для remote |
355
+ | `PYTORCH_INDEX` | Index URL для torch wheels |
356
+ | `PYPI_INDEX` | Force primary PyPI index |
357
+ | `DOCKER_BUILDKIT` | `1` для build |
358
+
359
+ ## Требования
360
+
361
+ - Python 3.11+
362
+ - Docker (CLI на PATH)
363
+
364
+ ## Разработка и тесты
365
+
366
+ ```bash
367
+ uv sync --extra dev # .venv с pyyaml, textual, pytest
368
+ uv run pytest -q # весь набор tests/
369
+ uv run offdock --version
370
+ uv run offdock --help
371
+ ```
372
+
373
+ - После правок зависимостей — `uv lock` и коммит `uv.lock`.
374
+ - Версия синхронизируется в `pyproject.toml` и `src/offline_docker/__init__.py`.
375
+ - Проверка на реальном проекте: `offdock discover -p ..\my-project`.
376
+ - Запуск без установки — `offdock.cmd` (`PYTHONPATH=src`, `python -m offline_docker`).
377
+
378
+ ## Структура репозитория
379
+
380
+ ```
381
+ offline-docker-cli/
382
+ ├── .github/ # EN README, шаблоны issue/PR, GitHub Actions
383
+ ├── docs/ru/ # RU-версии CONTRIBUTING / SECURITY / CHANGELOG
384
+ ├── examples/ # шаблоны .offline-docker.json
385
+ ├── src/offline_docker/ # CLI, cache, build, scan, TUI
386
+ ├── tests/ # pytest
387
+ ├── .gitattributes
388
+ ├── .gitlab-ci.yml # GitLab CI
389
+ ├── AGENTS.md # правила для агентов
390
+ ├── CONTRIBUTING.md # EN
391
+ ├── SECURITY.md # EN
392
+ ├── CHANGELOG.md # EN
393
+ ├── LICENSE
394
+ ├── offdock.cmd # запуск без установки (Windows)
395
+ ├── pyproject.toml
396
+ └── README.md # RU (этот файл)
397
+ ```
398
+
399
+ ## Ограничения
400
+
401
+ - Не LAN-зеркало: portable бандл, без постоянного registry-сервиса.
402
+ - Для `build` / `up` нужен Docker.
403
+ - Trivy DB стареет; `db_max_age_days` только предупреждает, не блокирует.
404
+ - Образы тянутся по тегу (digest не пиннится).
405
+ - Конфиги конкретных проектов в CLI не хранятся.
406
+
407
+ ## Участие
408
+
409
+ Правила разработки и проверки — [docs/ru/CONTRIBUTING.md](docs/ru/CONTRIBUTING.md). Политика безопасности — [docs/ru/SECURITY.md](docs/ru/SECURITY.md); уязвимости не публикуйте в публичном issue.
410
+
411
+ ## Лицензия
412
+
413
+ [MIT](LICENSE) © 2026 D.Shapovalov.
414
+
415
+ ## Changelog
416
+
417
+ Полная история изменений — [docs/ru/CHANGELOG.md](docs/ru/CHANGELOG.md).
@@ -0,0 +1,49 @@
1
+ offline_docker/__init__.py,sha256=0uCu3VGzVNKlIb1SVdE6T2hJU-ZEwkQ-Wb2RuDjGHxA,76
2
+ offline_docker/__main__.py,sha256=n9Zi49itvIk41_Sqoxmfzi82rEdcFkM2OnpMGnLfGfY,97
3
+ offline_docker/actions.py,sha256=ktS5OdxokO_FZfVKXrEa3YIAjy4DcXUIGEv1WrKlbZI,5311
4
+ offline_docker/build.py,sha256=ihmLK3RxoP3EuM03soSc1fT3vHjXzLoGOFNuiJnS3J8,7928
5
+ offline_docker/cli.py,sha256=LRWUBVds54-moWHgAQmPgxhBTK8ukCOoXaUF4xDxAH0,15538
6
+ offline_docker/config.py,sha256=b2xPGzwUDu4VNVmUF-f_WKjDmwHAlEw0Ti6AqHe2O0w,25506
7
+ offline_docker/configure.py,sha256=5EgG9uuZuS_wdbFeH9ZHt0hT2vwQfMX7R2aovouFuIY,10690
8
+ offline_docker/context.py,sha256=D5ZPmR3l929MutvC0dq_T_zOZlEsucjoqvTh5wS92S0,3814
9
+ offline_docker/defaults.py,sha256=O_XsaoMKfgJUZNRsI0Ulv9KSR3hfdy5GVs1bGFPsfwc,1008
10
+ offline_docker/discover.py,sha256=fS0128rYdqeDpHO4kJKcnkYMWsQ8aAuNWLxW5HSZOwQ,4778
11
+ offline_docker/docker_cmd.py,sha256=1_2l-ZKGyXWR-imQp9oTNJHWdpjOhvqQNfyaU1gDJZg,3715
12
+ offline_docker/download_debs.py,sha256=-Fo8kwRfmJQS6ZdNwENyr7IG_54D58lJ86XfB7145aM,4928
13
+ offline_docker/download_images.py,sha256=hUOuOhM3gpGwBPiHbn4Cxd3DSfMETLDWMomdibIiHEs,969
14
+ offline_docker/download_npm.py,sha256=_FXmNb1kvjLHlbzjrA6ACzEcfy3u_aZJBe0-6wyXK-c,3707
15
+ offline_docker/download_trivy.py,sha256=2Bgp7t74c83Y7l3ExC4gWMzRhPOiQkAvgW_GN0_YpTU,2696
16
+ offline_docker/download_wheels.py,sha256=OX2ii19BMnkXw8AHO7YQ1CjIHCl6PWHZubR_cqdi5Ck,13244
17
+ offline_docker/download_yarn.py,sha256=njGPlT5Bwu4xABPgpruuDIM6u0GRbX5_QBH2TtT7KUk,7098
18
+ offline_docker/ensure_base.py,sha256=pETxzBfXeFgneDrZsdTG3VQPIXPHoWiH-ZHWhujlrGM,1988
19
+ offline_docker/hooks.py,sha256=UTO3AB7wnc-SS9A5s6IKhutZVYuRO6sKC-E8vjxLCAU,2845
20
+ offline_docker/init_cmd.py,sha256=ccjRpwvKTer0F6-ez3UKdWFRpsC7gEipUtot6_dtCjk,1987
21
+ offline_docker/load_images.py,sha256=TesZXHsim5g6Ls6ddNNw93xm9VSbPFTNnf-kr9ZSdU0,1046
22
+ offline_docker/oc_discovery.py,sha256=sAaDUB-8lZYds0HaFtmzQqtNzOXkFA7Gvw-QVZeZffU,7098
23
+ offline_docker/resolve.py,sha256=-xcvVBCRuUPmA6zGusaeD8UjkYXtqJGG937dK8H62ow,11083
24
+ offline_docker/scan.py,sha256=2MourQN5QfY28PRQ6klu_yn4H2HrMCQbVJfwsTym3jc,18131
25
+ offline_docker/status.py,sha256=Ze9Lv4ITVeAHpxEWJxUWN9ljT9gcmkO2g3rd8SSXOVc,10422
26
+ offline_docker/sync_cache.py,sha256=v4cFfGR6oOOEO7jR6osJ5v3twXbVh5VLGrlbA5dyzZo,4717
27
+ offline_docker/trivy_scan.py,sha256=BXkUqN6LZ-N71h6BFlYrnBXxFZ_99ZmTb6WTGOymuwQ,2657
28
+ offline_docker/user_config.py,sha256=2jxm1tsIAXSvZmEee6ZxYEkDcXRG4MpXgLEMKnMxydI,2550
29
+ offline_docker/utils.py,sha256=EiYRAbMnIy0kffAEHw_mZq_4cHWSaD4Ph3gf_C77ggg,2268
30
+ offline_docker/validate.py,sha256=0iO0byMab977kRmrzBqIotml2f5qmvijBa3yTzCQNXA,2143
31
+ offline_docker/wheel_prune.py,sha256=zUrCtyP2sLWBshGRGW7wpceIZEg7B4gpvva33S-Sydo,4701
32
+ offline_docker/sources/__init__.py,sha256=dtvRuw-dWmHpSEqb8Gg9geTfe4iaFljGwuGnDwqhDG4,527
33
+ offline_docker/sources/base.py,sha256=pOBj-k0PouV8pHdzc8GT9PHyrHFD02kch5Om9gJ-C3c,440
34
+ offline_docker/sources/filesystem.py,sha256=7L6-ldEOUc4P7zipgr4JIHe7y7Dw17-kfi656wgYq-0,503
35
+ offline_docker/sources/nexus.py,sha256=bD8SiDJVAJOYB5TkbIKUloO0pdzR-51L6N4JOasYIWg,1885
36
+ offline_docker/sources/nexus_client.py,sha256=6KIXwNnyBz6M2ViAUFjg4w1tnQnQePkgqkTgC1zQH6w,2013
37
+ offline_docker/sources/proget.py,sha256=PbWgusm4ndJxjgaubE4D9n-uW6oK2It-ClAbTv2LwqM,1481
38
+ offline_docker/sources/proget_client.py,sha256=MVSJmQrfXqvAv6IeWzkjGl1QZE12thNMvKCz0bedM8w,1990
39
+ offline_docker/sources/upstream.py,sha256=mWj8u8hyq4fNUjJdEbBPAcbeNPHiDbClLhzJL_MX2Ow,1124
40
+ offline_docker/tui/__init__.py,sha256=8mqm7BSdeeffOQ7FkmcTn7dYJfpwAR9JtB8mRknSnBg,482
41
+ offline_docker/tui/app.py,sha256=6xnZjfTJJxldZ_yPnzQP_MSlkGdwM_FTkbVttaZb10g,1430
42
+ offline_docker/tui/runner.py,sha256=RDfbidgDKWhxpDJlTUrOWwiRkmtZCwieDT8uoQpzkQw,2553
43
+ offline_docker/tui/screens.py,sha256=SLPdoQ-5UR4K8sZQR5JcQrdesJ9OvRDL1j7xgBnl4z0,11639
44
+ offline_docker_cli-2.2.2.dist-info/licenses/LICENSE,sha256=cekH1grv_rsvyT3JwCujzD_BAfhBmMgOh9UfDt7E_X8,1069
45
+ offline_docker_cli-2.2.2.dist-info/METADATA,sha256=f84XLoiGjnBIiFa-SYdsi7B22oHnZyYW3QlsEVBYKfo,19788
46
+ offline_docker_cli-2.2.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
47
+ offline_docker_cli-2.2.2.dist-info/entry_points.txt,sha256=sB3WhnHmByt00eRCVD7GnlJQo-9NLWdwnp3Oby_87Ls,52
48
+ offline_docker_cli-2.2.2.dist-info/top_level.txt,sha256=wQ-vmVJgW6vTdWx1LVwP4-iQQnkIQcaG9XR2F2h6lIc,15
49
+ offline_docker_cli-2.2.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ offdock = offline_docker.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 D.Shapovalov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ offline_docker