Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.35] - 2026-08-01

### Fixed

- Preserved the elevated Windows setup transaction's restart-required result
before the signed-in process probes WSL, including when Windows returns an
unreliable zero child-process exit code, and kept terminal setup state
visible while the tracked parent process closes.
- Restored resident internet on nested Linux hosts by installing the narrow
AppArmor address-family permissions required by `crun`, bypassing
netavark's unavailable user-bus DNS scope, and requiring socket creation,
public DNS, and TCP egress before a resident computer becomes ready.
- Made the essential outcome, blocker-resolution, and Skipper-escalation
playbooks active in every resident turn so imperative setup requests are
executed instead of answered with tutorials, and evidenced machine-wide
network failures are escalated directly for repair.

## [0.0.34] - 2026-08-01

### Fixed
Expand Down Expand Up @@ -968,7 +985,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
notarization, stapled tickets, Gatekeeper verification, persistent
Application Support, and isolated Apple container machines.

[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.34...HEAD
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.35...HEAD
[0.0.35]: https://github.com/gitcommit90/1Helm/compare/v0.0.34...v0.0.35
[0.0.34]: https://github.com/gitcommit90/1Helm/compare/v0.0.33...v0.0.34
[0.0.33]: https://github.com/gitcommit90/1Helm/compare/v0.0.32...v0.0.33
[0.0.32]: https://github.com/gitcommit90/1Helm/compare/v0.0.31...v0.0.32
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ A fresh data directory opens first-run setup. The source runtime defaults to
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and non-OCI development/Apple workspace mirrors. |
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `oci` on Linux and Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.34` | Versioned channel-machine image contract. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.35` | Versioned channel-machine image contract. |

### Agent-first JSON CLI

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "1helm",
"productName": "1Helm",
"version": "0.0.34",
"version": "0.0.35",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
Expand Down
69 changes: 60 additions & 9 deletions scripts/1helm-oci-runtime
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,10 @@ print(bridge, subnet)
}

ensure_network() {
local owner="$1" installation network labels bridge_name
local owner="$1" installation network labels bridge_name network_path
installation="$(owner_installation "$owner")"
network="1helm-$installation"
network_path="$NETWORKS_ROOT/$network.json"
# Linux IFNAMSIZ is 15 chars; keep a stable, installation-scoped bridge name.
bridge_name="1h${installation:0:12}"
if "${PODMAN[@]}" network exists "$network"; then
Expand All @@ -283,17 +284,43 @@ labels=json.loads(sys.argv[1] or "{}")
if labels.get("com.1helm.managed") != "true" or labels.get("com.1helm.installation") != sys.argv[2]:
raise SystemExit("network ownership labels do not match")
PY
# Netavark 1.4 starts aardvark-dns through a user systemd scope. A root
# helper invoked by the system service has no user bus, so the advertised
# gateway DNS listener never exists and every resident lookup times out.
# These are 1Helm-owned JSON network definitions; disable only that broken
# plugin and let containers inherit the host's working resolvers instead.
if [[ -f "$network_path" && ! -L "$network_path" ]] \
&& grep -q '"dns_enabled"[[:space:]]*:[[:space:]]*true' "$network_path"; then
python3 - "$network_path" <<'PY'
import json, os, stat, sys, tempfile
path=sys.argv[1]
info=os.lstat(path)
if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode) or info.st_uid != 0 or info.st_gid != 0:
raise SystemExit("owned network definition is not a safe root-owned file")
with open(path, encoding="utf-8") as source: network=json.load(source)
labels=network.get("labels") or network.get("Labels") or {}
if labels.get("com.1helm.managed") != "true": raise SystemExit("refusing to change an unmanaged network")
network["dns_enabled"]=False
descriptor,candidate=tempfile.mkstemp(prefix=".1helm-network-",dir=os.path.dirname(path))
try:
with os.fdopen(descriptor,"w",encoding="utf-8") as destination:
json.dump(network,destination,indent=5); destination.write("\n"); destination.flush(); os.fsync(destination.fileno())
os.chmod(candidate,stat.S_IMODE(info.st_mode)); os.chown(candidate,0,0); os.replace(candidate,path)
finally:
if os.path.exists(candidate): os.unlink(candidate)
PY
fi
else
# Prefer a stable IFNAMSIZ-safe bridge name when the backend supports Docker-
# style bridge options (netavark). Debian bookworm's CNI backend rejects
# com.docker.network.bridge.name; fall back to a labeled default bridge and
# let ensure_guest_egress read the real interface from network inspect.
create_out=""
if ! create_out="$("${PODMAN[@]}" network create --disable-dns=false \
if ! create_out="$("${PODMAN[@]}" network create --disable-dns \
--opt "com.docker.network.bridge.name=${bridge_name}" \
--label com.1helm.managed=true --label "com.1helm.installation=$installation" "$network" 2>&1)"; then
if grep -qiE 'unsupported (bridge )?network option|unknown network option' <<<"$create_out"; then
"${PODMAN[@]}" network create --disable-dns=false \
"${PODMAN[@]}" network create --disable-dns \
--label com.1helm.managed=true --label "com.1helm.installation=$installation" "$network" >/dev/null \
|| die "channel network could not be created"
else
Expand Down Expand Up @@ -671,27 +698,51 @@ create_container() {
}

start_container() {
local name="$1" owner="$2" state network
local name="$1" owner="$2" state network root network_ready container_ok=0 network_ok=0
verify_container "$name" "$owner"
network="$(network_name "$owner")"
root="$(channel_root "$name")"
network_ready="$root/network-ready-v1"
Comment on lines +703 to +704

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether container/channel recreation clears the network-ready-v1 marker.
rg -n 'network-ready-v1|channel_root\(' scripts/1helm-oci-runtime

Repository: gitcommit90/1Helm

Length of output: 260


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- relevant function map ---'
ast-grep outline scripts/1helm-oci-runtime --match '$_' --view compact 2>/dev/null | sed -n '1,220p'
printf '%s\n' '--- marker and lifecycle references ---'
rg -n -C 8 'network_ready|network-ready-v1|create_container|destroy_container|remove_container|CHANNELS_ROOT|channel_root' scripts/1helm-oci-runtime
printf '%s\n' '--- lifecycle section ---'
sed -n '1,130p' scripts/1helm-oci-runtime
sed -n '650,770p' scripts/1helm-oci-runtime

Repository: gitcommit90/1Helm

Length of output: 30242


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- definitions and callers ---'
rg -n -C 12 '^(channel_root|create_container|delete_container|destroy_container|recreate_container|cleanup|remove|reset|main)[[:space:]]*\(\)|channel_root|create_container|network_ready|network-ready-v1|CHANNELS_ROOT' scripts/1helm-oci-runtime
printf '%s\n' '--- lines 680-760 ---'
sed -n '680,760p' scripts/1helm-oci-runtime

Repository: gitcommit90/1Helm

Length of output: 26319


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path

source = Path("scripts/1helm-oci-runtime").read_text()
marker = 'network-ready-v1'

def section(start, end):
    a = source.index(start)
    b = source.index(end, a)
    return source[a:b]

create = section('  create)\n', '  inspect)\n')
delete = section('  delete)\n', '  # Host Files/Cowork bridge')
restore = section('restore_container() {', '\noperation=')

checks = {
    "delete removes channel root": 'rm -rf -- "$root"' in delete,
    "create invokes prepare_storage before create_container": create.index('prepare_storage') < create.index('create_container'),
    "create does not remove network-ready-v1": marker not in create,
    "restore imports channel tree and starts container": 'mv -T "$temporary/channel" "$root"' in restore and 'start_container "$name" "$owner"' in restore,
    "restore does not remove network-ready-v1": marker not in restore,
}
for name, result in checks.items():
    print(f"{name}: {'YES' if result else 'NO'}")
PY

Repository: gitcommit90/1Helm

Length of output: 394


Clear network-ready-v1 when creating a new container from retained channel storage. The create and restore paths preserve the marker, so a new container can skip the network health check at lines 734-745.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/1helm-oci-runtime` around lines 703 - 704, Update the container
creation flow around channel_root and network_ready to remove or reset the
retained network-ready-v1 marker before the new container proceeds, including
restore handling as applicable. Ensure newly created containers cannot skip the
network health check in the logic around the create path.

network="$(ensure_network "$owner")"
# Re-assert egress on every start: Docker and host firewall reloads can drop
# our FORWARD/NAT inserts after boot or package updates.
if "${PODMAN[@]}" network exists "$network"; then ensure_guest_egress "$network"; fi
state="$(container_state "$name")"
# An update from the earlier aardvark-backed contract must restart a running
# retained container once so Podman regenerates /etc/resolv.conf from the
# now host-inherited network definition.
if [[ "$state" == running && ! -f "$network_ready" ]]; then
"${PODMAN[@]}" stop --time 90 "$name" >/dev/null
state="stopped"
fi
if [[ "$state" != running ]]; then "${PODMAN[@]}" start "$name" >/dev/null; fi
for _ in {1..100}; do
# Podman can briefly fail name-based --user lookup while it reconstructs
# its ephemeral runroot after a host reboot even though the persisted
# image passwd database is intact. These identities are pinned by the
# installed runtime manifest and image contract, so use their numeric form
# for the readiness probe and every subsequent exec boundary.
if "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c 'test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' 1helm-start "$owner" >/dev/null 2>&1; then
if "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c \
'test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' \
1helm-start "$owner" >/dev/null 2>&1; then
verify_container "$name" "$owner"
return
container_ok=1
break
fi
sleep 0.1
done
Comment on lines 718 to 732

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wrap the ownership/storage health-check exec in timeout.

This loop retries "${PODMAN[@]}" exec --user 0:0 "$name" ... up to 100 times, but the exec call itself has no timeout. If a single podman exec invocation hangs (for example, a container whose init process is unresponsive after a host reboot, which the surrounding comment explicitly anticipates), the loop blocks on that call indefinitely instead of retrying, and the script never reaches die. The socket/DNS/TCP probe added a few lines later (Line 736) is correctly wrapped in timeout 7, and timeout is added as a required dependency at Line 956 specifically for this function — apply the same protection here for consistency and to guarantee the retry loop cannot hang.

🐛 Proposed fix to bound the ownership/storage probe
   for _ in {1..100}; do
-    if "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c \
+    if timeout 7 "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c \
         'test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' \
         1helm-start "$owner" >/dev/null 2>&1; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _ in {1..100}; do
# Podman can briefly fail name-based --user lookup while it reconstructs
# its ephemeral runroot after a host reboot even though the persisted
# image passwd database is intact. These identities are pinned by the
# installed runtime manifest and image contract, so use their numeric form
# for the readiness probe and every subsequent exec boundary.
if "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c 'test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' 1helm-start "$owner" >/dev/null 2>&1; then
if "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c \
'test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' \
1helm-start "$owner" >/dev/null 2>&1; then
verify_container "$name" "$owner"
return
container_ok=1
break
fi
sleep 0.1
done
for _ in {1..100}; do
# Podman can briefly fail name-based --user lookup while it reconstructs
# its ephemeral runroot after a host reboot even though the persisted
# image passwd database is intact. These identities are pinned by the
# installed runtime manifest and image contract, so use their numeric form
# for the readiness probe and every subsequent exec boundary.
if timeout 7 "${PODMAN[@]}" exec --user 0:0 "$name" /bin/sh -c \
'test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' \
1helm-start "$owner" >/dev/null 2>&1; then
verify_container "$name" "$owner"
container_ok=1
break
fi
sleep 0.1
done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/1helm-oci-runtime` around lines 718 - 732, Wrap the ownership/storage
health-check command in the retry loop around the podman exec invocation, using
the existing timeout dependency and the same timeout duration as the later
socket/DNS/TCP probe. Preserve the current arguments, probe script, retry
behavior, and success handling in verify_container.

die "container did not pass its ownership and storage health check"
[[ "$container_ok" -eq 1 ]] || die "container did not pass its ownership and storage health check"
if [[ ! -f "$network_ready" ]]; then
for _ in {1..5}; do
if timeout 7 "${PODMAN[@]}" exec --user "$AGENT_UID:$AGENT_GID" "$name" /usr/bin/python3 -c \
'import socket; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.close(); socket.getaddrinfo("example.com",443,type=socket.SOCK_STREAM); c=socket.create_connection(("example.com",443),3); c.close()' \
>/dev/null 2>&1; then network_ok=1; break; fi
sleep 0.2
done
[[ "$network_ok" -eq 1 ]] || die "container did not pass its network socket, public DNS, and TCP egress health check"
printf '%s\n' 'host-dns-and-public-tcp-v1' >"$network_ready"
chown root:root "$network_ready"
chmod 0600 "$network_ready"
fi
}

stop_container() {
Expand Down Expand Up @@ -902,7 +953,7 @@ case "$operation" in
;;
ready)
(($# == 0)) || die "ready takes no arguments"
for command in find flock getfacl iptables podman python3 setfacl sha256sum stat tar; do command -v "$command" >/dev/null || die "missing $command"; done
for command in find flock getfacl iptables podman python3 setfacl sha256sum stat tar timeout; do command -v "$command" >/dev/null || die "missing $command"; done
[[ -r "$CONTAINERFILE" ]] || die "installed OCI image recipe is missing"
"${PODMAN[@]}" info >/dev/null
printf '{"ready":true,"version":"%s","engine":"podman"}\n' "$RUNTIME_VERSION"
Expand Down
67 changes: 50 additions & 17 deletions scripts/install-wsl-runtime.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,50 @@ function Fail-Setup {
exit $Code
}

function Read-ReportedSetupStatus {
$path = $env:HELM_WSL_SETUP_STATUS
if (-not $path -or -not (Test-Path -LiteralPath $path -PathType Leaf)) { return $null }
try {
return Get-Content -LiteralPath $path -Raw | ConvertFrom-Json
} catch {
return $null
}
}

# The elevated process exit code is not authoritative across every Windows
# PowerShell/UAC host. The shared status file is the transaction record: in
# particular, never continue into the signed-in-user WSL probe after the
# elevated pass has declared that a reboot is required.
function Get-HostSetupOutcome {
param([Nullable[int]]$ExitCode)
$reported = Read-ReportedSetupStatus
if ($null -ne $reported -and $reported.status -eq "restart_required") {
$step = if ($reported.step) { [string]$reported.step } else { "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup." }
$errorMessage = if ($reported.error) { [string]$reported.error } else { "Windows restart required to finish enabling WSL 2." }
return [pscustomobject]@{ Status = "restart_required"; Step = $step; Detail = $errorMessage }
}
if ($null -ne $reported -and $reported.status -eq "failed") {
$detail = if ($reported.error) { [string]$reported.error } elseif ($reported.step) { [string]$reported.step } else { "The administrator-approved WSL host setup failed." }
return [pscustomobject]@{ Status = "failed"; Step = $detail; Detail = $detail }
}
if ($null -eq $ExitCode) {
$detail = "Administrator approval was cancelled or Windows did not start the elevated WSL host setup."
return [pscustomobject]@{ Status = "failed"; Step = $detail; Detail = $detail }
}
if ($ExitCode -eq 10) {
return [pscustomobject]@{
Status = "restart_required"
Step = "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup."
Detail = "Windows restart required to finish enabling WSL 2."
}
}
if ($ExitCode -ne 0) {
$detail = "The administrator-approved WSL host setup failed with exit code $ExitCode."
return [pscustomobject]@{ Status = "failed"; Step = $detail; Detail = $detail }
}
return [pscustomobject]@{ Status = "continue"; Step = ""; Detail = "" }
}

if ($HostSetup) {
try {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
Expand Down Expand Up @@ -217,25 +261,14 @@ try {
$hostArguments += @("-StatusPath", ('"{0}"' -f $statusArg))
}
$hostProcess = Start-Process -FilePath "powershell.exe" -ArgumentList ($hostArguments -join " ") -Verb RunAs -Wait -PassThru
if ($null -eq $hostProcess -or $null -eq $hostProcess.ExitCode) {
Fail-Setup "Administrator approval was cancelled or Windows did not start the elevated WSL host setup."
}
if ($hostProcess.ExitCode -eq 10) {
Write-SetupStatus -Status "restart_required" -Step "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup." -Progress 20
Write-Host "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup."
$hostExitCode = if ($null -eq $hostProcess) { $null } else { $hostProcess.ExitCode }
$hostOutcome = Get-HostSetupOutcome -ExitCode $hostExitCode
if ($hostOutcome.Status -eq "restart_required") {
Write-SetupStatus -Status "restart_required" -Step $hostOutcome.Step -Progress 20 -ErrorMessage $hostOutcome.Detail
Write-Host $hostOutcome.Step
exit 10
}
if ($hostProcess.ExitCode -ne 0) {
$detail = "The administrator-approved WSL host setup failed with exit code $($hostProcess.ExitCode)."
if ($env:HELM_WSL_SETUP_STATUS -and (Test-Path -LiteralPath $env:HELM_WSL_SETUP_STATUS)) {
try {
$reported = Get-Content -LiteralPath $env:HELM_WSL_SETUP_STATUS -Raw | ConvertFrom-Json
if ($reported.error) { $detail = [string]$reported.error }
elseif ($reported.step -and $reported.status -eq "failed") { $detail = [string]$reported.step }
} catch { }
}
Fail-Setup $detail
}
if ($hostOutcome.Status -eq "failed") { Fail-Setup $hostOutcome.Detail }
if (-not (Test-PinnedWslRuntime)) {
Fail-Setup "Microsoft WSL $wslVersion is not ready in the signed-in user's session."
}
Expand Down
1 change: 1 addition & 0 deletions site/public/apply-linux-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ HOST_CONTRACT_PATHS=(
/usr/libexec/1helm-oci-runtime
/etc/1helm/oci-runtime-v1.conf
/etc/sudoers.d/1helm-oci-runtime
/etc/apparmor.d/local/crun
/etc/tmpfiles.d/1helm-oci.conf
/usr/lib/1helm-oci/Containerfile.oci
/usr/lib/1helm-oci/channel-machine.oci.tar
Expand Down
26 changes: 26 additions & 0 deletions site/public/install-oci-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ fi
for command in crun find flock getfacl iptables podman python3 setfacl sha256sum stat sudo tar visudo; do command -v "$command" >/dev/null || { echo "Missing OCI prerequisite after setup: $command" >&2; exit 1; }; done
[[ "$(stat -fc %T /sys/fs/cgroup)" == cgroup2fs ]] || { echo "1Helm OCI resource controls require cgroup v2." >&2; exit 1; }

# Ubuntu ships an AppArmor attachment for /usr/bin/crun whose nominally
# unconfined profile can still inherit the outer container host's address-family
# restrictions. On a nested systemd host that manifests inside every resident
# as EPERM from socket(2), even though the 1Helm host itself has internet. Add
# only the missing address-family grants to crun's supported local include; the
# outer host/container profile remains the isolation boundary.
NESTED_CRUN_PROFILE="/etc/apparmor.d/local/crun"
NESTED_CRUN_MARKER="# Managed by 1Helm: allow resident OCI network sockets on nested hosts."
container_virt="$(systemd-detect-virt --container 2>/dev/null || true)"
apparmor_enabled="$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null || true)"
if [[ -n "$container_virt" && "$container_virt" != none && "$apparmor_enabled" =~ ^[Yy]$ && -r /etc/apparmor.d/crun ]]; then
command -v apparmor_parser >/dev/null || { echo "Nested AppArmor OCI setup requires apparmor_parser." >&2; exit 1; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Confirm that the installer provisions the command it requires.
grep -nE 'apt-get install.*apparmor|for command in .*apparmor_parser' \
  site/public/install-oci-runtime.sh

# Run on each supported Ubuntu/Debian base image.
command -v apparmor_parser

Repository: gitcommit90/1Helm

Length of output: 155


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- installer context ---'
sed -n '1,90p' site/public/install-oci-runtime.sh

printf '%s\n' '--- AppArmor references ---'
rg -n -C 3 'apparmor|apt-get install|command -v' site/public/install-oci-runtime.sh

Repository: gitcommit90/1Helm

Length of output: 8609


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

path = Path("site/public/install-oci-runtime.sh")
text = path.read_text()

install_lines = [
    line.strip() for line in text.splitlines()
    if "apt-get install" in line
]
command_loops = [
    line.strip() for line in text.splitlines()
    if re.search(r"for command in", line)
]

print("apt install lines:")
print("\n".join(install_lines))
print("command prerequisite loops:")
print("\n".join(command_loops))
print("apparmor in install set:", any(re.search(r"\bapparmor\b", line) for line in install_lines))
print("apparmor_parser in prerequisite loops:",
      any("apparmor_parser" in line for line in command_loops))
PY

Repository: gitcommit90/1Helm

Length of output: 826


🌐 Web query:

Debian Ubuntu package apparmor_parser provided by apparmor package official package contents

💡 Result:

In both Debian and Ubuntu, the apparmor_parser executable is officially provided by the apparmor binary package [1][2][3]. The apparmor package is the core user-space utility package for AppArmor, which includes the apparmor_parser tool used to compile and load AppArmor security policies into the Linux kernel [1][4][3]. Depending on the specific system configuration and version history, this file may be located in /usr/sbin/apparmor_parser or historically in /sbin/apparmor_parser [3][5][6][7]. Modern distributions typically utilize a merged /usr layout where these paths are unified [7].

Citations:


Install the AppArmor parser before requiring it.

Add apparmor to the apt-get install set and add apparmor_parser to both prerequisite checks. Otherwise, a nested host can skip installation and exit when the parser check runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/public/install-oci-runtime.sh` at line 44, Update the package
installation list and prerequisite validation in the OCI setup script: include
the apparmor package in the apt-get install set, and ensure both prerequisite
checks include apparmor_parser before the nested AppArmor configuration runs.
Preserve the existing failure behavior when the parser remains unavailable.

if [[ ! -e "$NESTED_CRUN_PROFILE" ]]; then
install -d -o root -g root -m 0755 "$(dirname "$NESTED_CRUN_PROFILE")"
profile_candidate="$(mktemp)"
printf '%s\nnetwork inet,\nnetwork inet6,\n' "$NESTED_CRUN_MARKER" >"$profile_candidate"
install -o root -g root -m 0644 "$profile_candidate" "$NESTED_CRUN_PROFILE"
rm -f -- "$profile_candidate"
elif ! grep -Eq '^[[:space:]]*network[[:space:]]+inet[[:space:]]*,' "$NESTED_CRUN_PROFILE" \
|| ! grep -Eq '^[[:space:]]*network[[:space:]]+inet6[[:space:]]*,' "$NESTED_CRUN_PROFILE"; then
echo "The existing AppArmor local/crun policy does not permit resident IPv4 and IPv6 sockets; 1Helm left the custom policy unchanged." >&2
exit 1
fi
apparmor_parser -r /etc/apparmor.d/crun
fi

install -d -o root -g root -m 0755 /etc/1helm "$RECIPE_ROOT" /usr/libexec
install -d -o root -g root -m 0711 "$STATE_ROOT/runtime/oci" "$STATE_ROOT/runtime/oci/channels"
install -d -o root -g root -m 0700 "$STATE_ROOT/runtime/oci/storage" "$STATE_ROOT/runtime/oci/backups" "$STATE_ROOT/runtime/oci/networks"
Expand Down
1 change: 1 addition & 0 deletions site/public/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ HOST_CONTRACT_PATHS=(
/usr/libexec/1helm-oci-runtime
/etc/1helm/oci-runtime-v1.conf
/etc/sudoers.d/1helm-oci-runtime
/etc/apparmor.d/local/crun

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reload the crun AppArmor profile during rollback.

A failed transaction can reload the new local profile before rollback. Each rollback restores /etc/apparmor.d/local/crun on disk, but only runs systemctl daemon-reload. That command does not reload AppArmor policy. The kernel can retain the new socket grants after rollback.

  • site/public/install.sh#L17-L17: After restoring host-contract files, reload /etc/apparmor.d/crun when apparmor_parser and the parent profile are available.
  • site/public/apply-linux-release.sh#L23-L23: After restoring host-contract files, reload /etc/apparmor.d/crun when apparmor_parser and the parent profile are available.
  • site/public/update-host.sh#L25-L25: After restoring host-contract files, reload /etc/apparmor.d/crun when apparmor_parser and the parent profile are available.
📍 Affects 3 files
  • site/public/install.sh#L17-L17 (this comment)
  • site/public/apply-linux-release.sh#L23-L23
  • site/public/update-host.sh#L25-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/public/install.sh` at line 17, Update rollback handling in
site/public/install.sh (line 17), site/public/apply-linux-release.sh (line 23),
and site/public/update-host.sh (line 25) to reload /etc/apparmor.d/crun after
restoring host-contract files, but only when apparmor_parser and the parent
AppArmor profile are available; retain the existing daemon-reload behavior.

/etc/tmpfiles.d/1helm-oci.conf
/usr/lib/1helm-oci/Containerfile.oci
/usr/lib/1helm-oci/channel-machine.oci.tar
Expand Down
7 changes: 7 additions & 0 deletions site/public/uninstall-host.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ done
systemctl disable --now 1helm-update.path 2>/dev/null || true
rm -f -- /etc/systemd/system/1helm.service /etc/systemd/system/1helm-update.service /etc/systemd/system/1helm-update.path
rm -f -- /etc/sudoers.d/1helm-oci-runtime /etc/1helm/oci-runtime-v1.conf /usr/libexec/1helm-oci-runtime /usr/lib/1helm-oci/Containerfile.oci /usr/lib/1helm-oci/channel-machine.oci.tar /usr/lib/1helm-oci/channel-machine.oci.sha256 /usr/lib/1helm-oci/channel-machine.oci.json
managed_crun_profile="$(mktemp)"
printf '%s\nnetwork inet,\nnetwork inet6,\n' '# Managed by 1Helm: allow resident OCI network sockets on nested hosts.' >"$managed_crun_profile"
if [[ -f /etc/apparmor.d/local/crun ]] && cmp -s "$managed_crun_profile" /etc/apparmor.d/local/crun; then
rm -f -- /etc/apparmor.d/local/crun
command -v apparmor_parser >/dev/null 2>&1 && [[ -r /etc/apparmor.d/crun ]] && apparmor_parser -r /etc/apparmor.d/crun || true
fi
rm -f -- "$managed_crun_profile"
rm -f -- "$INSTALL_ROOT/update-host.sh" "$INSTALL_ROOT/uninstall-host.sh"
systemctl daemon-reload
printf 'Removed the 1Helm services and %s owned channel container(s). Preserved %s and versioned release files for recovery.\n' "${#MACHINES[@]}" "$STATE_ROOT"
1 change: 1 addition & 0 deletions site/public/update-host.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ HOST_CONTRACT_PATHS=(
/usr/libexec/1helm-oci-runtime
/etc/1helm/oci-runtime-v1.conf
/etc/sudoers.d/1helm-oci-runtime
/etc/apparmor.d/local/crun
/etc/tmpfiles.d/1helm-oci.conf
/usr/lib/1helm-oci/Containerfile.oci
/usr/lib/1helm-oci/channel-machine.oci.tar
Expand Down
Loading
Loading