Add support for Ubuntu containers and WSL verification

- Implemented tasks for managing Ubuntu containers using Podman, including inspection, creation, starting, and SSH setup.
- Added verification tasks for WSL and Podman installation, machine existence, and NSSM service status.
- Created PowerShell scripts for Podman auto-start on Windows boot.
- Introduced Prometheus role with default configurations and installation tasks.
- Updated Windows SSH role to improve authorized_keys management and permissions handling.
- Modified inventory to include Windows containers host.
- Updated Grafana datasource configurations with direct IP addresses for Prometheus instances.
This commit is contained in:
2026-06-07 20:35:59 +02:00
parent 3775fa4b6e
commit 25d3eafa5b
34 changed files with 1475 additions and 155 deletions
@@ -0,0 +1,41 @@
---
# ── Mirantis Container Runtime (Windows Server) defaults ──────────────────
windows_containers_mcr_install_script_url: "https://get.mirantis.com/install.ps1"
windows_containers_mcr_channel: "stable"
windows_containers_mcr_version: ""
windows_containers_disable_telemetry: false
# --- Podman defaults (container behavior) ---
windows_containers_ubuntu_version: "latest"
windows_containers_install_dir: "C:\\Program Files\\RedHat\\Podman"
windows_containers_installer_url: "https://github.com/podman-container-tools/podman/releases/download/v5.8.2/podman-installer-windows-amd64.exe"
windows_containers_download_retries: 5
windows_containers_download_delay: 10
windows_containers_download_timeout: 120
windows_containers_machine_name: "podman-machine-default"
windows_containers_image: "ubuntu:{{ windows_containers_ubuntu_version }}"
windows_containers_container_name: "ubuntu-container"
windows_containers_container_port: "2222"
windows_containers_ssh_enabled: false
windows_containers_ssh_users:
- user: wsluser
key: ""
# --- WSL2 bootstrap defaults ---
windows_containers_wsl_enabled: true
windows_containers_wsl_features:
- Microsoft-Windows-Subsystem-Linux
- VirtualMachinePlatform
windows_containers_wsl_reboot: true
windows_containers_wsl_default_version: 2
windows_containers_wsl_install_no_distribution: true
# --- Dedicated service account ---
windows_containers_service_user: podman
windows_containers_service_password: ""
windows_containers_service_password_never_expires: true
windows_containers_machine_cpus: 2
windows_containers_machine_memory: 2048
windows_containers_machine_disk_size: 10000
@@ -0,0 +1,12 @@
---
- name: Restart podman machine
ansible.windows.win_shell: podman machine stop; podman machine start
failed_when: false
tags:
- roles::windows_containers
- name: Restart Ubuntu container
ansible.windows.win_shell: podman restart {{ windows_containers_container_name }}
failed_when: false
tags:
- roles::windows_containers
@@ -0,0 +1,15 @@
---
galaxy_info:
author: Alexandre Pires
description: Install Mirantis Container Runtime on Windows Server and Podman on Windows Workstation
company: A13Labs
role_name: windows_containers
namespace: a13labs
license: MIT
min_ansible_version: "2.1"
platforms:
- name: Windows
categories: []
dependencies:
- role: nssm
@@ -0,0 +1,106 @@
---
# ── Phase 0: WSL2 prerequisite bootstrap ─────────────────────────────────
- name: Enable Microsoft-Windows-Subsystem-Linux feature
ansible.windows.win_feature:
name: Microsoft-Windows-Subsystem-Linux
state: present
include_sub_features: true
include_management_tools: true
register: __wsl_feature
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::wsl_bootstrap
- name: Enable Virtual Machine Platform feature
ansible.windows.win_feature:
name: VirtualMachinePlatform
state: present
include_sub_features: true
include_management_tools: true
register: __vmp_feature
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::wsl_bootstrap
when: ansible_facts.os_product_type == "workstation"
- name: Enable Hyper-V feature
ansible.windows.win_feature:
name: Hyper-V
state: present
include_sub_features: true
include_management_tools: true
register: __hyperv_feature
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::wsl_bootstrap
when: ansible_facts.os_product_type == "server"
- name: Enable Containers feature
ansible.windows.win_feature:
name: Containers
state: present
include_sub_features: true
include_management_tools: true
register: __containers_feature
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::wsl_bootstrap
when: ansible_facts.os_product_type == "server"
- name: Reboot if WSL features were changed
ansible.windows.win_reboot:
reboot_timeout: 600
pre_reboot_delay: 5
post_reboot_delay: 15
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- windows_containers_wsl_reboot | default(true)
- __wsl_feature.changed or __vmp_feature.changed or __hyperv_feature.changed or __containers_feature.changed
tags:
- roles::windows_containers::wsl_bootstrap
- name: Download WSL2 kernel update package
ansible.windows.win_get_url:
url: "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi"
dest: "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\wsl_update_x64.msi"
force: false
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::wsl_bootstrap
- name: Install WSL2 kernel update package
ansible.windows.win_shell: |
Start-Process "msiexec.exe" -ArgumentList "/i {{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\wsl_update_x64.msi /quiet" -NoNewWindow -Wait
exit $LASTEXITCODE
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __wsl_update_result
changed_when: __wsl_update_result.rc == 0
tags:
- roles::windows_containers::wsl_bootstrap
- name: Run wsl update to update WSL2 kernel
ansible.windows.win_shell: |
wsl --update
exit $LASTEXITCODE
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __wsl_set_default_result
changed_when: __wsl_set_default_result.rc == 0
tags:
- roles::windows_containers::wsl_bootstrap
@@ -0,0 +1,118 @@
---
# ── Phase 1: Mirantis Container Runtime (MCR) installation ────────────────
- name: Check if MCR (Docker) is already installed
ansible.windows.win_shell: |
$dockerCmd = Get-Command docker -ErrorAction SilentlyContinue
if ($dockerCmd) {
$version = (& docker --version 2>&1)
Write-Output "MCR_INSTALLED:$version"
exit 0
}
exit 1
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __mcr_installed_check
changed_when: false
failed_when: false
tags:
- roles::windows_containers::install
- name: Download Mirantis install script
ansible.windows.win_get_url:
url: "{{ windows_containers_mcr_install_script_url }}"
dest: "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\mcr-install.ps1"
force: false
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __mcr_installed_check.rc != 0
tags:
- roles::windows_containers::install
- name: Set execution policy for current process
ansible.windows.win_shell: |
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force -Scope Process
exit 0
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __mcr_installed_check.rc != 0
changed_when: false
tags:
- roles::windows_containers::install
- name: Install Mirantis Container Runtime
ansible.windows.win_shell: |
& "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\mcr-install.ps1" \
-Channel "{{ windows_containers_mcr_channel }}" \
-DockerVersion "{{ windows_containers_mcr_version }}"
exit $LASTEXITCODE
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __mcr_install_result
changed_when: __mcr_install_result.rc == 0
when: __mcr_installed_check.rc != 0
retries: 3
delay: 30
tags:
- roles::windows_containers::install
- name: Clean up MCR install script
ansible.windows.win_file:
path: "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\mcr-install.ps1"
state: absent
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __mcr_installed_check.rc != 0
tags:
- roles::windows_containers::install
- name: Disable MCR telemetry (optional)
ansible.windows.win_copy:
content: '{"features":{"telemetry": false}}'
dest: "C:\\ProgramData\\docker\\config\\daemon.json"
mode: "0644"
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
notify: Restart Docker service
when: windows_containers_disable_telemetry | default(false)
tags:
- roles::windows_containers::install
- name: Restart Docker service
ansible.windows.win_shell: |
Restart-Service -Name docker -Force -ErrorAction SilentlyContinue
exit 0
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __mcr_installed_check.rc != 0
changed_when: false
tags:
- roles::windows_containers::install
- name: Verify MCR (Docker) is running
ansible.windows.win_shell: |
$dockerdRunning = Get-Service -Name docker -ErrorAction SilentlyContinue
if ($dockerdRunning -and $dockerdRunning.Status -eq 'Running') {
$version = (& docker --version 2>&1)
Write-Output "MCR_SERVICE_RUNNING:$version"
exit 0
}
Write-Error "Docker service is not running"
exit 1
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __mcr_service_verify
changed_when: false
until: __mcr_service_verify.rc == 0
retries: 10
delay: 10
tags:
- roles::windows_containers::install
@@ -0,0 +1,37 @@
---
# ── Phase 1: WSL2 prerequisites ─────────────────────────────────────────
- name: Bootstrap WSL2 prerequisites
ansible.builtin.include_tasks: bootstrap_wsl.yml
tags:
- roles::windows_containers
- roles::windows_containers::wsl_bootstrap
# ── Phase 0: Docker Engine (Windows Server) ──────────────────────────────
- name: Install Docker Engine for Windows Server (Mirantis Container Runtime)
ansible.builtin.include_tasks: containers_install.yml
tags:
- roles::windows_containers
- roles::windows_containers::install
# ── Phase 2: Podman install, user, NSSM service ─────────────────────────
- name: Install Podman
ansible.builtin.include_tasks: podman_install.yml
tags:
- roles::windows_containers
- roles::windows_containers::podman_install
# ── Phase 2: Ubuntu container ────────────────────────────────────────────
- name: Setup Ubuntu container
ansible.builtin.include_tasks: ubuntu_container.yml
tags:
- roles::windows_containers
- roles::windows_containers::ubuntu_container
when: ansible_facts.os_product_type == "workstation"
# ── Phase 3: Verification ────────────────────────────────────────────────
- name: Verify installation
ansible.builtin.include_tasks: verify.yml
tags:
- roles::windows_containers
- roles::windows_containers::verify
when: ansible_facts.os_product_type == "workstation"
@@ -0,0 +1,389 @@
---
# ── Phase 1: Podman installer download & install ─────────────────────────
- name: Check if Podman is already installed
ansible.windows.win_shell: |
if (Get-Command podman -ErrorAction SilentlyContinue) {
Write-Output "__PODMAN_INSTALLED__"
exit 0
}
exit 1
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __podman_installed_check
changed_when: false
failed_when: false
tags:
- roles::windows_containers::podman_install
- name: Download Podman installer on remote host
ansible.windows.win_get_url:
url: "{{ windows_containers_installer_url }}"
dest: "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\podman-installer.exe"
force: false
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __podman_installed_check.rc != 0
tags:
- roles::windows_containers::podman_install
- name: Install Podman silently
ansible.windows.win_shell: |
Start-Process "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\podman-installer.exe" -ArgumentList @("/S", "/D={{ windows_containers_install_dir }}") -Wait
exit $LASTEXITCODE
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __podman_install_result
changed_when: __podman_install_result.rc == 0
when: __podman_installed_check.rc != 0
tags:
- roles::windows_containers::podman_install
- name: Clean up Podman installer
ansible.windows.win_file:
path: "{{ ansible_env.TEMP | default('C:\\Windows\\Temp') }}\\podman-installer.exe"
state: absent
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __podman_installed_check.rc != 0
tags:
- roles::windows_containers::podman_install
- name: Add Podman to PATH if not already present
ansible.windows.win_path:
name: Path
elements:
- "{{ windows_containers_install_dir }}"
state: present
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __podman_installed_check.rc != 0
tags:
- roles::windows_containers::podman_install
# ── Phase 2: Dedicated service user ──────────────────────────────────────
- name: Fail when podman service password is missing
ansible.builtin.fail:
msg: >-
windows_containers_service_password must be provided to configure service account
.\\{{ windows_containers_service_user }} for NSSM.
when: windows_containers_service_password | length == 0
tags:
- roles::windows_containers::podman_install
- name: Create dedicated podman service user
ansible.windows.win_user:
name: "{{ windows_containers_service_user }}"
password: "{{ windows_containers_service_password }}"
account_disabled: false
password_never_expires: true
user_cannot_change_password: true
state: present
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- windows_containers_service_password | length > 0
no_log: true
tags:
- roles::windows_containers::podman_install
- name: Get localized Users group name via PowerShell
ansible.windows.win_powershell:
script: |
$sid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-545")
$ntAccount = $sid.Translate([System.Security.Principal.NTAccount])
# Handle both local and domain accounts - take the last part after backslash
$parts = $ntAccount.Value -split '\\'
$parts[-1]
register: __users_group_name
changed_when: false
tags:
- roles::windows_containers::podman_install
- name: Debug localized Users group name
ansible.builtin.debug:
msg: "Localized Users group name is '{{ __users_group_name.output | first }}'"
tags:
- roles::windows_containers::podman_install
- name: Add podman service user to Users group for interactive login
ansible.windows.win_group_membership:
name: "{{ __users_group_name.output | first }}"
members:
- ".\\{{ windows_containers_service_user }}"
state: present
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- windows_containers_service_password | length > 0
tags:
- roles::windows_containers::podman_install
- name: Grant "Log on as a service" right to podman service account
ansible.windows.win_user_right:
name: SeServiceLogonRight
users:
- ".\\{{ windows_containers_service_user }}"
action: add
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- windows_containers_service_password | length > 0
tags:
- roles::windows_containers::podman_install
- name: Grant "Log on locally" right to podman service account (required for Ansible become)
ansible.windows.win_user_right:
name: SeInteractiveLogonRight
users:
- ".\\{{ windows_containers_service_user }}"
action: add
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- windows_containers_service_password | length > 0
tags:
- roles::windows_containers::podman_install
- name: Grant "Log on as a batch job" right to podman service account
ansible.windows.win_user_right:
name: SeBatchLogonRight
users:
- ".\\{{ windows_containers_service_user }}"
action: add
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- windows_containers_service_password | length > 0
tags:
- roles::windows_containers::podman_install
# ── Phase 3: Podman machine setup ──────────────────────────────────────
- name: Get Podman executable path
ansible.windows.win_shell: where.exe podman.exe 2>&1
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __podman_where
changed_when: false
failed_when: false
tags:
- roles::windows_containers::podman_install
- name: Set Podman executable path from where output
ansible.builtin.set_fact:
__podman_exe: "{{ (__podman_where.stdout_lines | last).strip() }}"
when:
- __podman_where is not failed
- __podman_where.stdout | trim != ""
- name: Default Podman executable path if not on PATH or where failed
ansible.builtin.set_fact:
__podman_exe: "{{ windows_containers_install_dir }}\\podman.exe"
when: __podman_where is failed or __podman_where.stdout | trim == ""
# ── Phase 4: Podman machine init ────────────────────────────────────────
- name: Initialize Podman machine
ansible.windows.win_shell: |
$machinesJson = (podman machine ls --format json 2>$null | Out-String).Trim()
$machines = @()
if ($machinesJson) {
$machines = $machinesJson | ConvertFrom-Json
}
$machine = $machines | Where-Object { $_.Name -eq "{{ windows_containers_machine_name }}" } | Select-Object -First 1
if (-not $machine) {
podman machine init "{{ windows_containers_machine_name }}" --cpus {{ windows_containers_machine_cpus }} --memory {{ windows_containers_machine_memory }} --disk-size {{ windows_containers_machine_disk_size }}
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to initialize Podman machine {{ windows_containers_machine_name }}"
exit 1
}
$afterInitJson = (podman machine ls --format json 2>$null | Out-String).Trim()
$afterInitMachines = @()
if ($afterInitJson) {
$afterInitMachines = $afterInitJson | ConvertFrom-Json
}
$createdMachine = $afterInitMachines | Where-Object { $_.Name -eq "{{ windows_containers_machine_name }}" } | Select-Object -First 1
if (-not $createdMachine) {
Write-Error "Podman machine {{ windows_containers_machine_name }} is still missing after init"
exit 1
}
Write-Output "__PODMAN_MACHINE_CREATED__"
}
become: true
become_method: ansible.builtin.runas
become_user: "{{ windows_containers_service_user }}"
register: __podman_init
changed_when: "'__PODMAN_MACHINE_CREATED__' in (__podman_init.stdout + __podman_init.stderr)"
tags:
- roles::windows_containers::podman_install
- name: Start Podman machine on boot via NSSM service only
ansible.windows.win_shell: |
# Podman machine will be started on boot via NSSM service (PodmanAutoStart)
# No need to start it during playbook execution
podman machine status "{{ windows_containers_machine_name }}" 2>$null | Out-Null
$status = $LASTEXITCODE
if ($status -eq 0) {
Write-Output "Podman machine {{ windows_containers_machine_name }} is already running"
exit 0
}
Write-Output "Podman machine not running (will start on boot)"
exit 0
become: true
become_method: ansible.builtin.runas
become_user: "{{ windows_containers_service_user }}"
register: __podman_status_check
changed_when: false
failed_when: false
tags:
- roles::windows_containers::podman_install
- name: Deploy Podman auto-start PowerShell script
ansible.windows.win_template:
src: podman-auto-start.ps1.j2
dest: "{{ windows_containers_install_dir }}\\auto-start-podman.ps1"
mode: "0644"
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::podman_install
- name: Deploy batch wrapper to avoid path-with-spaces issues in NSSM
ansible.windows.win_template:
src: podman-auto-start.bat.j2
dest: "{{ windows_containers_install_dir }}\\auto-start-podman.bat"
mode: "0644"
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::podman_install
- name: Delete existing PodmanAutoStart service via NSSM
ansible.windows.win_shell: |
$existing = Get-CimInstance Win32_Service -Filter "Name='PodmanAutoStart'" -ErrorAction SilentlyContinue
if ($existing) {
Stop-Service -Name PodmanAutoStart -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
$null = nssm.exe remove PodmanAutoStart confirm
Start-Sleep -Seconds 2
}
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
changed_when: true
failed_when: false
no_log: true
tags:
- roles::windows_containers::podman_install
- name: Register Podman auto-start service via NSSM (batch wrapper for proper path quoting)
ansible.windows.win_shell: |
$installDir = "{{ windows_containers_install_dir }}"
$batchPath = Join-Path $installDir "auto-start-podman.bat"
nssm.exe install PodmanAutoStart "`"$batchPath`""
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
changed_when: false
tags:
- roles::windows_containers::podman_install
- name: Create log directory for Podman auto-start
ansible.windows.win_file:
path: C:\Logs\Podman
state: directory
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::podman_install
- name: Grant podman service user full control over log directory
ansible.windows.win_acl:
path: C:\Logs\Podman
user: ".\\{{ windows_containers_service_user }}"
rights: FullControl
type: allow
state: present
inherit: ContainerInherit, ObjectInherit
propagation: None
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
tags:
- roles::windows_containers::podman_install
- name: Configure Podman auto-start service via NSSM
ansible.windows.win_shell: |
$installDir = "{{ windows_containers_install_dir }}"
nssm.exe set PodmanAutoStart AppDirectory $installDir
nssm.exe set PodmanAutoStart DisplayName "Podman Auto Start"
nssm.exe set PodmanAutoStart Description "Starts Podman machine and containers on boot"
nssm.exe set PodmanAutoStart Start SERVICE_AUTO_START
nssm.exe set PodmanAutoStart AppRestartDelay 30000
nssm.exe set PodmanAutoStart AppStdout "C:\Logs\Podman\auto-start.log"
nssm.exe set PodmanAutoStart AppStderr "C:\Logs\Podman\auto-start-error.log"
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
changed_when: false
tags:
- roles::windows_containers::podman_install
- name: Set PodmanAutoStart service account via NSSM
ansible.windows.win_shell: |
nssm.exe set PodmanAutoStart ObjectName ".\{{ windows_containers_service_user }}" "{{ windows_containers_service_password }}"
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
changed_when: true
no_log: true
tags:
- roles::windows_containers::podman_install
- name: Start PodmanAutoStart service
ansible.windows.win_shell: |
$svc = Get-CimInstance Win32_Service -Filter "Name='PodmanAutoStart'" -ErrorAction SilentlyContinue
if ($svc -and $svc.State -ne 'Running') {
nssm.exe start PodmanAutoStart
Start-Sleep -Seconds 5
$svc = Get-CimInstance Win32_Service -Filter "Name='PodmanAutoStart'"
Write-Output "PodmanAutoStart service state: $($svc.State)"
} else {
Write-Output "PodmanAutoStart service already running or not found"
}
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __podman_auto_start_service
changed_when: "'state: Running' in (__podman_auto_start_service.stdout)"
failed_when: false
tags:
- roles::windows_containers::podman_install
# ansible.windows.win_shell: podman pull "{{ windows_containers_image }}"
# become: true
# become_method: ansible.builtin.runas
# become_user: "{{ windows_containers_service_user }}"
# register: __podman_pull
# changed_when: false
# tags:
# - roles::windows_containers::podman_install
@@ -0,0 +1,114 @@
---
- name: Inspect Ubuntu container state
ansible.windows.win_shell: |
$containersJson = (podman ps -a --filter "name=^{{ windows_containers_container_name }}$" --format json 2>$null | Out-String).Trim()
if (-not $containersJson -or $containersJson -eq "[]") {
Write-Output "__CONTAINER_ABSENT__"
exit 0
}
$containers = $containersJson | ConvertFrom-Json
$container = $containers | Select-Object -First 1
if ($container) {
Write-Output "__CONTAINER_PRESENT__"
if ($container.State -eq "running") {
Write-Output "__CONTAINER_RUNNING__"
}
}
register: container_state
changed_when: false
tags:
- roles::windows_containers::ubuntu_container
- name: Create Ubuntu container
ansible.windows.win_shell: podman run -d --name {{ windows_containers_container_name }} -p {{ windows_containers_container_port }}:22 --restart unless-stopped "{{ windows_containers_image }}"
when: "'__CONTAINER_ABSENT__' in (container_state.stdout + container_state.stderr)"
tags:
- roles::windows_containers::ubuntu_container
- name: Start Ubuntu container when stopped
ansible.windows.win_shell: podman start {{ windows_containers_container_name }}
when:
- "'__CONTAINER_PRESENT__' in (container_state.stdout + container_state.stderr)"
- "'__CONTAINER_RUNNING__' not in (container_state.stdout + container_state.stderr)"
tags:
- roles::windows_containers::ubuntu_container
- name: Wait for container to be ready
ansible.windows.win_shell: |
$status = (podman inspect --format "{{ '{{.State.Running}}' }}" {{ windows_containers_container_name }} 2>$null | Out-String).Trim()
if ($LASTEXITCODE -eq 0 -and $status -eq "true") {
exit 0
}
Write-Error "Container {{ windows_containers_container_name }} is not running yet"
exit 1
register: container_ready
until: container_ready.rc == 0
retries: 20
delay: 5
tags:
- roles::windows_containers::ubuntu_container
- name: Set up SSH in Ubuntu container
ansible.windows.win_shell: |
podman exec {{ windows_containers_container_name }} sh -c 'mkdir -p /home/{{ windows_containers_user }}/.ssh'
podman exec {{ windows_containers_container_name }} sh -c 'touch /home/{{ windows_containers_user }}/.ssh/authorized_keys'
podman exec {{ windows_containers_container_name }} sh -c 'chown -R {{ windows_containers_user }}:$(id -g {{ windows_containers_user }}) /home/{{ windows_containers_user }}/.ssh'
podman exec {{ windows_containers_container_name }} sh -c 'chmod 700 /home/{{ windows_containers_user }}/.ssh && chmod 600 /home/{{ windows_containers_user }}/.ssh/authorized_keys'
register: ssh_dir_setup
changed_when: false
tags:
- roles::windows_containers::ubuntu_container
- name: Deploy SSH authorized_keys
ansible.windows.win_shell: |
$keysJson = '{{ windows_containers_ssh_users | map(attribute="key") | list | to_json }}'
$keys = @()
if ($keysJson) {
$keys = $keysJson | ConvertFrom-Json
}
$keys = $keys | Where-Object { $_ -and $_.Trim() -ne "" } | ForEach-Object { $_.Trim() } | Sort-Object -Unique
$desiredContent = ""
if ($keys.Count -gt 0) {
$desiredContent = ($keys -join "`n") + "`n"
}
$currentContent = (podman exec {{ windows_containers_container_name }} sh -c "cat /home/{{ windows_containers_user }}/.ssh/authorized_keys 2>/dev/null || true" | Out-String)
if ($currentContent -ne $desiredContent) {
$encodedContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($desiredContent))
podman exec {{ windows_containers_container_name }} sh -c "echo $encodedContent | base64 -d > /home/{{ windows_containers_user }}/.ssh/authorized_keys"
podman exec {{ windows_containers_container_name }} sh -c 'chown {{ windows_containers_user }}:$(id -g {{ windows_containers_user }}) /home/{{ windows_containers_user }}/.ssh/authorized_keys'
podman exec {{ windows_containers_container_name }} sh -c 'chmod 600 /home/{{ windows_containers_user }}/.ssh/authorized_keys'
Write-Output "__AUTHORIZED_KEYS_UPDATED__"
}
register: authorized_keys_result
changed_when: "'__AUTHORIZED_KEYS_UPDATED__' in (authorized_keys_result.stdout + authorized_keys_result.stderr)"
tags:
- roles::windows_containers::ubuntu_container
- name: Enable and start sshd in Ubuntu container
ansible.windows.win_shell: |
$changed = $false
podman exec {{ windows_containers_container_name }} sh -c "dpkg -s openssh-server >/dev/null 2>&1"
if ($LASTEXITCODE -ne 0) {
podman exec {{ windows_containers_container_name }} sh -c "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server"
$changed = $true
}
podman exec {{ windows_containers_container_name }} sh -c "mkdir -p /run/sshd"
podman exec {{ windows_containers_container_name }} sh -c "pgrep -x sshd >/dev/null 2>&1"
if ($LASTEXITCODE -ne 0) {
podman exec {{ windows_containers_container_name }} /usr/sbin/sshd
$changed = $true
}
if ($changed) {
Write-Output "__SSHD_PROVISIONED__"
}
register: sshd_result
changed_when: "'__SSHD_PROVISIONED__' in (sshd_result.stdout + sshd_result.stderr)"
tags:
- roles::windows_containers::ubuntu_container
@@ -0,0 +1,100 @@
---
# ── WSL verification ─────────────────────────────────────────────────────
- name: Verify WSL is operational
ansible.windows.win_shell: wsl.exe --status --default
register: __wsl_verify
changed_when: false
failed_when: __wsl_verify.rc != 0
tags:
- roles::windows_containers::verify
# ── Podman verification ──────────────────────────────────────────────────
- name: Verify Podman is installed
ansible.windows.win_shell: podman version
register: __podman_version
changed_when: false
tags:
- roles::windows_containers::verify
- name: Verify Podman machine exists
ansible.windows.win_shell: |
$machinesJson = (podman machine ls --format json 2>$null | Out-String).Trim()
if (-not $machinesJson) {
Write-Error "No Podman machines found"
exit 1
}
$machines = $machinesJson | ConvertFrom-Json
$machine = $machines | Where-Object { $_.Name -eq "{{ windows_containers_machine_name }}" } | Select-Object -First 1
if (-not $machine) {
Write-Error "Podman machine {{ windows_containers_machine_name }} not found"
exit 1
}
Write-Output "__PODMAN_MACHINE_EXISTS__"
register: __podman_machine_ls
changed_when: false
failed_when: "'__PODMAN_MACHINE_EXISTS__' not in (__podman_machine_ls.stdout + __podman_machine_ls.stderr)"
tags:
- roles::windows_containers::verify
# ── NSSM service verification ────────────────────────────────────────────
- name: Verify NSSM Podman machine service is installed and running
ansible.windows.win_shell: |
$svc = (Get-Service -Name "{{ windows_containers_service_name }}" -ErrorAction SilentlyContinue)
if (-not $svc) { Write-Error "Service {{ windows_containers_service_name }} not found"; exit 1 }
if ($svc.StartType -ne 'Automatic') { Write-Error "Service {{ windows_containers_service_name }} is not Automatic"; exit 1 }
Write-Output "Service: $($svc.DisplayName) | Status: $($svc.Status) | StartType: $($svc.StartType)"
Write-Output "__NSSM_SERVICE_OK__"
register: __nssm_verify
changed_when: false
failed_when: "__NSSM_SERVICE_OK__ not in (__nssm_verify.stdout + __nssm_verify.stderr)"
tags:
- roles::windows_containers::verify
- name: Verify NSSM service runs as dedicated podman user
ansible.windows.win_shell: |
$svc = Get-CimInstance -ClassName Win32_Service -Filter "Name='{{ windows_containers_service_name }}'"
$owner = $svc.StartName
if ($owner -eq ".\\{{ windows_containers_service_user }}" -or $owner -eq "{{ windows_containers_service_user }}") {
Write-Output "Service runs as $owner"
Write-Output "__NSSM_USER_OK__"
} else {
Write-Error "Expected service to run as .\\{{ windows_containers_service_user }}, got $owner"
exit 1
}
register: __nssm_user_verify
changed_when: false
failed_when: "__NSSM_USER_OK__ not in (__nssm_user_verify.stdout + __nssm_user_verify.stderr)"
tags:
- roles::windows_containers::verify
# ── Container verification ───────────────────────────────────────────────
- name: Verify Ubuntu container is running
ansible.windows.win_shell: podman inspect --format "{{ '{{.State.Running}}' }}" {{ windows_containers_container_name }}
register: __container_status
changed_when: false
failed_when: __container_status.rc != 0 or __container_status.stdout | trim != 'true'
tags:
- roles::windows_containers::verify
- name: Verify SSH access to Ubuntu container
ansible.windows.win_shell: podman exec {{ windows_containers_container_name }} id {{ windows_containers_user }}
register: __user_check
changed_when: false
failed_when: __user_check.rc != 0
tags:
- roles::windows_containers::verify
- name: Report SSH connection details
ansible.windows.win_shell: |
Write-Output "Podman is installed"
Write-Output "WSL version: wsl.exe --version"
Write-Output "NSSM service: {{ windows_containers_service_name }}"
Write-Output "Ubuntu container: {{ windows_containers_container_name }}"
Write-Output "SSH access: ssh {{ windows_containers_user }}@localhost -p {{ windows_containers_container_port }}"
changed_when: false
tags:
- roles::windows_containers::verify
@@ -0,0 +1,2 @@
@echo off
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0auto-start-podman.ps1"
@@ -0,0 +1,71 @@
# Podman auto-start on Windows boot
# Runs as {{ windows_containers_service_user }} (set via NSSM ObjectName)
$ErrorActionPreference = "Stop"
$LogDir = "C:\Logs\Podman"
$LogTimestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$MachineName = "{{ windows_containers_machine_name }}"
function Write-Log {
param([string]$LogName, [System.Collections.ArrayList]$Messages)
$Log = Join-Path $LogDir "$LogName-$LogTimestamp.log"
$lines = @()
foreach ($m in $Messages) {
$lines += "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $m"
}
[System.IO.File]::WriteAllText($Log, ($lines -join "`r`n") + "`r`n", [System.Text.Encoding]::UTF8)
}
try {
$bootMessages = [System.Collections.ArrayList]::new()
$bootMessages.Add("Starting Podman auto-start process") > $null
# Check if machine is already running
$listOutput = & "{{ windows_containers_install_dir }}\podman.exe" @('machine', 'list', '--format', 'table {{ '{{' }}.Name{{ '}}' }}\t{{ '{{' }}.Running{{ '}}' }}') 2>&1 | Out-String
$listExit = $LASTEXITCODE
$bootMessages.Add("Machine list exit code: $listExit") > $null
$bootMessages.Add("Machine list output: $listOutput") > $null
$running = ($listOutput -match "$MachineName\s+true")
if ($running) {
$bootMessages.Add("Machine is already running") > $null
} else {
$startOutput = & "{{ windows_containers_install_dir }}\podman.exe" @('machine', 'start', $MachineName) 2>&1 | Out-String
$bootMessages.Add("Machine start output: $startOutput") > $null
if ($LASTEXITCODE -ne 0) {
Write-Log "auto-start" $bootMessages
exit 1
}
}
# Wait for the machine to be ready (poll podman info)
$bootMessages.Add("Waiting for machine to be ready...") > $null
$ready = $false
for ($i = 0; $i -lt 90; $i++) {
$infoOutput = & "{{ windows_containers_install_dir }}\podman.exe" @('info', '--format', 'json') 2>&1 | Out-String
if ($LASTEXITCODE -eq 0 -and $infoOutput -and $infoOutput.Trim()) {
$ready = true
$bootMessages.Add("Machine is ready after $(($i + 1) * 2) seconds") > $null
break
}
Start-Sleep -Seconds 2
}
if (-not $ready) {
Write-Log "auto-start" $bootMessages
exit 1
}
# Start any stopped containers
$startContainersOutput = & "{{ windows_containers_install_dir }}\podman.exe" @('start', '-a') 2>&1 | Out-String
$bootMessages.Add("Container start output: $startContainersOutput") > $null
$bootMessages.Add("Podman auto-start complete") > $null
Write-Log "auto-start" $bootMessages
} catch {
$errMessages = [System.Collections.ArrayList]::new()
$errMessages.Add("Podman auto-start failed: $_") > $null
$errMessages.Add("Stack trace: $($_.ScriptStackTrace)") > $null
Write-Log "auto-start-ERROR" $errMessages
exit 1
}