Skip to content

fix notepad-window via cmd.exe /c start + native-pattern research doc #534

fix notepad-window via cmd.exe /c start + native-pattern research doc

fix notepad-window via cmd.exe /c start + native-pattern research doc #534

Workflow file for this run

name: mios-ci
# In-repo build: MiOS owns the Containerfile and Justfile, so CI builds
# straight from the repository tree -- no cross-repo fetch.
#
# Parity with .forgejo/workflows/build-mios.yml: both pipelines use
# `podman build` (NOT docker/build-push-action) so the OCI manifests,
# layer digests, labels, and provenance match bit-for-bit between the
# self-hosted Forgejo Runner closure and the GitHub Actions cloud
# closure. An operator pulling ghcr.io/mios-dev/mios:latest gets the
# same image whether the runner was forgejo-side or github-side.
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: mios-dev/mios
jobs:
build:
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read VERSION
id: ver
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- name: Resolve image tag (VERSION + UTC timestamp + short SHA)
id: tag
run: |
VER="$(cat VERSION 2>/dev/null || echo 'v0.0.0')"
SHA="$(git rev-parse --short=12 HEAD)"
TS="$(date -u +%Y%m%d-%H%M%S)"
TAG="${VER#v}-${TS}-${SHA}"
echo "image_tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "Building tag: ${TAG}"
- name: Install podman
run: |
sudo apt-get update -qq
sudo apt-get install -y podman skopeo
- name: Free disk space on the GHA runner
# GitHub-hosted ubuntu-24.04 runners ship with ~14 GB free on
# /. The MiOS Containerfile bakes 16+ container images into
# /usr/lib/containers/storage at OCI build time
# (BOUND-IMAGES law -- runtime pulls are bugs), and the bake
# blew through the runner's free space mid-pull at the 16th
# image (open-webui's NotoSansSC font blob, ~30 MB) on
# 2026-05-15. jlumbroso/free-disk-space reclaims ~30 GB by
# removing pre-installed Android SDK, .NET, GHC, Haskell,
# CodeQL, large /opt entries, etc. -- none of which the OCI
# build needs. Forgejo's self-hosted runner has plenty of
# disk and skips this; keeping the steps in parity here
# matters less than building successfully.
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: false
- name: Login to GHCR (raise base-image pull rate limit)
# Anonymous GHCR pulls hit "503 Egress is over the account limit"
# mid-pull on big multi-layer images like ublue-os/ucore-hci
# (~70 blobs, all anonymous-quota-counted). Authenticated pulls
# don't share the anonymous pool, so logging in even with the
# auto-provided GITHUB_TOKEN (which has GHCR read on public
# images) bypasses the rate limit. Operator-confirmed CI failure
# 2026-05-15: Containerfile FROM ghcr.io/ublue-os/ucore-hci died
# at blob 67/70 with the anonymous-quota error.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GH_TOKEN" | sudo podman login ghcr.io \
-u "${{ github.actor }}" --password-stdin
- name: Build OCI image (parity with Forgejo Runner)
run: |
set -euo pipefail
# Source build-args from /etc/mios/install.env if present
# (matches Forgejo runner behavior). On GitHub-hosted runners
# there's no install.env so the build uses Containerfile
# defaults — which is the correct vendor-neutral build.
BUILD_ARGS=()
if [[ -r /etc/mios/install.env ]]; then
# shellcheck source=/dev/null
source /etc/mios/install.env
[[ -n "${MIOS_LINUX_USER:-}" ]] && BUILD_ARGS+=(--build-arg "MIOS_USER=${MIOS_LINUX_USER}")
[[ -n "${MIOS_HOSTNAME:-}" ]] && BUILD_ARGS+=(--build-arg "MIOS_HOSTNAME=${MIOS_HOSTNAME}")
[[ -n "${MIOS_AI_MODEL:-}" ]] && BUILD_ARGS+=(--build-arg "MIOS_AI_MODEL=${MIOS_AI_MODEL}")
[[ -n "${MIOS_AI_EMBED_MODEL:-}" ]] && BUILD_ARGS+=(--build-arg "MIOS_AI_EMBED_MODEL=${MIOS_AI_EMBED_MODEL}")
[[ -n "${MIOS_OLLAMA_BAKE_MODELS:-}" ]] && BUILD_ARGS+=(--build-arg "MIOS_OLLAMA_BAKE_MODELS=${MIOS_OLLAMA_BAKE_MODELS}")
fi
# sudo: rootful podman avoids the user-namespace UID exhaustion
# that breaks the bound-images bake step. The bake's inner
# `podman --root /usr/lib/containers/storage pull` unpacks
# images that contain files owned by high-numbered system
# GIDs (e.g. /etc/gshadow gid=42); rootless podman remaps via
# /etc/subuid + /etc/subgid which the GHA runner user has only
# 65k entries for, and the chown then fails with "lchown
# /etc/gshadow: invalid argument" -- "potentially insufficient
# UIDs or GIDs available in user namespace". Rootful podman
# has full UID range and skips the remap. Operator-confirmed
# CI failure 2026-05-15 (qdrant + 13 other bound images failed
# with this exact error mid-bake).
sudo podman build \
"${BUILD_ARGS[@]}" \
-f Containerfile \
-t "localhost/mios:latest" \
-t "localhost/mios:${{ steps.tag.outputs.image_tag }}" \
.
- name: Verify bootc lint passes (Architectural Law 4)
run: |
# Containerfile already runs `bootc container lint` as the
# final build step. Belt-and-suspenders check: confirm the
# freshly-built image still carries the required bootc labels.
# Identical check to .forgejo/workflows/build-mios.yml.
# sudo: read from rootful storage where the build placed the image
sudo podman image inspect localhost/mios:latest \
--format '{{ index .Config.Labels "containers.bootc" }}' \
| grep -qx '1' || {
echo "ERROR: built image missing containers.bootc=1 label" >&2
exit 1
}
sudo podman image inspect localhost/mios:latest \
--format '{{ index .Config.Labels "ostree.bootable" }}' \
| grep -qx '1' || {
echo "ERROR: built image missing ostree.bootable=1 label" >&2
exit 1
}
- name: Rechunk for smaller Day-2 deltas (every push)
run: |
set -euo pipefail
# hhd-dev/rechunk produces 5-10x smaller bootc-upgrade
# deltas. Forgejo Runner runs this on every push (closing
# the self-replication loop with optimal pull bandwidth);
# GitHub Actions does the same so cloud-pulled images
# match self-hosted images bit-for-bit.
IMAGE_TAG="localhost/mios:${{ steps.tag.outputs.image_tag }}"
MAX_LAYERS="${MIOS_RECHUNK_MAX_LAYERS:-67}"
# sudo: rechunk reads from + writes to rootful storage
sudo podman run --rm \
--security-opt label=type:unconfined_t \
-v /var/lib/containers/storage:/var/lib/containers/storage \
"${IMAGE_TAG}" \
/usr/libexec/bootc-base-imagectl rechunk \
--max-layers "${MAX_LAYERS}" \
"containers-storage:${IMAGE_TAG}" \
"containers-storage:${IMAGE_TAG}-rechunked" || {
echo "WARN: rechunk failed; pushing un-rechunked image" >&2
exit 0
}
sudo podman tag "${IMAGE_TAG}-rechunked" "${IMAGE_TAG}"
sudo podman tag "${IMAGE_TAG}" "localhost/mios:latest"
- name: Compute registry tags
id: meta
if: github.event_name != 'pull_request'
run: |
BASE="${REGISTRY}/${IMAGE_NAME}"
TAGS=()
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
TAGS+=("${BASE}:latest")
TAGS+=("${BASE}:${{ steps.ver.outputs.version }}")
fi
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
SEMVER="${GITHUB_REF#refs/tags/v}"
TAGS+=("${BASE}:${SEMVER}")
TAGS+=("${BASE}:latest")
fi
TAGS+=("${BASE}:${{ steps.tag.outputs.image_tag }}")
{
echo "tags<<EOF"
printf '%s\n' "${TAGS[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Log in to GHCR (push step)
if: github.event_name != 'pull_request'
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
sudo podman login -u "${{ github.actor }}" --password-stdin "${REGISTRY}"
- name: Push to GHCR
if: github.event_name != 'pull_request'
run: |
set -euo pipefail
while IFS= read -r tag; do
[[ -z "$tag" ]] && continue
sudo podman tag localhost/mios:latest "$tag"
sudo podman push "$tag"
echo "Pushed: $tag"
done <<< "${{ steps.meta.outputs.tags }}"
- name: Install cosign
if: startsWith(github.ref, 'refs/tags/v')
uses: sigstore/cosign-installer@v3
- name: Cosign keyless sign (tag pushes)
if: startsWith(github.ref, 'refs/tags/v')
env:
COSIGN_EXPERIMENTAL: '1'
run: |
set -euo pipefail
while IFS= read -r tag; do
[[ -z "$tag" ]] && continue
cosign sign --yes "$tag"
done <<< "${{ steps.meta.outputs.tags }}"
smoke-test:
runs-on: ubuntu-24.04
needs: build
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install podman
run: |
sudo apt-get update -qq
sudo apt-get install -y podman
- name: Free disk space on the GHA runner (smoke job)
# Same rationale as the main build job -- runner needs ~30 GB
# extra room for the 16+ bound-image bakes during smoke build.
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: false
- name: Login to GHCR (raise base-image pull rate limit, smoke step)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GH_TOKEN" | sudo podman login ghcr.io \
-u "${{ github.actor }}" --password-stdin
- name: Smoke build (lint via Containerfile final RUN)
run: |
# sudo: rootful podman avoids user-namespace UID exhaustion in
# the bake step (same fix as the main build step above).
sudo podman build -t mios:smoke -f Containerfile .