Metadata-Version: 2.4
Name: wimcast
Version: 0.1.0
Summary: Linux-native Windows image builder for Foreman. Drop-in replacement for WIMan/GenerateWIM.ps1.
Author: 6admin.io
License-Expression: MIT
Project-URL: Homepage, https://gitlab.com/6admin.io/misc/wimcast
Project-URL: Source, https://gitlab.com/6admin.io/misc/wimcast
Project-URL: Issues, https://gitlab.com/6admin.io/misc/wimcast/-/issues
Project-URL: Changelog, https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/CHANGELOG.md
Project-URL: Documentation, https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/runbook.md
Keywords: foreman,wim,wimlib,winpe,pxe,provisioning
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Installation/Setup
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pycdlib>=1.20.0
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == "dev"
Requires-Dist: pytest-asyncio<2,>=0.23; extra == "dev"
Requires-Dist: pytest-cov<8,>=5; extra == "dev"
Requires-Dist: mypy<3,>=1.10; extra == "dev"
Requires-Dist: ruff<1,>=0.5; extra == "dev"
Provides-Extra: release
Requires-Dist: build<2,>=1.2; extra == "release"
Requires-Dist: twine<7,>=6.2; extra == "release"
Dynamic: license-file

# wimcast

[![Python versions](https://img.shields.io/pypi/pyversions/wimcast)](https://pypi.org/project/wimcast/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/LICENSE)
[![Code style](https://img.shields.io/badge/code%20style-ruff-000000)](https://docs.astral.sh/ruff/)
[![Types](https://img.shields.io/badge/types-mypy--strict-blue)](https://mypy-lang.org/)
[![SemVer](https://img.shields.io/badge/semver-2.0.0-brightgreen)](https://semver.org/)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)

Build Windows images for [Foreman](https://theforeman.org) on Linux.

wimcast turns Windows ISOs into a Foreman-ready HTTP tree of per-edition WIM
files. It is a drop-in replacement for [WIMan](https://github.com/UXabre/WIMan)
/ `GenerateWIM.ps1`, running **unprivileged on Linux**: no Windows host, no
root, no `mount(2)`, no FUSE, no ADK.

```
sources/<arch>/<name>/*.iso   -->   finalized/<arch>/<name>/
                                        sources/
                                          boot.wim
                                          ServerStandard.wim
                                          ServerDatacenter.wim
                                          images.ini
                                          drivers/...
                                        wimboot
                                        bootmgr, bootmgr.efi
                                        boot/boot.sdi, boot/bcd
                                        build.json
```

## Why wimcast

| Problem | wimcast solution |
|---------|------------------|
| WIMan needs Windows, the ADK and an admin shell | Any Linux machine, unprivileged |
| Serial builds, one ISO at a time | Parallel scheduler with CPU semaphores |
| Downloads `latest` wimboot on every build (non-reproducible) | Pinned, checksummed wimboot |
| Full re-export of unchanged ISOs | Content-addressed cache: unchanged ISO skips re-export |
| Driver tree never published (production bug in WIMan) | `sources/drivers/` published for deploy-time DISM |

## Quick start

```bash
uv tool install wimcast   # or: pipx install wimcast

wimcast doctor                 # check prerequisites (safe, installs nothing)
wimcast probe sources/         # inspect your ISOs before building
wimcast build sources/         # build the finalized/ tree
ls finalized/                  # ready to serve over HTTP
```

That's it. Three commands from zero to a deployable tree.

## Installation

```bash
uv tool install wimcast
# or: pipx install wimcast
# or: python3.12 -m pip install --user wimcast
```

Debian/Ubuntu:

```bash
sudo apt install wimtools
uv tool install wimcast   # pycdlib is installed as a dependency
```

`wimcast doctor` tells you exactly what is missing, and whether it is
required or only conditional.

## Deploy to Foreman

The full walkthrough (deployment repo, CI pipeline, web server, Foreman
setup with Ansible) is in [docs/foreman-integration.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/foreman-integration.md).

The 30-second version:

1. **Create a deployment repo** with your ISOs:

   ```
   deploy-repo/
     sources/<arch>/<name>/*.iso     # your ISOs
     .gitlab-ci.yml                  # copy ci/examples/deploy-pipeline.yml
     wimcast.toml                    # optional, see ci/examples/wimcast.toml
   ```

2. **Configure 4 CI/CD variables**: `WEB_SERVER`, `WEB_SERVER_URL`,
   `WEB_SERVER_USER`, `SSH_PRIVATE_KEY_B64`.
3. **Push**: the pipeline runs `init -> build -> deploy -> verify` and your
   `finalized/` tree lands on your web server.
4. **Configure Foreman** (media, OS, provisioning template): see the
   Ansible playbook in [docs/foreman-integration.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/foreman-integration.md).

Ready-to-copy files:

| File | Purpose |
|------|---------|
| [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 |
| [ci/examples/wimcast.toml](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/ci/examples/wimcast.toml) | Example configuration |

## Commands

| Command | What it does |
|---------|--------------|
| `wimcast doctor` | Check the runtime contract (required + conditional tools) |
| `wimcast probe [path]` | Inspect ISO(s) without building: backend, WIM paths, per-index image metadata |
| `wimcast build [path]` | Build all sources into `./finalized` |

Global flags: `--version`, `--config <path>`, `-v/--verbose`.

## Configuration

`wimcast.toml` in the working directory, all values optional. A missing
file yields the defaults; an invalid value is an error.

```toml
[winpe]
source = "iso"          # "iso" (default): boot.wim index 1 from the ISO
# source = "file"       # externally prepared WinPE (e.g. ADK-built)
# path   = "assets/boot-winpe-amd64.wim"
# sha256 = "..."
iso_index = 1           # boot.wim index to export as WinPE base

[editions]
allowed = []            # substring match on image name; e.g. ["Standard", "Datacenter"]

[output]
filename_scheme = "installationtype"   # or "editionid" or "install"

[wimboot]
version = "2.9.0"       # pinned version, verified by checksum
# sha256 = "..."        # required for an unpinned version; overrides the built-in digest

[jobs]
cpu = 0                 # concurrent wimlib-imagex calls (0 = auto-detect)

[cache]
directory = ".cache"    # content-addressed store; unset = no cache

[tools]
wimlib_imagex = "wimlib-imagex"   # override binary path if not in PATH
sevenzip = "7z"                   # ISO fallback backend
```

wimboot is downloaded once from the pin's GitHub release and then cached.
The built-in SHA-256 is checked before use; override `[wimboot] sha256` to
pin a different release, including offline mirrors.

## Requirements

| Tool | Minimum | Package | Required |
|------|---------|---------|----------|
| Python | 3.12 | python3 | yes |
| `wimlib-imagex` | 1.13 | `wimtools` | yes |
| `pycdlib` | 1.20 | pip dependency | yes |
| `7zz` | - | `p7zip-full` | no (ISO fallback backend) |
| `rsync` | - | `rsync` | no (deployment pipeline) |
| `qemu-system-x86_64` | - | `qemu-system` | no (smoke test) |

## Drivers

- **boot.wim (WinPE)**: INF trees from `winpe/drivers/` are added into the
  WIM under `\Drivers\` and loaded with `drvload` in `startnet.cmd` before
  `wpeinit`.
- **install.wim (installed OS)**: the driver tree is published into
  `finalized/<arch>/<name>/sources/drivers/` and applied at deploy time by
  `dism /image:W:\ /add-driver /recurse` inside WinPE. Offline injection
  into `install.wim` needs DISM, so it is not done on Linux.

## Windows versions

Supported target: Windows Server 2016/2019/2022/2025 and Windows 10/11
(`amd64`). `wimcast probe` reports the backend actually used per ISO; the
live test matrix is maintained in
[docs/compatibility.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/docs/compatibility.md).

## Non-goals (deliberate)

- **Offline update integration** (`.msu`/`.cab`): `updates/` is warned about
  and ignored. Patching happens after deployment (Ansible/WSUS/GPO).
- **Offline driver injection into `install.wim`**: drivers are published as
  a tree and injected by DISM inside WinPE at deploy time.
- **ISO authoring**: the output is an HTTP tree, not bootable media.

## Contributing

See [CONTRIBUTING.md](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/CONTRIBUTING.md): architecture, module map, quality
gates, conventions, release process.

## License

[MIT](https://gitlab.com/6admin.io/misc/wimcast/-/blob/main/LICENSE)
