Portable virtualization artifacts¶
Windows capacity admission¶
export-ovf.ps1 -Prerelease checks free storage before it creates staging directories, retrieves credentials,
downloads software, builds, exports, or mutates a VM. Read-only GitHub metadata and retained-artifact verification
run first. Admission resolves each destination to its actual Windows volume and checks the additional allocation
for the next stage against fresh free space. Source verification, build/compaction, export, conversion, smoke,
and candidate staging are separate evidence boundaries. Paths sharing a volume compete for the same free bytes;
sequential VMware and Hyper-V smoke imports are not added together. Summaries identify the stage, paths, volume,
free and required bytes, components, and shortfall, with a readable GiB breakdown.
The initial check admits source verification, not an unconditional promise that every later stage will fit. After source verification, the build check uses the authenticated tree's actual file lengths for its snapshot copy. After compaction, the retained builder consumes only the space reported by the filesystem: its earlier zero-fill high-water mark is not reserved again alongside the smoke import. Every expensive stage still checks capacity before it starts. If another process consumes space or an artifact grows unexpectedly, a later check can stop the workflow with the verified outputs preserved for retry. No speculative deletion or compaction credit is added to free space.
The conservative planning estimates below are additional physical storage, in GiB. They are not the virtual capacity of the two empty 500 GiB data disks. Existing files already consume the reported free space and do not receive speculative cleanup credits.
Keep the size categories separate: guest virtual capacity is the addressable disk size; host allocation is the space currently consumed on the Windows volume; file logical length bounds an ordinary copy even when a source file has sparse holes or host compression. Stream-optimized VMDKs and the Hyper-V ZIP also compress guest data, so their archive sizes do not bound the growth of an imported disk. Admission includes conversion scratch, memory backing, and guest-write allowances separately. File-length measurements are not reported as physical allocation measurements.
Source reconstruction checks the aggregate declared size of selected archive members before extracting any payload. The 16 GiB limit includes the signed manifest and signature plus a 1 MiB reserve for the generated source identity; compressed download sizes alone do not establish this expansion bound. Oversized bundles are rejected and preserved.
| Component | Additional estimate | Lifetime |
|---|---|---|
| Signed software downloads | 2 | Source verification; larger or unknown GitHub asset sizes fail admission |
| Reconstructed verified source | 16, or verified retained tree length | Source through staging |
| Builder payload disks | 64 | Build admission; includes the 40 + 20 GiB zero-fill high-water mark and metadata |
| Builder compaction and memory | 44 | Build only; one 40 GiB payload scratch copy plus metadata/memory allowance |
| Source snapshot, source copies, ISO and credential staging | Source estimate + 8 | Build only |
| ISO download cache | 5, or zero for checksum-verified reuse | Build admission; the pinned ISO is about 4.31 GiB |
| OVF/OVA outputs and staged OVA copy | 8 | Export through staging |
| Export before asset-size admission | 60 | Export only |
| Hyper-V ZIP | 2 | Conversion through staging |
| Conversion extraction and VHDX scratch | 68 | Conversion only |
| VMware smoke import, growth, validation and memory | 84 | VMware smoke only |
| Hyper-V extraction, copies, growth and VMRS | 36 | Hyper-V smoke only |
| Candidate copies and metadata | 8 | Candidate staging |
| Operational headroom | 2 per volume | Every admission |
These estimates cover the canonical four-disk pipeline, including payload zero filling and compaction, not an arbitrary workload inside a diagnostic guest. The pinned VMware Packer plugin's compaction loop handles the two payload disks sequentially. Its Workstation driver finishes defragmentation and shrinking for one disk before advancing to the other; the scratch budget therefore covers the largest payload rather than a second copy of both disks.
The remaining export and conversion estimates conservatively cover both 40 + 20 GiB payloads before their output sizes are known. The converter subsequently discounts validated completed VHDX lengths and checks its ZIP step again. The Hyper-V package enforces an 8 GiB expanded limit: extraction plus a second disk copy account for at most 16 GiB, with 16 GiB guest growth and 4 GiB memory added for smoke. VMware's 84 GiB estimate retains the 64 GiB import/validation allowance plus the same 20 GiB memory/growth allowance. Neither smoke estimate reserves the two empty 500 GiB virtual data-disk capacities. The 8 GiB candidate-copy allowance covers the admitted release assets; existing candidate bytes are verified for reuse instead of copied again. These bounds remain deliberately larger than a typical compressed image and do not guarantee space for arbitrary writes to the virtual data disks.
For a fresh source, initial verification reserves at most 20 GiB including headroom. This is a source-extraction
bound, not a 20 GiB whole-build budget. On one volume, the following build admission reserves 123 GiB plus the
verified source-copy length, or 118 GiB plus that length when the existing ISO passes the builder's pinned SHA-512
check. Zero filling can expand thin disks substantially; thin provisioning is not proof that a complete build fits
in 20 GiB. The compaction and guest-growth allowances remain conservative estimates, not measured guarantees.
Cache admission checks every *.iso in the shared image/common/source directory, regardless of filename.
Checkout-local Packer caches are not reused by the release builder's fresh isolated child and receive no credit.
A verified retained template omits new builder allocations; a verified candidate omits build, export, conversion, and smoke allocations. Retained source, template provenance, powered-off state, and candidate bytes must pass their existing validation before the smaller resume plan is admitted. An invalid retained operation is preserved and rejected, never relocated automatically. When the source tree is already verified, only its temporary reconstruction copy is budgeted during source verification; that copy is removed before later stages. A newly created source tree remains allocated through candidate staging.
The workflow repeats admission before each heavy stage using its additional allocation plan. Hyper-V conversion discounts validated completed VHDX bytes from subsequent conversion checks while retaining the ZIP output budget. Direct OVF export, Hyper-V conversion and smoke entry points also check capacity; the standalone packaged Hyper-V importer uses actual VHDX file lengths for disk copies, checks again before each copy and VM creation/start, and separately budgets the observed 4 GiB VMRS allocation, 16 GiB guest growth, and 2 GiB metadata/headroom. Thus 4 GiB alone is not the total required free space. Capacity checks do not reserve space against concurrent processes. Later provider allocation errors retain their original diagnostic and the existing exact-identity cleanup safeguards.
On refusal, free space through an ownership-verified cleanup procedure or choose a supported output location.
-StagingRoot moves new prerelease staging and its builder only; it does not move checkout-local caches, OVF output,
conversion output, or smoke roots. Use a checkout on an adequately sized volume for those outputs, and use
-DestinationRoot for a standalone Hyper-V import. Do not move or delete a retained operation to evade admission.
Keep the completed source template powered off and preserve its provenance plus valid OVA/ZIP evidence for retry.
Measured storage example¶
On 2026-09-21, an isolated Windows build used published source v0.9.363
(1246b50d95648784ff76f1174824f7faafa1319e), Windows build 26200, VMware Workstation 26.0.1,
the pinned VMware Packer plugin 2.1.5, and QEMU 2.3.0. The fresh operation started with 150.70 GiB free.
A maintainer-authorized, process-only 140 GiB admission cap allowed measurement past the old 180 GiB forecast;
that cap is not a supported production setting or a replacement estimate.
The fresh build completed zero filling, compaction, export, conversion, and VMware smoke. Its first Hyper-V attempt
timed out without a management IPv4 address. A verified-template retry on Windows Default Switch ran the updated
admission code without an override and passed both smokes, including reboot checks. The producer then encountered
the separate nested OVF package staging issue. Selecting the actual
package with the supported stager's --ova-directory and supplying the exact OVA beside it completed all 14 candidate
assets and their readback validation. No image Release was published. That explicit extra OVA copy is included below.
| Phase | Largest sampled total task-file allocation, GiB |
|---|---|
| Retained-source verification | 15.00 |
| Fresh build, zero filling, and compaction | 17.80 |
| Export | 14.94 |
| Hyper-V conversion | 22.68 |
| VMware smoke | 25.07 |
| Hyper-V smoke | 30.08 |
| Candidate staging and readback | 22.82 |
These are total resident task files across the initial attempt and retry, including retained outputs and the ISO, not additional stage requirements. The verified source contained 33,717,157 bytes. The updated model's largest additional requirement for that fresh source is 123.03 GiB at build, including headroom, compared with the old 180 GiB initial forecast and the 30.08 GiB observed maximum during Hyper-V smoke. All seven stage admissions passed replay against their recorded free-space observations; the retry also passed every applicable resume-stage check live. The estimates intentionally retain headroom for unobserved zero-fill, compaction, copy, and guest-growth cases.
Measurement used Windows GetCompressedFileSizeW over the task's source/builder, export, conversion, smoke, ISO,
temporary, and build-state trees. Sampling was approximately five seconds initially and one second on retry/staging;
no file-read races were reported. This API accounts for sparse/compressed files but reports file length for ordinary
files, excluding filesystem metadata and allocation rounding. Recursive samples are not atomic and can miss brief
transient peaks, especially the short initial source extraction. Thus this observation does not establish a safe
30 GiB minimum, and it does not support a blanket 20 GiB whole-pipeline allowance.
Disposable VMware console diagnostics¶
For a fresh diagnostic import with a securely retrievable console password, rerun the normal prerelease command with an explicit bounded window, for example:
.\scripts\windows\vmware\export-ovf.ps1 -Prerelease `
-ManagementSwitch 'Atlaso-Mgmt' -ServiceSwitch 'Atlaso-Services' `
-SmokeConsoleMinutes 15
The window accepts 1 through 30 minutes. Omission runs ordinary release smoke. The installed 1Password plugin and
unlocked, authorized desktop integration are required for concealed-variable publication. Existing checkout-local
Environment selection and DPAPI-protected service-account configuration continue to authenticate the image builder;
Environment publication uses the plugin because the SDK Environment interface is read-only. The workflow never
overwrites DEFAULT_ADMIN_PASSWORD, DEFAULT_ROOT_PASSWORD, or another run's credential.
Before starting the exact fresh Atlaso-Ova-Console-<run> VM, the bounded helper generates a unique password and
stores it as concealed ATLASO_SMOKE_CONSOLE_<full-run-id> in the verified Atlaso Environment. The terminal prints
only that variable name, the exact VM path, and the non-secret handoff record. Open 1Password > Developer > Atlaso,
select that variable, and reveal it privately in 1Password. Use account root on that VM's local console; the same
temporary password also belongs to its bootstrap admin account. Retrieval is independent of guest networking and
SSH. Root SSH remains disabled. Never copy the value into a command line, task report, screenshot, or .env file.
The diagnostic window starts after power-on. At expiry, or ordinary interruption, the existing identity-verified smoke cleanup runs. Diagnostic runs always stop before successful smoke evidence or publication, even if the guest appears healthy; rerun ordinary smoke against a fresh import after troubleshooting. Source templates stay powered off and exported image bytes remain unchanged. A retained successful candidate is preserved and rejects diagnostic mode.
smoke-console-<run-id>.json in the retained operation records the exact run, VM directory, variable, and retirement
state without a password. The local credential exchange contains current-user DPAPI ciphertext only and is removed
after handoff. A successful exact VM cleanup retires the credential by destroying its only guest identity. Its
concealed 1Password record remains available as history: the current Environment plugin has no variable-deletion
operation. After verifying the handoff is retired and its exact VM is absent, the operator may remove that one obsolete
variable in 1Password. Do not append a duplicate variable as a supposed password rotation.
If the host or controller is killed, or cleanup cannot verify ownership, retain the VM and its concealed credential for recovery. The recorded window is no longer an enforced deadline after controller termination; do not claim the guest was stopped or that its password was revoked. Perform the documented exact-VM cleanup with the recorded VMX and expected name, independently verify provider and filesystem absence, and only then retire the matching variable. Never identify a cleanup target from its display name alone. An incomplete diagnostic run is never release evidence.
Canonical artifact lifecycle¶
Atlaso builds and validates one appliance template with VMware Workstation. A release publishes that template as the canonical OVA for VMware, Proxmox VE, and KVM, plus one Hyper-V ZIP converted from the same OVA payload. The import helpers normalize target-specific VM configuration without changing the source OVA.
The lifecycle is verify published software → construct the complete template → verify and shut down → export → test disposable imports. The published application wheel and complete CPython 3.14 wheelhouse are installed during construction. After final shutdown and compaction, the source template never restarts for software installation, provider selection, customization, or export preparation. Export and smoke tests revalidate its final hashes.
| Platform | Completed template | Deployed first boot |
|---|---|---|
| VMware | Installed open-vm-tools |
Retain and enable VMware Tools |
| KVM/QEMU/Proxmox | QEMU agent RPM and complete verified offline dependencies | Install locally and enable the agent |
| Hyper-V | Photon Hyper-V RPMs and complete verified offline dependencies | Install locally and enable required daemons |
All tools survive construction, final Photon updates, cleanup, and export. Only deployed appliances select a provider and remove unused packages. The selector installs from the verified local RPM closures, verifies required services, and commits selection before retryable staging cleanup. A failed detection, install, or service check blocks application readiness. See first boot guest-agent selection for the transaction behavior.
The guest-neutral Photon provisioner installs Atlaso's system-wide PowerShell profile in the canonical root reported
by the reviewed package layout. Current images use /usr/share/powershell; the older
/opt/microsoft/powershell/7 root remains an explicitly supported compatibility layout. VMware builds and derived
Hyper-V artifacts fail closed when the executable resolves elsewhere, when the profile ancestry is not root-owned and
non-writable, or when the destination is a symlink. Protected artifact verification accepts exactly one of those two
profile locations and requires the admitted Atlaso bytes with mode 0644.
When a package update moves the executable between those layouts, provisioning and wheel deployment remove the
inactive copy only after proving its canonical root-owned directory chain, exact Atlaso bytes, ownership, and mode.
Unexpected inactive content or a symlink fails closed instead of being removed or followed.
Virtualization has its own immutable Release namespace. A maintainer's existing Windows workstation creates and smokes
virtualization-vX.Y.Z-rc.N; a protected GitHub-hosted job signs and publishes it. Manual stable promotion runs that
exact prerelease OVA on Proxmox and KVM before publishing the unchanged OVA and Hyper-V bytes as
virtualization-vX.Y.Z. The software/update vX.Y.Z Release is the required source of the embedded wheel and CPython
3.14 wheelhouse, but never contains virtualization assets.
Successful main CI automatically publishes the 90-day atlaso-wheel-vX.Y.Z-<full-sha> Actions artifact with its
commit/version/digest and CI/publisher identity. The separately protected Publish appliance release workflow then
consumes and records that exact automatic-main wheel handoff, adds the offline CPython 3.14 wheelhouse, signs the
bundle, publishes vX.Y.Z, and advances development. Only after that software Release exists may the
separate manual virtualization producer consume it. The automatic wheel path has no signing material, Release/tag or
Pages write, channel, self-hosted runner, or virtualization access, and it cannot queue an OVA, Hyper-V, Proxmox, KVM,
or virtualization smoke job. If the handoff expires, protected Replay Python wheel admission from main accepts
only the exact commit and successful source CI run ID and attempt. It validates that evidence without target checkout,
then a completed-run handoff lets Publish Python wheel revalidate and rebuild without gaining virtualization
authority. If the software Release already exists, its signed assets are reused only after the replay wheel matches the
bundled wheel byte for byte, so recovery cannot change the immutable source Release consumed by virtualization.
The maintainer workstation is a trusted release producer. An optional explicitly approved ephemeral Windows runner is trusted for the same single release while it is online. Neither receives the signing key. The protected hosted finalizer performs the independent checks below as defense in depth and retains exclusive signing and publication authority, but it is not a reproducible Photon image builder and does not claim to prove an entire root filesystem safe against a compromised producer. A producer compromise is therefore a release-security incident that requires stopping publication, rotating affected credentials, and rebuilding from a known-good trusted workstation.
The producer cannot attribute a mixed live checkout to a later commit. The canonical VMware wrapper admits one clean commit, materializes an invocation-owned snapshot, removes the build identity's write access for the child lifetime, runs its bounded child, exact HCL template, and every Packer source from that tree, and records the snapshot inventory digest in schema-v3 VMX provenance. OVA export requires that exact binding; Hyper-V is derived only from the validated OVA and therefore inherits it. Protected finalization still verifies the signed software-source sidecar, privileged assets, artifact bytes, and publication identity independently. Those checks are defense in depth around the source-bound producer output, not a replacement for the snapshot boundary.
Published-software verification runs with Python's -B option before source ACL protection and again before Packer
admission. Imports cannot create bytecode in the admitted snapshot, including when the caller sets a Python cache
prefix; no PYTHONDONTWRITEBYTECODE workaround is required. The complete file inventory remains authoritative.
An inventory mismatch still blocks construction: preserve the failure evidence and investigate changed or added files
instead of excluding caches or accepting a new baseline.
Schema-v3 also carries template_contract: schema version 1, state uninitialized, and the exact verified
software_source identity. That identity binds the software tag, version, source commit, manifest and bundle digests,
application-wheel path and digest, and cp314 ABI. It is copied into OVA provenance and required during retained
template reuse, candidate validation, and protected publication. Protected read-only disk inspection additionally
checks the guest-tool inventories and absence of consumed provider selection, OVF customization, HTTPS initialization,
machine IDs, SSH host keys, and application identity. Provenance alone cannot replace those disk checks.
Older schema-v3 templates without this contract, previously booted templates, changed disks, mismatched software, and incomplete retained candidates are preserved and rejected with rebuild instructions. Never retrofit them by starting them or rewriting their provenance. Existing published release bytes remain immutable.
The shared machine contract is UEFI with Secure Boot disabled, four virtual CPUs, 4096 MiB RAM, two NICs, one SCSI controller, and four ordered disks:
- 40 GiB Photon OS at SCSI slot 0;
- 20 GiB Atlaso system content at SCSI slot 1;
- 500 GiB VCF Offline Depot at SCSI slot 2; and
- 500 GiB VCF Backups at SCSI slot 3.
The canonical VMware OVF declaration emitted by Atlaso's supported OVF Tool is
vmw:key="bootOptions.efiSecureBootEnabled" vmw:value="false". The validator also recognizes the older
uefi.secureBoot.enabled spelling for compatible artifacts, but every present supported declaration must explicitly
evaluate to false. A missing, enabled, malformed, or conflicting declaration blocks export and import. Export validates
the normalized descriptor's complete machine contract before writing provenance that records secure_boot: false.
Before either virtualization index is signed, the protected GitHub-hosted finalizer opens the OVA-validated
system-content VMDK read-only with libguestfs. It resolves the active CPython 3.14 environment, requires every hashed
member installed from the Atlaso wheel and complete signed wheelhouse to match, and rejects unexpected active files
except bounded inert pip metadata. Both image provisioning and release deployment disable bytecode compilation and
remove retained package bytecode; the protected finalizer rejects every active .pyc file rather than trusting
producer-generated compilation. Every shipped systemd unit that executes the active virtualenv also sets
PYTHONDONTWRITEBYTECODE=1, including the root console and first-boot customization services, so post-deployment
restarts cannot recreate bytecode before signing. The image retains the exact Photon RPM closure that owns the
interpreter and
standard library. The finalizer authenticates every retained RPM against the Photon package keys pinned in the admitted
Atlaso commit, requires its exact name, epoch, version, release, architecture, and SHA-256 digest to remain present in
the current official Photon 5.0 release or updates repository metadata, extracts those signed payloads on the hosted
runner, and requires /usr/bin/python3.14 plus the complete
/usr/lib/python3.14 tree in the guest to match byte for byte. The pinned keys originate from Photon OS's
photon-repos package sources; updating them requires an explicit reviewed source change. It also requires the active
virtualenv Python link to resolve only to that authenticated CPython 3.14 interpreter and every Atlaso console script to
match its signed-wheel entry point and canonical pip launcher. The finalizer opens both payload disks and compares every
Atlaso-provisioned privileged helper, service unit, drop-in,
console setting, vault profile, complete PowerShell global profile, and boot-branding asset with its exact bytes from the
admitted software-release commit. The installer places that profile in the supported PowerShell runtime home discovered
from the resolved pwsh executable, including the Photon package locations /opt/microsoft/powershell/7 and
/usr/share/powershell. The producer replaces that global profile with the canonical Atlaso import instead
of preserving workstation-controlled commands, and safely retires a proven Atlaso copy from the inactive supported
layout so exactly one global profile remains. Before comparing bytes, the finalizer also requires each privileged
file and trust key to be a root-owned regular file with its exact declared mode; every ancestor must be a root-owned
directory without group or other write access.
It also requires the installed update-trust directory to contain exactly that commit's public PEM set, rejecting both
altered files and injected trust keys. Producer-authored provenance and smoke evidence cannot substitute for these
independent payload checks. The
same boundary uses qemu-img compare to require both Hyper-V payload VHDX disks to
expose the same guest-visible bytes as the admitted OVA VMDKs and both 500 GiB Hyper-V data disks to match an
independent all-zero sparse reference.
The protected index signer also requires the exact versioned OVA name and the canonical OVF, manifest, provenance, and
two payload-VMDK names; suffix-compatible aliases are not publishable assets. The complete Hyper-V archive-name set
must equal exactly {atlaso-v<version>-hyperv-x86_64.zip} before the finalizer validates evidence, builds or signs an
index, or changes publication state. Missing, additional, wrong-version, case-variant, and path-variant suffix matches
fail closed. Stable promotion independently enforces the same invariant from the signed prerelease index before it can
publish the unchanged bytes.
The OVA contains files for the two payload disks. Its two 500 GiB data disks are fileless declarations. Import helpers retain fileless disks when the platform creates them, create only missing data disks, and reject reordered or conflicting disks.
Verify release assets¶
Download the release into a new directory, then obtain the trusted public key from the immutable source tag. The
release key ID is atlaso-release-2026-01; its SHA-256 fingerprint is
b0bb5614342c4f432a01c53fc4c9aae54c1eeffb12806539a92babbcda74b58e. This example verifies the detached Ed25519
signature, expected version, and every indexed asset before import:
TAG=virtualization-vX.Y.Z
ASSET_ROOT="atlaso-$TAG"
mkdir -- "$ASSET_ROOT"
gh release download "$TAG" --repo mdaneri/Atlaso --dir "$ASSET_ROOT"
curl --fail --location --output "$ASSET_ROOT/atlaso-release-2026-01.pem" \
"https://raw.githubusercontent.com/mdaneri/Atlaso/$TAG/image/common/update-trust/atlaso-release-2026-01.pem"
printf '%s %s\n' \
'b0bb5614342c4f432a01c53fc4c9aae54c1eeffb12806539a92babbcda74b58e' \
"$ASSET_ROOT/atlaso-release-2026-01.pem" | sha256sum --check --strict
jq -e --arg version "${TAG#virtualization-v}" --arg tag "$TAG" '
.schema_version == 2 and .kind == "atlaso-virtualization-artifacts" and
.classification == "stable" and .release_tag == $tag and
.source_software_tag == ("v" + $version) and
.version == $version and .signing_key_id == "atlaso-release-2026-01" and
(.source_release_manifest_sha256 | test("^[0-9a-f]{64}$")) and
(.application_wheel_sha256 | test("^[0-9a-f]{64}$")) and
(.source_commit | test("^[0-9a-f]{40}$")) and
(.assets | type == "array" and length > 0 and
all(.[]; (.name | test("^[A-Za-z0-9][A-Za-z0-9._-]*$")) and
(.size | type == "number") and (.sha256 | test("^[0-9a-f]{64}$"))) and
([.[].name] | length == (unique | length)))
' "$ASSET_ROOT/virtualization-artifact-index.json" >/dev/null
jq -e '
.schema_version == 1 and .algorithm == "ed25519" and
.key_id == "atlaso-release-2026-01" and (.signature | type == "string")
' "$ASSET_ROOT/virtualization-artifact-index.json.sig" >/dev/null
jq -r .signature "$ASSET_ROOT/virtualization-artifact-index.json.sig" |
base64 --decode >"$ASSET_ROOT/virtualization-artifact-index.raw.sig"
openssl pkeyutl -verify -pubin -rawin \
-inkey "$ASSET_ROOT/atlaso-release-2026-01.pem" \
-in "$ASSET_ROOT/virtualization-artifact-index.json" \
-sigfile "$ASSET_ROOT/virtualization-artifact-index.raw.sig"
while IFS=$'\t' read -r digest size name; do
test "$(stat --format='%s' "$ASSET_ROOT/$name")" -eq "$size"
printf '%s %s\n' "$digest" "$name"
done < <(jq -r '.assets[] | [.sha256, .size, .name] | @tsv' \
"$ASSET_ROOT/virtualization-artifact-index.json") |
(cd "$ASSET_ROOT" && sha256sum --check --strict)
rm -- "$ASSET_ROOT/virtualization-artifact-index.raw.sig"
Run the verification from a trusted administrative workstation with OpenSSL 3, jq, GNU coreutils, and GitHub CLI.
The standard tools authenticate the signed index before trusting its asset records; no downloaded verifier executes
before that trust boundary. Keep the verified OVA immutable and available until deployment validation succeeds. Do not
import any asset if key fingerprint, signature, version, size, or asset hash verification fails.
Every helper runs validate_ova.py before changing hypervisor state. Validation requires the manifest, source commit,
version, payload hashes and roles, fixed capacities, and complete machine topology to agree. An unexpected archive
member, symbolic link, unsafe path, missing provenance record, or changed payload blocks the import.
Deploy on VMware¶
Import the OVA directly in VMware Workstation or another compatible VMware OVF deployment environment. Map the first NIC to the management network and the second NIC to the services network. Do not change the disk controller, slots, or capacities.
VMware deployment continues through OVF-property customization. The first-boot selector retains open-vm-tools,
discards the unused offline QEMU and Hyper-V RPM payloads, and records success before Atlaso services start.
Supply a unique FQDN, Atlaso administrator password, and root password in the OVF deployment properties. The release
image contains no usable build or deployment credential and no reusable SSH host key.
Packer schedules final build-account removal in a detached root-owned unit so the SSH communicator exits first. That
unit verifies the build account, home directory, passwordless sudo authorization, and build-only helper are absent;
any failed verification leaves the VM powered on and blocks export.
Import on Proxmox VE¶
The Proxmox VE node needs qm, pvesm, qemu-img, jq, Python 3, and access to the selected storage and bridges. Copy
the OVA, import-atlaso-proxmox.sh, and validate_ova.py into one ordinary directory, then run:
chmod 0755 import-atlaso-proxmox.sh
./import-atlaso-proxmox.sh atlaso-vX.Y.Z.ova 240 local-lvm vmbr0 vmbr1
Arguments are the OVA, an unused VM ID, destination storage, management bridge, and optional services bridge. The
helper uses the platform OVF/OVA importer, then enforces q35, OVMF without pre-enrolled Secure Boot keys, four CPUs,
4096 MiB RAM, one shared virtio SCSI controller, two NICs, QEMU guest-agent support, boot from SCSI slot 0, and the
four-disk contract. It
extracts the manifest-verified OVF member into a private temporary directory for qm importovf; the downloaded OVA is
never rewritten.
Do not reuse a VM ID or pre-create matching destination volumes. A failed invocation destroys only the VM ID and unreferenced storage that it created after all preflight checks passed.
Import on KVM with virt-v2v¶
The KVM host needs virt-v2v, virsh, qemu-img, jq, Python 3, q35-compatible OVMF firmware, one active storage
pool, and two existing libvirt networks. Copy the OVA, import-atlaso-kvm.sh, validate_ova.py, and
normalize_libvirt.py into one ordinary directory, then run:
chmod 0755 import-atlaso-kvm.sh
sudo ./import-atlaso-kvm.sh atlaso-vX.Y.Z.ova atlaso default atlaso-management atlaso-services
Arguments are the OVA, an unused domain name, active storage pool, management network, and optional services network.
The helper imports with virt-v2v -i ova, creates only missing 500 GiB data volumes, verifies the resulting disk order
and capacities, and normalizes the inactive libvirt definition. The resulting domain remains shut off for inspection.
The helper serializes each pool/domain namespace and rejects an existing domain or storage volume with the exact
literal <domain-name>- prefix. Dots remain valid in domain names and do not match other characters when storage
ownership is checked.
Rollback removes only the domain and matching volumes created after that locked preflight, including partial
virt-v2v volumes left before a domain definition exists.
Import on Hyper-V¶
Download and extract atlaso-v<version>-hyperv-x86_64.zip into a new ordinary directory on the Hyper-V host. Open an
elevated PowerShell 7.4 or newer (pwsh) session and run:
pwsh -File .\Import-Atlaso.ps1 `
-Name Atlaso `
-ManagementSwitch 'Management' `
-ServiceSwitch 'Services' `
-DestinationRoot 'D:\Hyper-V\Atlaso' `
-Start
The importer verifies its manifest and checksums before creating state. It copies four dynamic VHDX disks into a new destination, creates a Generation 2 VM with Secure Boot off, attaches two selected switches and the four ordered SCSI disks, and applies the shared CPU and memory contract. Existing VM names, destinations, unsafe paths, malformed manifests, checksum failures, and conflicting topology are rejected. Failure cleanup removes only resources recorded as created by that invocation.
Choose a short DestinationRoot: the importer creates <DestinationRoot>\<Name> once and passes its parent to
New-VM, which appends the name itself. Before creating directories or copying disks, admission checks disk paths,
UUID-based configuration/state files under Virtual Machines, snapshot paths, and a 64-character provider filename
reserve for Smart Paging against a conservative 240-character full-path budget. This is an Atlaso safety budget,
not a promise that Windows long-path support changes Hyper-V limits. A Windows host reproduction accepted a
190-character VM directory but rejected 195 characters with Smart Paging error 0x800700CE; configuration files added
another 59 characters. Shorten the selected destination or VM name when admission reports an over-budget path.
Hyper-V smoke uses <OutputRoot>\.hv-<128-bit compact identifier>\p for extraction and places the imported VM beside
p. Its preflight checks ZIP member paths and reserves the larger provider layout used by older ZIP importers before
extracting anything. OutputRoot must remain beneath the checkout's artifacts\virtualization-smoke directory. It
never moves retained operations, falls back to an OS temporary directory, or changes host policy to fit a path.
An import failure retains the original exception together with any importer and smoke cleanup diagnostics. If no exact
created VM identity was returned, files remain for investigation even when a later inventory contains no matching VM.
Existing root, descendant, reparse-point, and exact-VM cleanup checks remain mandatory.
Hyper-V conversion and ZIP size¶
The Hyper-V ZIP artifact is built from the validated OVA payload and keeps raw payload-to-VHDX ordering, two dynamic
payload VHDX files, 2 MiB VHDX blocks, two 500 GiB data disks, and the same four-slot SCSI topology as the OVA.
The final archive creation uses .NET System.IO.Compression.ZipArchive in streaming create mode and retains ZIP64 support.
This avoids buffering multi-GiB disk members in memory.
Raw VHDX file length can exceed 2 GiB and still be valid. The release asset limit applies to the final ZIP:
- empty VHDX members are rejected before disk inspection and archive creation;
- a non-empty final Hyper-V ZIP of 2,147,483,648 bytes or more is rejected as an oversized publish candidate.
Export and protected publication also cap the combined uncompressed package at 8 GiB to bound extraction space. Protected validation retains the separate 1 MiB metadata-member limit, exact package inventory, checksums, source binding, disk topology, and guest-visible byte comparisons. A raw disk crossing 2 GiB alone does not fail publication.
When a check fails, keep the completed source template powered off:
- if a raw VHDX is empty, investigate the converter output and revalidate the source OVA before retrying;
- if only the final ZIP is oversized, review candidate evidence and rebuild from a validated source after reducing content where supported by policy.
For diagnostics, capture and retain:
- reported converter version, dynamic subformat, and block size;
- reported virtual capacity and raw byte length for each VHDX;
- ZIP member
uncompressed_bytesandcompressed_bytes; - final archive byte size.
At final import time or release troubleshooting, use PowerShell 7.4+ (pwsh) and Expand-Archive for zip inspection;
raw extraction and import paths may need more temporary space than the downloaded ZIP size.
First boot guest-agent selection¶
Atlaso retains its checksum-pinned QEMU guest-agent build. Photon 5.0's published qemu build disables the guest
agent, so installing that emulator package does not supply the required qemu-ga daemon. Atlaso builds only the agent
RPM from the pinned upstream source and stages its complete Photon dependency closure; it does not add the full
emulator to the appliance. See the Photon QEMU specification
and the checked-in image/common/guest-agents build inputs.
The image carries locked offline RPM closures under /var/lib/atlaso/first-boot-packages. A provider-neutral service
runs before data-disk initialization, networking handoff, nginx, Atlaso, and the worker. It does not use a network
repository. Both the canonical VMware producer and its derived Hyper-V artifact therefore inherit the same shared
provisioning checks: the physical bootstrap release must match both compatibility links, and QEMU's offline-agent build
uses a private root-owned HOME/cache sandbox rather than the Packer communicator user's home.
- VMware retains and enables
open-vm-tools. - KVM, QEMU, and Proxmox remove VMware Tools, install the verified local QEMU guest-agent closure, and enable its service.
- Hyper-V removes VMware Tools, installs the verified local Photon Hyper-V closure, and requires its KVP and VSS
daemons. Atlaso disables the packaged legacy
hv_fcopy_daemon: its/dev/vmbus/hv_fcopytransport is absent from the supported Linux 6.12 kernel. Hyper-V host-to-guest file copy is not supported by this artifact; enabling Guest Service Interface in the host does not restore that obsolete transport. File-copy availability must not block machine identity initialization or management DHCP. - Bare metal removes VMware Tools and all virtual guest-agent payloads, then continues without an agent.
- Unknown or contradictory platform evidence blocks appliance startup for diagnosis.
The bare-metal selector branch applies only when the canonical portable four-disk set is attached with its declared SCSI layout; it uses the explicit system-content, Depot, and Backups identities at slots 1, 2, and 3. It does not select, partition, or adopt arbitrary physical disks. The separate offline installation ISO tracked in #542 owns interactive target selection and the physical-hardware disk-safety contract.
Successful selection proves that only the expected agent is installed, then securely removes the RPM staging tree,
checksum manifest, package-manager cache, and runtime scratch directory. Failure leaves the verified persistent RPM
closure available for automatic retry while the Atlaso front door and application remain stopped.
Production cleanup is fixed to the Atlaso staging, runtime, marker, and TDNF cache paths. The selector rejects path
overrides unless an isolated test invocation also supplies one ordinary mode-0700 test root owned by the expected
test identity; every overridden cleanup target must be a strict, non-overlapping descendant with canonical,
non-symlink ancestry, no mount anywhere in the explicit test root, and no multiply linked regular file. The selector
revalidates those boundaries, ownership, and
permissions immediately before every cleanup attempt, including a retry after the durable success marker exists.
Test-override cleanup pins the validated isolated-root identity, opens every target ancestor relative to that descriptor,
and atomically renames staging, runtime, and package-cache artifacts to randomized siblings before recreating an empty
cache. It never recursively traverses or erases same-identity paths, so a root or ancestor replacement cannot redirect
cleanup and a mount added after validation fails closed; the test harness owns eventual cleanup of retained artifacts.
Production retains secure erasure on its fixed paths.
The selector's success marker is the only first-boot transaction commit. It is stored under the root-only
/var/lib/atlaso-privileged/guest-agent boundary, whose ownership and mode are revalidated before the marker is trusted.
An interruption before that commit reruns
identity initialization, generates a fresh credential set, and republishes its matching one-time envelope before
networking. The retry never exposes a password whose corresponding host state was replaced.
VMware continues into OVF-property customization. Hyper-V, KVM, and Proxmox use DHCP-first defaults and do not wait for VMware metadata. Use the appliance console to complete initial networking when DHCP is unavailable.
Management DHCP configuration retains Photon's SendRelease=no policy through image provisioning, OVF customization,
and Network Apply. Reboot therefore does not explicitly release the management lease, which can cause Hyper-V's
Default Switch to assign a different address despite an unchanged MAC and DHCP client identity. DHCP remains subject
to server policy and lease expiry; use a reservation or static address when permanent address stability is required.
Smoke validation still rejects an address change across reboot. Existing exported images require a rebuild to receive
the provisioning setting.
DHCP-first images admit management SSH and HTTP/HTTPS over IPv4 only through the deployed management interface
(eth0). IPv6 management admission requires explicit deployment configuration.
The temporary VMware builder subnet is not retained as a source restriction. An explicitly supplied
ATLASO_MGMT_SOURCE_CIDR remains an additional source restriction on that interface; static builds otherwise derive
it from the final configured management network. Services interfaces remain outside this management admission.
Fresh appliance initialization retains these
source and address-family restrictions in the Bootstrap management Source Group assigned to management admission,
including physical and VLAN interfaces flagged for management UI, so normal Firewall Apply preserves them.
Editing a flagged listener's Source Group overrides that listener only; other listeners retain their shared default.
Console IPv6 correction uses the shared Network Objects transaction lock before reading desired state, preserving
concurrent operator saves. It updates the address family of an untouched bootstrap group before Apply; an operator-saved
Source Group remains authoritative. Operators can subsequently edit that Source Group or its assignment;
assignment-only saves and edits to other groups preserve the bootstrap group's console-recovery eligibility.
startup does not replace saved choices or migrate existing appliances.
When validating a portable image, use a management subnet different from the builder's and verify SSH plus
/openapi.json both on initial boot and after reboot. Existing exported images need a rebuilt artifact to receive
this provisioning correction; changing an appliance software version alone does not replace their initial firewall.
Before networking, every cloned appliance generates a new machine ID, OpenSSH host-key set, application secrets, and
high-entropy administrator and root passwords. VMware replaces the generated passwords with its required OVF values
and publishes the regenerated Ed25519 public host key through VMware guest-info for authenticated automation. KVM and
Proxmox expose a root-only one-time envelope on tmpfs at /run/atlaso/first-boot-access.json, readable through the QEMU
guest agent or from the local console. Hyper-V publishes the same envelope under KVP key atlaso.first_boot_access.
The host reads that guest-authored record from Msvm_KvpExchangeComponent.GuestExchangeItems for the exact VM ID;
GuestIntrinsicExchangeItems contains OS/provider metadata, not the Atlaso envelope.
The local console keeps the envelope on a dedicated first-time initialization screen until an operator presses Enter
to acknowledge that every value was recorded; acknowledgement removes only the console's tmpfs copy. Retrieve the
envelope only from the authenticated hypervisor control plane or the physically controlled console, pin its SSH host
key before connecting, and rotate both passwords immediately. The first reboot removes any remaining runtime file and
Atlaso's Hyper-V KVP record while preserving unrelated KVP data.
Signed appliance updates preserve the disk policy already proven for the installed generation. Portable artifacts use the shared four-disk policy recorded by the verified first-boot provider marker. Older VMware appliances retain their existing four-disk controller identities, while older three-disk Hyper-V appliances retain their Depot and Backups slots and are never reinterpreted as the new four-disk layout. These compatibility policies support updates only; the retired Hyper-V template-build and lifecycle environment is not restored.
The protected smoke jobs retrieve each booted VM's regenerated Ed25519 public host key and unique credential through its authenticated hypervisor metadata channel before SSH authentication. They reject an unknown or changed host and never use trust-on-first-use host-key acceptance. Artifact provenance intentionally contains no reusable host identity.
Protected virtualization admission, finalization, and publication jobs configure Python without requesting the
built-in setup-python pip cache. Their read-only or otherwise narrowly scoped Actions permissions are not widened for
cache saving; exact hash-locked release-tool installation and every signing, attestation, immutable-asset, and isolated
smoke boundary remain authoritative.
Each smoke also captures both provider-side NIC identities before probing the guest. Hyper-V binds the address to the
named Management adapter and its exact switch, KVM and Proxmox match QEMU guest-agent interface data to the
management MAC from the ordered provider topology, and VMware resolves ethernet0 only through its mapped management
vmnet and exact MAC. On a clean Windows runner, the VMware smoke uses the exact management MAC to obtain DHCP lease
candidates and sends an interface-scoped probe to populate neighbor evidence; a lease is never accepted without the
matching management-vmnet neighbor entry. Services-first enumeration cannot choose the probe target. Missing, duplicate,
mismatched, or
changing management MAC/address evidence fails the smoke run before the SSH or /openapi.json result is accepted, and
the same binding is revalidated after reboot.
Hyper-V smoke imports without starting the VM so adapter IDs and switch bindings are captured before power-on. During initial acquisition only, an all-zero MAC on an explicitly dynamic adapter is pending allocation. The smoke requires that adapter to remain explicitly dynamic when its MAC is assigned, pins each nonzero MAC as soon as it appears, and requires both MACs plus management IPv4 before probing. It never rebases an assigned MAC, adapter ID, or switch; subsequent changes remain fatal, including after reboot. MAC allocation and pinned IPv4 mismatch errors report expected and observed values and both current adapter IDs for diagnosis. Zero-MAC rejection also reports whether pending acquisition was permitted and distinguishes static from missing dynamic mode evidence. If a service-MAC failure recurs, retain that evidence to distinguish allocation from adapter replacement or a later MAC change. Dynamic-mode rejections also identify the expected and observed MAC, and distinguish a reported static mode from missing mode evidence. Temporary loss of the provider's IPv4 report does not clear an already pinned address. The smoke waits for a fresh report and rejects a different address, including across reboot.
VMware SSH admission requires a currently Reachable management neighbor. Cached Stale, Delay, and Probe
entries are probe candidates only; expired, malformed, or unbounded DHCP leases are excluded. Each failed SSH
transport attempt returns to the Windows wrapper for another identity check under the same 15-minute phase deadline.
The wrapper handles the child retry status explicitly even when PowerShell native-error promotion is enabled, and
preserves the caller's preference; other nonzero child results remain terminal.
Unanswered ICMP probes also preserve that preference and continue to refreshed neighbor evaluation; ICMP success
alone never admits an address.
Loss of MAC-bound address ownership receives at most a 20-second neighbor refresh window before an explicit identity
failure. A changed address is never silently substituted, including during initial boot. Correct the reported network
condition and start a new disposable smoke attempt. Host-key or authentication rejection is terminal.
Progress identifies the provider, initial or post-reboot phase, VMX, management MAC/vmnet/host interface, target and
elapsed/remaining SSH wait. Child progress uses standard error so the initial TLS fingerprint remains the sole standard
output result. Transfer Completed from OVF Tool proves only import completion; guest disk/service/OpenAPI checks and
post-reboot validation must still pass. The original stale-address report did not establish what moved the guest address;
the fixed-host retry loop and admission of cached neighbor state are independently reproducible from the smoke helpers.
Validate and recover¶
After first boot, verify all of the following before adopting the VM:
- the expected guest agent is installed and active and foreign agents are absent;
/var/lib/atlaso/first-boot-packagesno longer exists;- both NICs and four ordered disks are present;
- the Depot and Backups data disks are mounted at their documented paths; and
https://<management-address>/openapi.jsonreturns the Atlaso API document.
Reboot the VM and repeat the readiness and disk checks. An assigned address or a running VM alone is not application readiness.
If guest-agent selection fails, inspect its service status and journal from the console. Preserve the RPM staging tree, correct only the reported image or platform conflict, and restart the selector. Do not manually enable Atlaso or nginx while the selector is failed. For an import-time failure, keep the original release assets, remove only the target VM and storage owned by that import attempt, correct the host prerequisite, and run the helper again.
A Hyper-V management-address timeout does not by itself prove a DHCP-server problem. Check
journalctl -u atlaso-guest-agent-select.service -u hv_fcopy_daemon.service: older artifacts can stop before networking
because the legacy file-copy daemon reports open /dev/vmbus/hv_fcopy failed. Rebuild with the corrected selector;
do not disable the KVP/VSS readiness checks, admit another adapter's address, or boot a completed source template to
repair it. Both initial and post-reboot SSH, disk, service, and OpenAPI checks remain required.
Protected release runners¶
The primary Windows producer is a maintainer workstation, not a permanent GitHub runner. From a clean attached branch or detached checkout at the successful software-release SHA, run:
python -m pip install --require-hashes --requirement requirements-virtualization-smoke.lock
./scripts/windows/vmware/export-ovf.ps1 -Prerelease
The command verifies and extracts the exact published software bundle, installs it while building the canonical
VMware template, exports that checked powered-off template directly, derives
Hyper-V from that OVA, runs both Windows smokes, creates the annotated tag before the draft Release, uploads without
clobbering, and waits for the exact newly dispatched hosted-finalizer run to succeed before verifying publication.
The intermediate VMware builder uses a deterministic version-and-source-commit identity and records that identity in
its output manifest and schema-v3 provenance. The exporter consumes that exact proven VMX while retaining the canonical
atlaso-vX.Y.Z OVA/product and release filenames; release output never inherits a pull-request number.
Its credential bridge, sensitive source snapshot, Packer workspace, cleanup state, and builder-address release handoff
remain beneath the exact checkout-local .atlaso-local/photon-image-build-state root. Snapshot admission preserves
and revalidates whether that exact source checkout was attached to its original branch or detached at the release SHA.
The non-task-owned address
allocation lock and ledger remain per-user and host-shared so parallel worktrees cannot choose the same address; no
task-owned output or sensitive state falls back to a Windows profile, temporary directory, or LocalApplicationData.
Use -CandidateOnly to stop after candidate production and smoke; otherwise the command continues through tag creation,
draft upload, and protected hosted finalization. The standard
producer defaults StagingRoot to <checkout>\artifacts\virtualization-release, uses the exact Hyper-V switches
Atlaso Management and Atlaso Services, resolves the pinned Atlaso 1Password Environment through the checkout-local
selector file, prefers the checkout-local current-user DPAPI service-account token before desktop discovery, and
discovers a standard Windows x64 CPython 3.14 runtime. The retained -StagingRoot, -ManagementSwitch,
-ServiceSwitch, -OnePasswordEnvironmentId, -OnePasswordServiceAccountTokenFile, -OnePasswordAccount, and
-OnePasswordPython parameters remain authoritative overrides.
The resolved credential selectors are forwarded unchanged to the fresh image build. The producer performs no
post-build address discovery or SSH wheel deployment on the source template.
The image-builder handoff uses named parameters and deliberately leaves both SecureString credential parameters
unbound so the reviewed 1Password defaults remain authoritative.
For production-path acceptance without creating a virtualization tag or Release, use the matching signed software release from its clean source checkout:
Before merge, validate development construction and deployment using PR-owned test VMs. Protected candidate production
still requires the matching successful main software release; do not weaken that binding to test a feature branch.
Both Windows smokes import separate disposable VMware and Hyper-V VMs, verify offline provider initialization,
unique identity, cleanup, services and host-facing /openapi.json, then reboot and recheck persistent readiness.
The source stays powered off and its VMX and payload hashes must remain unchanged after export and smoke completion.
The exporter returns the validated descriptor's package directory to the producer, including when OVF Tool creates
a child named after the release builder. The producer copies the exact OVA beside the descriptor, manifest,
provenance, and payload VMDKs and passes that directory to candidate staging; staging still validates only that
directory's immediate assets and requires the complete, hash-bound package.
These Windows checks establish candidate acceptance. Actual KVM and Proxmox tests remain mandatory for stable promotion.
New builders live directly at <StagingRoot>\<rc-tag>\<builder-name>\<builder-name>.vmx; the redundant vmware-build
directory is omitted. Canonical names, ownership manifests, output claims, and provenance are unchanged. A retained
legacy output, sibling manifest, or claim keeps its original vmware-build layout. If both layouts contain state,
preflight stops for explicit recovery; it never moves, deletes, or adopts that state automatically.
Before credential selection or release downloads, new build preflight checks generated VMX, sidecar, disk, UUID memory,
and lock paths against a conservative 240-character budget. VMware Workstation can fail creating lock directories even
with Windows long-path support enabled. The error identifies the generated path and recommends a shorter absolute
-StagingRoot; direct Photon builds recommend a shorter -OutputDirectory parent while retaining the canonical
builder leaf. Existing candidate verification and publication retries do not require a new builder path. Preserve
retained operations when selecting a different staging root; do not manually delete locks or rename owned builders.
Preflight prints the synchronized version and selected tag, staging root, switch names, and selector-source labels
before creating staging directories or starting build activity. It never prints Environment IDs, account values,
credentials, credential material, or secret-bearing paths. If the resolved staging root contains exactly one valid
current-version rc.N operation, the producer resumes it after validating any corresponding remote tag and Release.
With no retained operation it inventories remote tags and all GitHub Releases, deduplicates canonical ordinals, and
selects rc.1 or one greater than the maximum. Multiple retained operations fail closed as ambiguous. The selection is
frozen for the invocation: a later collision never advances automatically and remains subject to non-force tag
creation, --verify-tag, no-clobber upload, exact-commit, and byte-idempotency guards.
Keep StagingRoot until stable verification; cleanup is a separate explicit operator action. Every retry reconstructs
and byte-validates the complete cached software source against freshly downloaded signed Release assets. A complete
retained candidate is independently
revalidated and reused byte-for-byte; only an absent candidate enters the image-build, OVA-export, and Hyper-V
conversion path. Pre-verification network downloads are invocation-temporary and never reused after interruption.
Repeat the same -Prerelease -CandidateOnly command to reuse an exact valid candidate, or omit -CandidateOnly only
when publication is intended. If verification rejects retained software, template, or candidate state, preserve that
operation for diagnosis and rebuild under a new explicitly selected owned staging root. A running source, unprovable
power state, consumed initialization marker, or incompatible contract cannot be repaired by starting or SSH-deploying
to the template. See the
Photon image guide
for the explicit verified software input and construction checks.
Before signing, the hosted finalizer requires the OVA provenance's software tag, manifest, bundle, application-wheel, and
Python-ABI fields to exactly match the verified software-source sidecar. A retry after only one signed-index file was
uploaded reconstructs the deterministic pair, verifies the retained byte without clobbering it, and uploads only the
missing counterpart.
Stable promotion never rebuilds:
./scripts/windows/vmware/export-ovf.ps1 -Release `
-FromPrerelease virtualization-vX.Y.Z-rc.1 `
-ProxmoxRunnerLabel atlaso-proxmox-virtualization-vX-Y-Z-rc-1 `
-KvmRunnerLabel atlaso-kvm-virtualization-vX-Y-Z-rc-1
Bring the uniquely labelled Proxmox and KVM --ephemeral runners online only for that approved promotion, then destroy
or sanitize them after the job. They receive read-only Actions/contents permissions, no signing secret, and no
write-capable token. Define the repository variables named by .github/workflows/virtualization-stable.yml for storage
and networks. The workstation waits for the exact promotion run it dispatched, rather than accepting an older stable
Release with the same version. Stable promotions are serialized repository-wide so two release candidates cannot race
one immutable stable tag. Signing and Release writes occur only in the protected GitHub-hosted finalizer. If stable
publication completed but its final live verification did not, an exact retry detects the published stable Release
during admission and verifies its signed index, source binding, attestations, and byte identity with the selected
prerelease directly on GitHub-hosted Linux. It does not schedule Proxmox or KVM again, rebuild the signed index, or
modify the immutable Release.
If an unpublished stable draft already contains both signed-index assets, the protected finalizer validates their
signature, release identity, source binding, and complete asset set, then resumes with those exact retained bytes even
when current main would render a different index. A draft containing only one index asset fails closed. After
confirming that the draft is unpublished and the retained file is the sole incomplete index asset, a maintainer may
rerun Promote stable virtualization release with recover_incomplete_index enabled; only that explicit protected
recovery deletes the incomplete file and reconstructs the pair. Ordinary retries never delete or replace draft assets.
For the optional ephemeral-Windows workflow, also define ATLASO_ONEPASSWORD_ENVIRONMENT_ID,
ATLASO_ONEPASSWORD_ACCOUNT, and ATLASO_ONEPASSWORD_PYTHON as repository variables. They are non-secret selectors;
the disposable runner must still complete its local 1Password authorization and receives no signing key.
The workstation requires PowerShell 7.4 or newer, VMware Workstation, Packer, OVF Tool, Hyper-V, qemu-img, and two
operator-owned virtual switches. The Proxmox and KVM runners require the host tools listed in their import sections.
The optional Windows producer workflow performs the same CPython 3.14 smoke-runtime installation before candidate
production. The generated, seven-day, hash-verified smoke lock is separate from
requirements-onepassword-deploy.lock: the latter belongs to the bounded standard Windows x64 CPython 3.14 1Password
credential bridge and includes SDK dependencies the smoke helper neither imports nor needs.
Every smoke identity and storage
namespace is invocation-scoped: VMware generates a disposable per-run password, Proxmox serializes each VMID import,
passes it to OVF Tool through a runner-only temporary configuration file instead of process arguments, and deletes that
file immediately after import. Proxmox serializes each VMID import, and cleanup failures fail the active smoke job
instead of allowing publication with retained provider state. VMware cleanup binds the root, VMX, provider aliases,
and every remaining descendant to captured Windows file identities; Proxmox and KVM require successful inventories to
prove absence. KVM serializes the global domain name and its selected pool namespace independently; Proxmox importer
rollback requires a final inventory proving its fixed VMID absent. Hyper-V
cleanup removes only a VM whose exact ID was captured after successful import; an indeterminate import preserves files
for diagnosis rather than claiming a later name match. KVM rollback preserves every imported volume unless a successful
libvirt inventory proves that the exact domain is absent. Every smoke identity and storage namespace must be dedicated to
the release invocation so cleanup can remain limited to resources created by that invocation. Stable publication waits
for both Linux platform smokes and refuses an asset at or above the repository's existing 2 GiB limit rather than
producing multipart output. Raw VHDX members are validated separately for format and capacity before the final ZIP
size check.
As an optional alternative, Produce virtualization candidate on ephemeral Windows runs the same producer with
-CandidateOnly on a temporary Windows runner whose exact release-specific label is
atlaso-windows-virtualization-vX-Y-Z-rc-N. The Windows job has read-only repository authority
and no signing material. A GitHub-hosted job alone creates the annotated tag and draft, then calls the same protected
hosted prerelease finalizer. Keep that runner offline except for an approved default-branch dispatch and destroy or
sanitize it after its single --ephemeral job. If a retry finds a complete existing draft, hosted admission validates
its exact candidate asset inventory and routes it directly back to protected finalization; it never schedules a fresh
ephemeral Windows build whose timestamp-bearing bytes could conflict with the retained draft. An incomplete or
unexpected draft fails closed for explicit operator recovery.
The disposable VMware smoke import explicitly binds its adapters to the selected existing VMnets. Cleanup uses the shared Workstation inventory and running-VM checks before removing only the owned import. If initial network-identity capture fails, cleanup retains the last verified VMX identity to stop the owned import. After shutdown, cleanup verifies the captured directory inventory and VMX contents before accepting a replacement VMX; only shutdown-status flags may change. Unexpected replacements or added files are preserved and fail cleanup. The protected read-only verifier checks offline-package names, entry types, sizes, and the 256-entry limit (including directories) before exporting the guest-tool tree, then rechecks the archive inventory. The local smoke import supplies its OVF environment through the shared Workstation serializer, with credentials confined to the protected disposable VM directory until first-boot cleanup. The smoke check allows 21 minutes after SSH becomes available for the 20-minute storage initialization window plus application readiness, with a 22-minute outer SSH-command timeout; a readiness timeout reports service states and still fails acceptance.