Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
be0b0df
Add disabled WASI GitOps canary packaging
r33drichards Sep 6, 2026
80ab17a
Restrict disabled IRC canary to port forwarding
r33drichards Sep 6, 2026
19e7bb3
Publish WASI OCI artifacts to GHCR
r33drichards Sep 6, 2026
daf692e
Validate runwasi against pinned WasmEdge RC
r33drichards Sep 6, 2026
6b12fd8
Fix runwasi CI manifest invocation
r33drichards Sep 6, 2026
6068f11
Install runwasi protobuf build dependency
r33drichards Sep 6, 2026
14506f6
Gate OCI publication on WASI acceptance
r33drichards Sep 6, 2026
3a6245c
Limit OCI publish credentials to protected job
r33drichards Sep 6, 2026
0e00501
Set up Buildx for OCI layout validation
r33drichards Sep 6, 2026
c4ae087
Port WasmEdge bindings for RC runtime validation
r33drichards Sep 6, 2026
447afcc
Avoid nested SDK workspace in runtime validation
r33drichards Sep 6, 2026
2c2d194
Install runwasi seccomp link dependency
r33drichards Sep 6, 2026
d2ff0c6
Verify shim resolves pinned RC library
r33drichards Sep 6, 2026
2a0644e
Exercise InspIRCd through isolated runwasi containerd
r33drichards Sep 6, 2026
f7672c7
Enable OCI export in runtime integration CI
r33drichards Sep 6, 2026
cf47dbc
Share shim path across integration CI steps
r33drichards Sep 6, 2026
b022d2c
ci: tag imported OCI image for containerd acceptance
r33drichards Sep 6, 2026
8619b15
ci: discover imported OCI image deterministically
r33drichards Sep 6, 2026
7eb92a4
ci: export a named OCI layout for containerd
r33drichards Sep 6, 2026
02bc904
ci: probe OCI layout naming before runtime acceptance
r33drichards Sep 6, 2026
86edb84
ci: normalize OCI references for containerd import
r33drichards Sep 6, 2026
303b40e
test: match IRC JOIN message target syntax
r33drichards Sep 6, 2026
5def9be
ci: bound non-interruptible WASI task termination
r33drichards Sep 6, 2026
92a2f04
test: assert runtime readiness and cleanup state
r33drichards Sep 6, 2026
bb0b8f7
test: verify IRC exchange after runtime restart
r33drichards Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/build-wasi-oci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Build WASI OCI artifact

on:
push:
branches: [master]
paths:
- 'Dockerfile.wasi'
- '.github/workflows/build-wasi-oci.yml'
- 'CMakeLists.txt'
- 'include/**'
- 'modules/**'
- 'src/**'
- 'tools/wasi/**'
- 'tools/runtime/normalize_oci_reference.py'
pull_request:
paths:
- 'Dockerfile.wasi'
- '.github/workflows/build-wasi-oci.yml'
- 'CMakeLists.txt'
- 'include/**'
- 'modules/**'
- 'src/**'
- 'tools/wasi/**'
- 'tools/runtime/normalize_oci_reference.py'
workflow_dispatch: {}

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Bootstrap digest-pinned tools
run: python3 tools/wasi/bootstrap.py
# Publish only the exact module that passed the full local WASI suite.
# verify.sh builds build-wasi/src/inspircd.wasm before exercising it.
- name: Build and fully validate the command module
env:
STRESS: '1'
run: sh tools/wasi/verify.sh
- name: Verify the WebAssembly module identity
run: |
set -eu
test "$(head -c 8 build-wasi/src/inspircd.wasm | od -An -t x1 | tr -d ' \n')" = 0061736d01000000
sha256sum build-wasi/src/inspircd.wasm | tee build-wasi/inspircd.wasm.sha256
- name: Set up isolated Buildx builder
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build OCI layout without publishing
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: Dockerfile.wasi
outputs: type=oci,dest=/tmp/inspircd-wasi.oci.tar
- name: Normalize OCI import reference
run: python3 tools/runtime/normalize_oci_reference.py /tmp/inspircd-wasi.oci.tar
- name: Retain deployable artifact evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: inspircd-wasi-oci
path: |
/tmp/inspircd-wasi.oci.tar
build-wasi/inspircd.wasm.sha256
retention-days: 7

publish:
needs: validate
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
permissions:
contents: read
packages: write
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Bootstrap digest-pinned tools
run: python3 tools/wasi/bootstrap.py
# Revalidate in this privileged job so the image is built from the exact
# module that passed WASI acceptance immediately before publication.
- name: Build and fully validate the command module
env:
STRESS: '1'
run: sh tools/wasi/verify.sh
- name: Verify the WebAssembly module identity
run: |
set -eu
test "$(head -c 8 build-wasi/src/inspircd.wasm | od -An -t x1 | tr -d ' \n')" = 0061736d01000000
sha256sum build-wasi/src/inspircd.wasm | tee build-wasi/inspircd.wasm.sha256
- name: Set up isolated Buildx builder
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Publish immutable OCI artifact
id: publish
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: Dockerfile.wasi
push: true
tags: ghcr.io/${{ github.repository_owner }}/inspircd:${{ github.sha }}
- name: Make this package public
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
await github.rest.packages.updatePackageForOrg({
package_type: 'container',
package_name: 'inspircd',
org: context.repo.owner,
visibility: 'public',
});
- name: Record immutable image digest
run: |
set -eu
test -n '${{ steps.publish.outputs.digest }}'
printf 'image=ghcr.io/%s/inspircd@%s\n' '${{ github.repository_owner }}' '${{ steps.publish.outputs.digest }}' >> "$GITHUB_STEP_SUMMARY"
printf 'wasm_sha256=' >> "$GITHUB_STEP_SUMMARY"
cat build-wasi/inspircd.wasm.sha256 >> "$GITHUB_STEP_SUMMARY"
102 changes: 102 additions & 0 deletions .github/workflows/ci-oci-import-probe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Probe OCI import naming

on:
pull_request:
paths:
- '.github/workflows/ci-oci-import-probe.yml'
- 'tools/runtime/oci-import-probe/**'
workflow_dispatch: {}

permissions:
contents: read

jobs:
probe:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Set up isolated Buildx builder
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build small OCI layouts with documented name variants
run: |
set -euxo pipefail
docker buildx build --file tools/runtime/oci-import-probe/Dockerfile --output type=oci,dest=/tmp/unnamed.oci.tar tools/runtime/oci-import-probe
docker buildx build --file tools/runtime/oci-import-probe/Dockerfile --output type=oci,name=probe/inspircd:latest,dest=/tmp/named.oci.tar tools/runtime/oci-import-probe
- name: Add a spec-defined descriptor reference variant
run: |
set -euxo pipefail
python3 - <<'PY'
import io
import json
import tarfile

source = "/tmp/unnamed.oci.tar"
destination = "/tmp/ref-name-latest.oci.tar"
with tarfile.open(source) as incoming, tarfile.open(destination, "w") as outgoing:
for member in incoming:
content = incoming.extractfile(member) if member.isfile() else None
if member.name == "index.json":
index = json.load(content)
descriptor = index["manifests"][0]
descriptor.setdefault("annotations", {})["org.opencontainers.image.ref.name"] = "latest"
payload = json.dumps(index, separators=(",", ":")).encode()
member.size = len(payload)
outgoing.addfile(member, io.BytesIO(payload))
elif content is None:
outgoing.addfile(member)
else:
outgoing.addfile(member, content)
PY
- name: Inspect layouts and containerd import results
run: |
set -euxo pipefail
python3 - <<'PY'
import json
import subprocess
import tarfile

reports = []
for variant in ("unnamed", "named", "ref-name-latest"):
archive_path = f"/tmp/{variant}.oci.tar"
with tarfile.open(archive_path) as archive:
index = json.load(archive.extractfile("index.json"))
descriptor = index["manifests"][0]
blobs = sorted(member.name for member in archive if member.name.startswith("blobs/sha256/"))
namespace = f"oci-probe-{variant}"
imported = subprocess.run(
["sudo", "ctr", "--namespace", namespace, "images", "import", "--base-name", "probe/inspircd", archive_path],
text=True,
capture_output=True,
)
listed = subprocess.run(
["sudo", "ctr", "--namespace", namespace, "images", "list", "-q"],
text=True,
check=True,
capture_output=True,
)
reports.append({
"variant": variant,
"descriptor_annotations": descriptor.get("annotations", {}),
"blob_digests": blobs,
"import_exit_code": imported.returncode,
"import_stdout": imported.stdout,
"import_stderr": imported.stderr,
"imported_images": listed.stdout.splitlines(),
})
# Changing index.json must not alter any content-addressed image blob.
assert reports[0]["blob_digests"] == reports[2]["blob_digests"]
with open("oci-import-probe.json", "w") as report:
json.dump(reports, report, indent=2, sort_keys=True)
print(json.dumps(reports, indent=2, sort_keys=True))
PY
- name: Retain OCI probe evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: oci-import-probe
path: |
oci-import-probe.json
/tmp/unnamed.oci.tar
/tmp/named.oci.tar
/tmp/ref-name-latest.oci.tar
retention-days: 7
171 changes: 171 additions & 0 deletions .github/workflows/ci-runwasi-rc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Validate runwasi against pinned WasmEdge RC

on:
pull_request:
paths:
- '.github/workflows/ci-runwasi-rc.yml'
- 'tools/wasi/toolchain.json'
- 'tools/runtime/**'
- 'Dockerfile.wasi'
- 'clusters/kopf-k3s/inspircd/configmap.yaml'
workflow_dispatch: {}

permissions:
contents: read

jobs:
build-shim:
runs-on: ubuntu-24.04
timeout-minutes: 45
env:
CANDIDATE: runwasi/target/release/containerd-shim-wasmedge-v1
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Bootstrap the checksum-pinned WasmEdge RC
run: python3 tools/wasi/bootstrap.py
- name: Fetch pinned runwasi source
run: |
set -eu
git init runwasi
git -C runwasi remote add origin https://github.com/containerd/runwasi.git
git -C runwasi fetch --depth=1 origin 5a8fc8e9ee0df28b0485da7419ee74c72127dfba
git -C runwasi checkout --detach FETCH_HEAD
test "$(git -C runwasi rev-parse HEAD)" = 5a8fc8e9ee0df28b0485da7419ee74c72127dfba
- name: Fetch and patch the official WasmEdge Rust SDK
run: |
set -euxo pipefail
git clone --depth=1 --branch 0.14.0 https://github.com/WasmEdge/WasmEdge-rust-sdk.git wasmedge-rust-sdk
test "$(git -C wasmedge-rust-sdk rev-parse HEAD)" = e7a74ab1837c43f609f26510cf3c456e9b9c2026
git -C wasmedge-rust-sdk apply "$GITHUB_WORKSPACE/tools/runtime/patches/wasmedge-rust-sdk-0.14.0-wasmedge-0.17.2-rc.1.patch"
python3 - <<'PY'
from pathlib import Path
path = Path("runwasi/crates/containerd-shim-wasmedge/Cargo.toml")
source = path.read_text()
old = 'wasmedge-sdk = { version = "0.14.0", default-features = false }'
new = 'wasmedge-sdk = { path = "../../../wasmedge-rust-sdk", default-features = false }'
assert old in source
path.write_text(source.replace(old, new))
PY
- name: Install the native protobuf compiler required by runwasi
run: |
sudo apt-get update
sudo apt-get install --yes protobuf-compiler libseccomp-dev
protoc --version
- name: Build dynamic shim against exactly the pinned RC
env:
WASMEDGE_DIR: ${{ github.workspace }}/.wasi-tools
LD_LIBRARY_PATH: ${{ github.workspace }}/.wasi-tools/lib64
run: |
set -euxo pipefail
cargo build --manifest-path runwasi/Cargo.toml --release -p containerd-shim-wasmedge --no-default-features 2>&1 | tee runwasi-build.log
test -x "$CANDIDATE"
ldd "$CANDIDATE" | tee runwasi-ldd.log
grep -F "$WASMEDGE_DIR/lib64/libwasmedge.so.0" runwasi-ldd.log
test "$(readlink -f "$WASMEDGE_DIR/lib64/libwasmedge.so.0")" = "$WASMEDGE_DIR/lib64/libwasmedge.so.0.1.1"
- name: Set up isolated Buildx builder
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build the validated InspIRCd OCI layout
env:
STRESS: '1'
run: |
set -euxo pipefail
sh tools/wasi/verify.sh
docker buildx build --file Dockerfile.wasi --output type=oci,dest=/tmp/inspircd-wasi.oci.tar .
python3 tools/runtime/normalize_oci_reference.py /tmp/inspircd-wasi.oci.tar
python3 - /tmp/inspircd-wasi.oci.tar <<'PY'
import json
import tarfile
import sys

with tarfile.open(sys.argv[1]) as archive:
index = json.load(archive.extractfile("index.json"))
manifests = index["manifests"]
assert len(manifests) == 1
annotations = manifests[0]["annotations"]
assert annotations["org.opencontainers.image.ref.name"] == "latest"
assert "io.containerd.image.name" not in annotations
PY
test -x "$CANDIDATE"
test -s /tmp/inspircd-wasi.oci.tar
sha256sum "$CANDIDATE" /tmp/inspircd-wasi.oci.tar | tee runtime-artifact.sha256
- name: Install the isolated runtime handler into the runner containerd
env:
WASMEDGE_DIR: ${{ github.workspace }}/.wasi-tools
run: |
set -euxo pipefail
test -x "$CANDIDATE"
test -s /tmp/inspircd-wasi.oci.tar
sudo install -m 0755 "$CANDIDATE" /usr/local/bin/containerd-shim-wasmedge-v1
sudo install -m 0755 "$WASMEDGE_DIR/lib64/libwasmedge.so.0.1.1" /usr/local/lib/libwasmedge.so.0.1.1
sudo ln -sf libwasmedge.so.0.1.1 /usr/local/lib/libwasmedge.so.0
sudo ldconfig
sudo ctr version
sudo ctr images import --base-name local/inspircd /tmp/inspircd-wasi.oci.tar
sudo ctr images list -q | grep -Fx local/inspircd:latest
- name: Exercise OCI lifecycle, invalid configuration, and two IRC clients
run: |
set -euxo pipefail
mkdir -p /tmp/inspircd-irc
sed -n '/^ inspircd.conf: |$/,$p' clusters/kopf-k3s/inspircd/configmap.yaml | sed '1d;s/^ //' > /tmp/inspircd-irc/inspircd.conf
test -s /tmp/inspircd-irc/inspircd.conf
mkdir -p /tmp/inspircd-invalid
printf '<server\n' > /tmp/inspircd-invalid/inspircd.conf
if timeout 20 sudo ctr run --rm --net-host --runtime=io.containerd.wasmedge.v1 --mount type=bind,src=/tmp/inspircd-invalid,dst=/irc,options=rbind:rw local/inspircd:latest invalid-config; then
echo 'invalid config unexpectedly started' >&2
exit 1
fi
sudo ctr run --net-host --runtime=io.containerd.wasmedge.v1 --mount type=bind,src=/tmp/inspircd-irc,dst=/irc,options=rbind:rw local/inspircd:latest inspircd-acceptance > inspircd-containerd.log 2>&1 &
runner=$!
trap 'sudo ctr tasks kill -s SIGKILL inspircd-acceptance 2>/dev/null || true; wait "$runner" || true' EXIT
for _ in $(seq 1 30); do python3 -c 'import socket; s=socket.create_connection(("127.0.0.1",6667),1); s.close()' && break || sleep 1; done
python3 -c 'import socket; s=socket.create_connection(("127.0.0.1",6667),1); s.close()'
python3 tools/runtime/containerd_irc_acceptance.py | tee irc-acceptance.log
sudo ctr tasks kill -s SIGTERM inspircd-acceptance
if timeout 10 bash -c 'while kill -0 "$1" 2>/dev/null; do sleep 0.2; done' _ "$runner"; then
echo 'SIGTERM stopped the runtime task' | tee lifecycle.log
else
# WasmEdge guest execution is non-interruptible through this shim's
# signal path. Bound this local ctr cleanup and record forced stop.
sudo ctr tasks list -q | grep -Fx inspircd-acceptance
echo 'SIGTERM grace period expired; forcing SIGKILL cleanup' | tee lifecycle.log
sudo ctr tasks kill -s SIGKILL inspircd-acceptance
timeout 15 bash -c 'while kill -0 "$1" 2>/dev/null; do sleep 0.2; done' _ "$runner"
fi
wait "$runner" || true
trap - EXIT
task_list="$(sudo ctr tasks list -q)"
! printf '%s\n' "$task_list" | grep -Fx inspircd-acceptance
container_list="$(sudo ctr containers list -q)"
! printf '%s\n' "$container_list" | grep -Fx inspircd-acceptance
! python3 -c 'import socket; socket.create_connection(("127.0.0.1", 6667), 1)'
sudo ctr run --rm --net-host --runtime=io.containerd.wasmedge.v1 --mount type=bind,src=/tmp/inspircd-irc,dst=/irc,options=rbind:rw local/inspircd:latest inspircd-restart > inspircd-restart.log 2>&1 &
restart_runner=$!
trap 'sudo ctr tasks kill -s SIGKILL inspircd-restart 2>/dev/null || true; wait "$restart_runner" || true' EXIT
for _ in $(seq 1 30); do python3 -c 'import socket; s=socket.create_connection(("127.0.0.1",6667),1); s.close()' && break || sleep 1; done
python3 -c 'import socket; s=socket.create_connection(("127.0.0.1",6667),1); s.close()'
python3 tools/runtime/containerd_irc_acceptance.py | tee -a irc-acceptance.log
sudo ctr tasks kill -s SIGKILL inspircd-restart
timeout 15 bash -c 'while kill -0 "$1" 2>/dev/null; do sleep 0.2; done' _ "$restart_runner"
wait "$restart_runner" || true
trap - EXIT
task_list="$(sudo ctr tasks list -q)"
! printf '%s\n' "$task_list" | grep -Fx inspircd-restart
container_list="$(sudo ctr containers list -q)"
! printf '%s\n' "$container_list" | grep -Fx inspircd-restart
! python3 -c 'import socket; socket.create_connection(("127.0.0.1", 6667), 1)'
- name: Retain shim build evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: runwasi-wasmedge-0.17.2-rc.1-evidence
path: |
runwasi-build.log
runwasi-ldd.log
runtime-artifact.sha256
inspircd-containerd.log
inspircd-restart.log
irc-acceptance.log
lifecycle.log
runwasi/target/release/containerd-shim-wasmedge-v1
if-no-files-found: warn
retention-days: 7
Loading
Loading