wimcast 0.1.0__tar.gz

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 (64) hide show
  1. wimcast-0.1.0/CHANGELOG.md +97 -0
  2. wimcast-0.1.0/CONTRIBUTING.md +186 -0
  3. wimcast-0.1.0/LICENSE +21 -0
  4. wimcast-0.1.0/MANIFEST.in +8 -0
  5. wimcast-0.1.0/PKG-INFO +229 -0
  6. wimcast-0.1.0/README.md +193 -0
  7. wimcast-0.1.0/ci/examples/deploy-pipeline.yml +141 -0
  8. wimcast-0.1.0/ci/examples/wimcast.toml +30 -0
  9. wimcast-0.1.0/docs/behaviour.md +179 -0
  10. wimcast-0.1.0/docs/benchmarks.md +55 -0
  11. wimcast-0.1.0/docs/ci-contract.md +128 -0
  12. wimcast-0.1.0/docs/compatibility.md +29 -0
  13. wimcast-0.1.0/docs/foreman-integration.md +251 -0
  14. wimcast-0.1.0/docs/inventory.md +77 -0
  15. wimcast-0.1.0/docs/output-contract.md +103 -0
  16. wimcast-0.1.0/docs/parity.md +27 -0
  17. wimcast-0.1.0/docs/runbook.md +122 -0
  18. wimcast-0.1.0/pyproject.toml +87 -0
  19. wimcast-0.1.0/scripts/smoke.py +114 -0
  20. wimcast-0.1.0/setup.cfg +4 -0
  21. wimcast-0.1.0/tests/test_bootfiles.py +149 -0
  22. wimcast-0.1.0/tests/test_build.py +229 -0
  23. wimcast-0.1.0/tests/test_cache.py +112 -0
  24. wimcast-0.1.0/tests/test_cli.py +86 -0
  25. wimcast-0.1.0/tests/test_config.py +186 -0
  26. wimcast-0.1.0/tests/test_doctor.py +155 -0
  27. wimcast-0.1.0/tests/test_drivers.py +88 -0
  28. wimcast-0.1.0/tests/test_inf.py +82 -0
  29. wimcast-0.1.0/tests/test_iso.py +306 -0
  30. wimcast-0.1.0/tests/test_iso_external.py +106 -0
  31. wimcast-0.1.0/tests/test_manifest.py +245 -0
  32. wimcast-0.1.0/tests/test_package.py +52 -0
  33. wimcast-0.1.0/tests/test_publish.py +59 -0
  34. wimcast-0.1.0/tests/test_run.py +55 -0
  35. wimcast-0.1.0/tests/test_sched.py +222 -0
  36. wimcast-0.1.0/tests/test_smoke.py +107 -0
  37. wimcast-0.1.0/tests/test_wim.py +150 -0
  38. wimcast-0.1.0/tests/test_winpe.py +165 -0
  39. wimcast-0.1.0/wimcast/__init__.py +3 -0
  40. wimcast-0.1.0/wimcast/__main__.py +6 -0
  41. wimcast-0.1.0/wimcast/bootfiles.py +151 -0
  42. wimcast-0.1.0/wimcast/build.py +453 -0
  43. wimcast-0.1.0/wimcast/cache.py +172 -0
  44. wimcast-0.1.0/wimcast/cli.py +192 -0
  45. wimcast-0.1.0/wimcast/config.py +293 -0
  46. wimcast-0.1.0/wimcast/doctor.py +160 -0
  47. wimcast-0.1.0/wimcast/drivers.py +98 -0
  48. wimcast-0.1.0/wimcast/inf.py +101 -0
  49. wimcast-0.1.0/wimcast/iso.py +258 -0
  50. wimcast-0.1.0/wimcast/manifest.py +169 -0
  51. wimcast-0.1.0/wimcast/model.py +53 -0
  52. wimcast-0.1.0/wimcast/probe.py +128 -0
  53. wimcast-0.1.0/wimcast/publish.py +72 -0
  54. wimcast-0.1.0/wimcast/py.typed +0 -0
  55. wimcast-0.1.0/wimcast/run.py +104 -0
  56. wimcast-0.1.0/wimcast/sched.py +102 -0
  57. wimcast-0.1.0/wimcast/wim.py +159 -0
  58. wimcast-0.1.0/wimcast/winpe.py +111 -0
  59. wimcast-0.1.0/wimcast.egg-info/PKG-INFO +229 -0
  60. wimcast-0.1.0/wimcast.egg-info/SOURCES.txt +62 -0
  61. wimcast-0.1.0/wimcast.egg-info/dependency_links.txt +1 -0
  62. wimcast-0.1.0/wimcast.egg-info/entry_points.txt +2 -0
  63. wimcast-0.1.0/wimcast.egg-info/requires.txt +12 -0
  64. wimcast-0.1.0/wimcast.egg-info/top_level.txt +1 -0
@@ -0,0 +1,97 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-10-21
11
+
12
+ Initial release.
13
+
14
+ ### Added
15
+
16
+ - `wimcast build`: full build pipeline (extract, export, winpe, bootfiles,
17
+ publish, manifest, verify) over a `sources/<arch>/<name>/` tree.
18
+ - `wimcast doctor`: runtime contract preflight (Python, wimlib-imagex,
19
+ pycdlib; conditional 7zz, rsync, qemu), never installs anything.
20
+ - `wimcast probe`: ISO inspection without building (arch, editions,
21
+ indices, sizes, ISO backend used).
22
+ - ISO reading via pycdlib with automatic `7zz` fallback backend; external
23
+ members are streamed and bounded by a timeout.
24
+ - wimlib facade: `wiminfo --xml` (UTF-16LE), export, update, verify.
25
+ - Content-addressed cache with sqlite index, flock-guarded and atomic.
26
+ - Per-edition WIM export with LZX compression and `--boot` support.
27
+ - boot.wim customisation: overlay, `\Drivers\`, `startnet.cmd` drvload
28
+ loop before `wpeinit`.
29
+ - `[winpe] source = "file"` extension point for externally prepared
30
+ WinPE WIMs, with sha256 validation.
31
+ - Boot files: pinned wimboot download (2.9.0) with SHA-256 verification,
32
+ bootmgr, boot.sdi, BCD, EFI.
33
+ - Atomic publish into `finalized/` (temp file + `os.replace`) with stale
34
+ file pruning.
35
+ - `images.ini` manifest (frozen v1 contract) and `build.json` (rich,
36
+ written after verification with real artifacts and stage durations).
37
+ - INF parser (decorated sections, `%strings%`, CRLF, UTF-16).
38
+ - Boot.wim driver injection via file placement + `drvload` loop (Level A).
39
+ - Install.wim driver trees published under `sources/drivers/` and applied
40
+ at deploy time (Linux has no DISM).
41
+ - Parallel scheduler: `[jobs] cpu` semaphore bounds concurrent wimlib
42
+ invocations across sources.
43
+ - GitLab CI: `lint -> test (3.12, 3.13) -> build -> publish`; tagged
44
+ releases publish to PyPI via Trusted Publishing (OIDC, no token).
45
+ - `scripts/smoke.py`: QEMU boot test that requires the `startnet.cmd`
46
+ serial banner.
47
+ - Design docs: behaviour, ci-contract, inventory, output-contract,
48
+ compatibility, parity, benchmarks, runbook.
49
+ - User docs: README and `docs/foreman-integration.md`. Example
50
+ configuration at `ci/examples/wimcast.toml`.
51
+
52
+ ### Changed
53
+
54
+ - Configuration is strict: unknown sections/keys, invalid TOML, bad
55
+ digests and invalid values fail with an actionable `ConfigError`.
56
+ - `[jobs] io` was removed; `[jobs] cpu` is the only parallelism knob.
57
+ - Default `[winpe] overlay_dir` is unset (an overlay is optional).
58
+ - Packaging uses a single version source (`wimcast.__version__`), PEP 639
59
+ license metadata and typed-package markers.
60
+
61
+ ### Fixed
62
+
63
+ - Missing default WinPE overlay no longer makes a default build fail.
64
+ - Edition filenames are collision-free and case-insensitively unique,
65
+ even when `INSTALLATIONTYPE` values collide.
66
+ - WIM metadata names are sanitized before becoming paths or `images.ini`
67
+ entries (path traversal, control characters, reserved names).
68
+ - wimboot downloads are checksum-verified and cached under the digest.
69
+ - `7zz` fallback extracts/streams the exact member instead of a
70
+ hard-coded path.
71
+ - Stale files in `finalized/` are pruned after a rebuild.
72
+ - CLI catches known build errors instead of printing raw tracebacks and
73
+ always closes the cache.
74
+ - Paths expand `~`; config-relative and temporary paths are no longer
75
+ silently cwd-dependent in manifests.
76
+ - `wimlib-imagex update` sends commands over stdin (a single invocation
77
+ accepts one `--command`).
78
+ - `ExternalReader` has list/stream timeouts and kills its process group.
79
+ - Quoted wimlib update paths, so directories with spaces work.
80
+ - INF decoding falls back to replacement; unreadable INFs are skipped
81
+ with a warning.
82
+ - `ScheduleError`, `WinpeError` and `BootfilesError` now return their
83
+ message from `str(exc)`.
84
+ - Duplicate `Artifact` models, dead scheduler IO semaphore and unused
85
+ publish helpers were removed.
86
+ - CI removed unreachable runner tags that left pipelines pending.
87
+
88
+ ### Security
89
+
90
+ - WIM metadata, edition names and driver reports are sanitized before
91
+ use in paths, manifests or terminal output.
92
+ - Downloads and cached blobs are integrity-checked (SHA-256).
93
+ - External tools run without a shell, with captured output and bounded
94
+ timeouts; timeouts kill the whole process group.
95
+
96
+ [Unreleased]: https://gitlab.com/6admin.io/misc/wimcast/-/compare/v0.1.0...main
97
+ [0.1.0]: https://gitlab.com/6admin.io/misc/wimcast/-/tags/v0.1.0
@@ -0,0 +1,186 @@
1
+ # Contributing to wimcast
2
+
3
+ Thanks for contributing. This project is deliberately small and strictly
4
+ governed: every rule below exists to keep it that way.
5
+
6
+ ## Architecture
7
+
8
+ ### Pipeline
9
+
10
+ One source leaf (`sources/<arch>/<name>/*.iso`) produces one
11
+ `finalized/<arch>/<name>/` tree:
12
+
13
+ ```
14
+ probe -> hash -> extract -> info -> filter -> export(per edition) -> winpe
15
+ -> bootfiles -> publish -> manifest -> verify
16
+ ```
17
+
18
+ Every expensive stage is keyed in the content-addressed cache
19
+ (`[cache] directory`); a hit skips the work entirely and only republishes
20
+ the files. The scheduler runs multiple sources concurrently, bounded by
21
+ `[jobs] cpu`.
22
+
23
+ ### Module map
24
+
25
+ | Module | Responsibility |
26
+ |--------|----------------|
27
+ | `config.py` | TOML to frozen dataclasses, explicit validation. Every site decision (paths, versions, job limits) lives here, never in code |
28
+ | `doctor.py` | runtime contract preflight: the single source of truth for what the tool needs. Adding a dependency without a `doctor` entry is a bug |
29
+ | `run.py` | subprocess wrapper: `Runner` protocol (injected in tests), `SubprocessRunner`, `ToolError` |
30
+ | `model.py` | core dataclasses: `Source`, `WimImage`, `IsoMedia`, `Artifact` |
31
+ | `iso.py` | ISO reading: pycdlib primary, `7zz` fallback |
32
+ | `wim.py` | wimlib facade: info, export, update, verify |
33
+ | `winpe.py` | boot.wim customisation (overlay, drivers, startnet.cmd) |
34
+ | `inf.py` | INF parser (decorated sections, `%strings%`, CRLF, UTF-16) |
35
+ | `drivers.py` | driver discovery, WinPE injection, publication |
36
+ | `bootfiles.py` | wimboot, bootmgr, boot.sdi, BCD, EFI |
37
+ | `publish.py` | atomic writes into `finalized/` |
38
+ | `manifest.py` | `images.ini` (frozen v1 contract, parsed by Foreman) + `build.json` (audit, never parsed) |
39
+ | `cache.py` | content-addressed store + sqlite index |
40
+ | `sched.py` | parallel source scheduler bounded by `[jobs] cpu` |
41
+
42
+ ### Key contracts
43
+
44
+ - `finalized/` layout and `images.ini` schema are frozen for v1: see
45
+ `docs/output-contract.md`. Changing them is a MAJOR bump.
46
+ - `images.ini` and WIM naming are parity-sensitive: covered by golden
47
+ tests and by QEMU smoke tests (`scripts/smoke.py`).
48
+ - `build.json` is additive only, never parsed by Foreman.
49
+
50
+ ## Ground rules
51
+
52
+ - **KISS / YAGNI / DRY**. The simplest thing that works, no speculative
53
+ generality. An interface with one implementation and no foreseeable second
54
+ one is not accepted.
55
+ - **Security by default**: validate inputs at the boundary, never hardcode
56
+ secrets, never log sensitive data, fail loud in development.
57
+ - **No new dependencies** without a written reason and a `doctor` entry.
58
+ If it is not in `wimcast doctor`, adding it is a bug.
59
+
60
+ ## Development setup
61
+
62
+ ```bash
63
+ git clone git@gitlab.com:6admin.io/misc/wimcast.git
64
+ cd wimcast
65
+ python3.12 -m venv .venv
66
+ source .venv/bin/activate
67
+ pip install -e ".[dev]"
68
+ wimcast doctor
69
+ ```
70
+
71
+ Note: `--system-site-packages` is only needed if the registry feature
72
+ (`hivex`, not on PyPI) is ever re-enabled. It is not needed today.
73
+
74
+ ## Quality gates
75
+
76
+ Every contribution must pass, locally or in CI:
77
+
78
+ ```bash
79
+ ruff check .
80
+ ruff format --check .
81
+ mypy wimcast/
82
+ pytest
83
+ ```
84
+
85
+ - **Ruff** for lint and format (config in `pyproject.toml`, line length 100).
86
+ - **mypy strict** for typing. `tests/` is excluded from strict checking.
87
+ - **pytest** for tests. Unit tests must not require external tools: the
88
+ `Runner` is injected. Fixture tests may use `wimcapture` and `xorriso`
89
+ when the machine has them; media must never be committed to the repo
90
+ (licensing).
91
+
92
+ ## Commit convention
93
+
94
+ [Conventional Commits](https://www.conventionalcommits.org/) 1.0.0:
95
+
96
+ ```
97
+ <type>(<optional scope>): <description>
98
+
99
+ [optional body]
100
+
101
+ [optional footer]
102
+ ```
103
+
104
+ Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`,
105
+ `ci`, `revert`. Scopes (optional, encouraged): `config`, `doctor`, `iso`,
106
+ `wim`, `winpe`, `drivers`, `inf`, `cache`, `publish`, `manifest`, `sched`,
107
+ `cli`, `docs`, `ci`.
108
+
109
+ Examples:
110
+
111
+ ```
112
+ feat(wim): add LZX export with configurable thread count
113
+ fix(iso): handle UDF paths with Unicode characters
114
+ docs: explain wimboot version pinning
115
+ chore(ci): pin wimboot in Containerfile
116
+ ```
117
+
118
+ ## Versioning
119
+
120
+ [SemVer](https://semver.org/) 2.0.0:
121
+
122
+ - **MAJOR**: breaking changes to the CLI, config schema, or output layout.
123
+ - **MINOR**: backward-compatible functionality.
124
+ - **PATCH**: backward-compatible fixes.
125
+
126
+ The `layout_version` key in `wimcast.toml` tracks output compatibility
127
+ separately from the package version. Changing the default output layout is a
128
+ MAJOR bump even if the code diff is small.
129
+
130
+ ## Release process
131
+
132
+ 1. Update `CHANGELOG.md` (Keep a Changelog format), move `[Unreleased]`
133
+ entries into a dated release section.
134
+ 2. Bump `__version__` in `wimcast/__init__.py` (the only version source;
135
+ `pyproject.toml` reads it dynamically).
136
+ 3. **Wait for CI to pass on `main`** (lint + test-3.12/3.13 + build green).
137
+ 4. Build and validate the artifacts locally:
138
+
139
+ ```bash
140
+ python -m pip install -e ".[release]"
141
+ python -m build
142
+ twine check --strict dist/*
143
+ ```
144
+
145
+ 5. Tag the exact commit, push the tag, and let the `publish` job upload via
146
+ PyPI Trusted Publishing:
147
+
148
+ ```bash
149
+ git tag v<version>
150
+ git push origin v<version>
151
+ ```
152
+
153
+ 6. Verify the release on PyPI, then install the published wheel in a clean
154
+ environment and run `wimcast --version` and `wimcast doctor`.
155
+
156
+ Never tag a broken build. If CI fails, fix first, tag after. The `publish`
157
+ job also refuses a tag that does not match `wimcast.__version__`.
158
+
159
+ ## Testing policy
160
+
161
+ - New code ships with tests.
162
+ - Behaviour changes update existing tests in the same commit.
163
+ - Parity-sensitive code (manifest, publish, naming) is covered by golden
164
+ tests on `images.ini` and by QEMU smoke tests (`scripts/smoke.py`)
165
+ against reference trees.
166
+ - Windows media never enters the repository or any public artifact store.
167
+
168
+ ## Branching model (simplified gitflow)
169
+
170
+ - **`main`** is always deployable. No direct commits.
171
+ - **`feat/*`** branches for features; **`fix/*`** for bug fixes.
172
+ - **`hotfix/*`** for urgent production fixes (branch from `main`, merge back).
173
+ - Open a merge request against `main`, reference the issue, keep the diff
174
+ focused.
175
+ - The operator reviews every MR; no self-merge.
176
+ - Tag `main` with `v<semver>` at release time (see Release process above).
177
+
178
+ ## Docs
179
+
180
+ - Design decisions are recorded in `docs/`, not only in commit messages.
181
+ - `docs/compatibility.md` and `docs/parity.md` are living documents: update
182
+ them when you touch ISO backends, drivers, or output behaviour.
183
+
184
+ ## Questions
185
+
186
+ Open an issue: https://gitlab.com/6admin.io/misc/wimcast/-/issues
wimcast-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 6admin.io
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,8 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CONTRIBUTING.md
5
+ recursive-include docs *.md
6
+ recursive-include ci/examples *
7
+ recursive-include scripts *.py
8
+ recursive-include tests *.py
wimcast-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: wimcast
3
+ Version: 0.1.0
4
+ Summary: Linux-native Windows image builder for Foreman. Drop-in replacement for WIMan/GenerateWIM.ps1.
5
+ Author: 6admin.io
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://gitlab.com/6admin.io/misc/wimcast
8
+ Project-URL: Source, https://gitlab.com/6admin.io/misc/wimcast
9
+ Project-URL: Issues, https://gitlab.com/6admin.io/misc/wimcast/-/issues
10
+ Project-URL: Changelog, https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/CHANGELOG.md
11
+ Project-URL: Documentation, https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/runbook.md
12
+ Keywords: foreman,wim,wimlib,winpe,pxe,provisioning
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: System :: Installation/Setup
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Requires-Python: >=3.12
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: pycdlib>=1.20.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest<9,>=8; extra == "dev"
28
+ Requires-Dist: pytest-asyncio<2,>=0.23; extra == "dev"
29
+ Requires-Dist: pytest-cov<8,>=5; extra == "dev"
30
+ Requires-Dist: mypy<3,>=1.10; extra == "dev"
31
+ Requires-Dist: ruff<1,>=0.5; extra == "dev"
32
+ Provides-Extra: release
33
+ Requires-Dist: build<2,>=1.2; extra == "release"
34
+ Requires-Dist: twine<7,>=6.2; extra == "release"
35
+ Dynamic: license-file
36
+
37
+ # wimcast
38
+
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/wimcast)](https://pypi.org/project/wimcast/)
40
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/LICENSE)
41
+ [![Code style](https://img.shields.io/badge/code%20style-ruff-000000)](https://docs.astral.sh/ruff/)
42
+ [![Types](https://img.shields.io/badge/types-mypy--strict-blue)](https://mypy-lang.org/)
43
+ [![SemVer](https://img.shields.io/badge/semver-2.0.0-brightgreen)](https://semver.org/)
44
+ [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
45
+
46
+ Build Windows images for [Foreman](https://theforeman.org) on Linux.
47
+
48
+ wimcast turns Windows ISOs into a Foreman-ready HTTP tree of per-edition WIM
49
+ files. It is a drop-in replacement for [WIMan](https://github.com/UXabre/WIMan)
50
+ / `GenerateWIM.ps1`, running **unprivileged on Linux**: no Windows host, no
51
+ root, no `mount(2)`, no FUSE, no ADK.
52
+
53
+ ```
54
+ sources/<arch>/<name>/*.iso --> finalized/<arch>/<name>/
55
+ sources/
56
+ boot.wim
57
+ ServerStandard.wim
58
+ ServerDatacenter.wim
59
+ images.ini
60
+ drivers/...
61
+ wimboot
62
+ bootmgr, bootmgr.efi
63
+ boot/boot.sdi, boot/bcd
64
+ build.json
65
+ ```
66
+
67
+ ## Why wimcast
68
+
69
+ | Problem | wimcast solution |
70
+ |---------|------------------|
71
+ | WIMan needs Windows, the ADK and an admin shell | Any Linux machine, unprivileged |
72
+ | Serial builds, one ISO at a time | Parallel scheduler with CPU semaphores |
73
+ | Downloads `latest` wimboot on every build (non-reproducible) | Pinned, checksummed wimboot |
74
+ | Full re-export of unchanged ISOs | Content-addressed cache: unchanged ISO skips re-export |
75
+ | Driver tree never published (production bug in WIMan) | `sources/drivers/` published for deploy-time DISM |
76
+
77
+ ## Quick start
78
+
79
+ ```bash
80
+ uv tool install wimcast # or: pipx install wimcast
81
+
82
+ wimcast doctor # check prerequisites (safe, installs nothing)
83
+ wimcast probe sources/ # inspect your ISOs before building
84
+ wimcast build sources/ # build the finalized/ tree
85
+ ls finalized/ # ready to serve over HTTP
86
+ ```
87
+
88
+ That's it. Three commands from zero to a deployable tree.
89
+
90
+ ## Installation
91
+
92
+ ```bash
93
+ uv tool install wimcast
94
+ # or: pipx install wimcast
95
+ # or: python3.12 -m pip install --user wimcast
96
+ ```
97
+
98
+ Debian/Ubuntu:
99
+
100
+ ```bash
101
+ sudo apt install wimtools
102
+ uv tool install wimcast # pycdlib is installed as a dependency
103
+ ```
104
+
105
+ `wimcast doctor` tells you exactly what is missing, and whether it is
106
+ required or only conditional.
107
+
108
+ ## Deploy to Foreman
109
+
110
+ The full walkthrough (deployment repo, CI pipeline, web server, Foreman
111
+ setup with Ansible) is in [docs/foreman-integration.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/foreman-integration.md).
112
+
113
+ The 30-second version:
114
+
115
+ 1. **Create a deployment repo** with your ISOs:
116
+
117
+ ```
118
+ deploy-repo/
119
+ sources/<arch>/<name>/*.iso # your ISOs
120
+ .gitlab-ci.yml # copy ci/examples/deploy-pipeline.yml
121
+ wimcast.toml # optional, see ci/examples/wimcast.toml
122
+ ```
123
+
124
+ 2. **Configure 4 CI/CD variables**: `WEB_SERVER`, `WEB_SERVER_URL`,
125
+ `WEB_SERVER_USER`, `SSH_PRIVATE_KEY_B64`.
126
+ 3. **Push**: the pipeline runs `init -> build -> deploy -> verify` and your
127
+ `finalized/` tree lands on your web server.
128
+ 4. **Configure Foreman** (media, OS, provisioning template): see the
129
+ Ansible playbook in [docs/foreman-integration.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/foreman-integration.md).
130
+
131
+ Ready-to-copy files:
132
+
133
+ | File | Purpose |
134
+ |------|---------|
135
+ | [ci/examples/deploy-pipeline.yml](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/ci/examples/deploy-pipeline.yml) | GitLab CI for the deployment repo |
136
+ | [ci/examples/wimcast.toml](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/ci/examples/wimcast.toml) | Example configuration |
137
+
138
+ ## Commands
139
+
140
+ | Command | What it does |
141
+ |---------|--------------|
142
+ | `wimcast doctor` | Check the runtime contract (required + conditional tools) |
143
+ | `wimcast probe [path]` | Inspect ISO(s) without building: backend, WIM paths, per-index image metadata |
144
+ | `wimcast build [path]` | Build all sources into `./finalized` |
145
+
146
+ Global flags: `--version`, `--config <path>`, `-v/--verbose`.
147
+
148
+ ## Configuration
149
+
150
+ `wimcast.toml` in the working directory, all values optional. A missing
151
+ file yields the defaults; an invalid value is an error.
152
+
153
+ ```toml
154
+ [winpe]
155
+ source = "iso" # "iso" (default): boot.wim index 1 from the ISO
156
+ # source = "file" # externally prepared WinPE (e.g. ADK-built)
157
+ # path = "assets/boot-winpe-amd64.wim"
158
+ # sha256 = "..."
159
+ iso_index = 1 # boot.wim index to export as WinPE base
160
+
161
+ [editions]
162
+ allowed = [] # substring match on image name; e.g. ["Standard", "Datacenter"]
163
+
164
+ [output]
165
+ filename_scheme = "installationtype" # or "editionid" or "install"
166
+
167
+ [wimboot]
168
+ version = "2.9.0" # pinned version, verified by checksum
169
+ # sha256 = "..." # required for an unpinned version; overrides the built-in digest
170
+
171
+ [jobs]
172
+ cpu = 0 # concurrent wimlib-imagex calls (0 = auto-detect)
173
+
174
+ [cache]
175
+ directory = ".cache" # content-addressed store; unset = no cache
176
+
177
+ [tools]
178
+ wimlib_imagex = "wimlib-imagex" # override binary path if not in PATH
179
+ sevenzip = "7z" # ISO fallback backend
180
+ ```
181
+
182
+ wimboot is downloaded once from the pin's GitHub release and then cached.
183
+ The built-in SHA-256 is checked before use; override `[wimboot] sha256` to
184
+ pin a different release, including offline mirrors.
185
+
186
+ ## Requirements
187
+
188
+ | Tool | Minimum | Package | Required |
189
+ |------|---------|---------|----------|
190
+ | Python | 3.12 | python3 | yes |
191
+ | `wimlib-imagex` | 1.13 | `wimtools` | yes |
192
+ | `pycdlib` | 1.20 | pip dependency | yes |
193
+ | `7zz` | - | `p7zip-full` | no (ISO fallback backend) |
194
+ | `rsync` | - | `rsync` | no (deployment pipeline) |
195
+ | `qemu-system-x86_64` | - | `qemu-system` | no (smoke test) |
196
+
197
+ ## Drivers
198
+
199
+ - **boot.wim (WinPE)**: INF trees from `winpe/drivers/` are added into the
200
+ WIM under `\Drivers\` and loaded with `drvload` in `startnet.cmd` before
201
+ `wpeinit`.
202
+ - **install.wim (installed OS)**: the driver tree is published into
203
+ `finalized/<arch>/<name>/sources/drivers/` and applied at deploy time by
204
+ `dism /image:W:\ /add-driver /recurse` inside WinPE. Offline injection
205
+ into `install.wim` needs DISM, so it is not done on Linux.
206
+
207
+ ## Windows versions
208
+
209
+ Supported target: Windows Server 2016/2019/2022/2025 and Windows 10/11
210
+ (`amd64`). `wimcast probe` reports the backend actually used per ISO; the
211
+ live test matrix is maintained in
212
+ [docs/compatibility.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/compatibility.md).
213
+
214
+ ## Non-goals (deliberate)
215
+
216
+ - **Offline update integration** (`.msu`/`.cab`): `updates/` is warned about
217
+ and ignored. Patching happens after deployment (Ansible/WSUS/GPO).
218
+ - **Offline driver injection into `install.wim`**: drivers are published as
219
+ a tree and injected by DISM inside WinPE at deploy time.
220
+ - **ISO authoring**: the output is an HTTP tree, not bootable media.
221
+
222
+ ## Contributing
223
+
224
+ See [CONTRIBUTING.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/CONTRIBUTING.md): architecture, module map, quality
225
+ gates, conventions, release process.
226
+
227
+ ## License
228
+
229
+ [MIT](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/LICENSE)