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
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.26] - 2026-07-29

### Fixed

- Linux LXC DHCP leases now live under 1Helm's dedicated writable runtime
state instead of the system-wide `/var/lib/misc` tree made read-only by the
hardened application service. Network recovery validates that exact lease
tree, the running dnsmasq DHCP contract, and the exact DNS, forwarding, and
outbound NAT rules before reporting a channel computer ready.
- Includes the retained-memory startup, Windows host-terminal and WSL
identity, Linux LXC recovery, and honest lifecycle/command corrections
prepared in `0.0.24` and `0.0.25`; neither version was tagged or published.

## [0.0.25] - 2026-07-29

### Fixed
Expand Down Expand Up @@ -764,7 +777,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.25...HEAD
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.26...HEAD
[0.0.26]: https://github.com/gitcommit90/1Helm/compare/v0.0.23...v0.0.26
[0.0.25]: https://github.com/gitcommit90/1Helm/compare/v0.0.23...v0.0.25
[0.0.24]: https://github.com/gitcommit90/1Helm/compare/v0.0.23...v0.0.24
[0.0.23]: https://github.com/gitcommit90/1Helm/compare/v0.0.22...v0.0.23
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,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 narrow workspace mirrors. |
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `lxc` on Linux, `wsl` on Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.25` | Versioned channel-machine image contract. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.26` | 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.25",
"version": "0.0.26",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
Expand Down
62 changes: 55 additions & 7 deletions scripts/1helm-lxc-net
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,37 @@ set -euo pipefail
MARKER="/run/1helm-lxc-net-owned"
RULE_MARKER="/run/1helm-lxc-net-rules-owned"
DNSMASQ_PID="/run/lxc/dnsmasq.pid"
DNSMASQ_STATE="/var/lib/1helm-lxc/network"
DNSMASQ_LEASE="$DNSMASQ_STATE/misc/dnsmasq.lxcbr0.leases"
BRIDGE="lxcbr0"
BRIDGE_CIDR="10.0.3.1/24"
BRIDGE_MAC="00:16:3e:00:00:00"
LXC_NET=""
for candidate in /usr/libexec/lxc/lxc-net /usr/lib/lxc/lxc-net; do [[ -x "$candidate" ]] && LXC_NET="$candidate" && break; done
[[ -n "$LXC_NET" ]] || { echo "lxc-net is unavailable" >&2; exit 1; }

start_bridge_dns() {
local dnsmasq_user
install -d -m 0755 /run/lxc "$DNSMASQ_STATE" "$DNSMASQ_STATE/misc"
lease_state_writable || { echo "The private dnsmasq lease directory is not writable." >&2; return 1; }
if [[ ! -d "/sys/class/net/$BRIDGE" ]]; then
ip link add dev "$BRIDGE" type bridge
fi
ip link set dev "$BRIDGE" address "$BRIDGE_MAC"
ip addr replace "$BRIDGE_CIDR" broadcast + dev "$BRIDGE"
ip link set dev "$BRIDGE" up
for dnsmasq_user in lxc-dnsmasq dnsmasq nobody; do
getent passwd "$dnsmasq_user" >/dev/null && break
done
dnsmasq_user="${dnsmasq_user:-nobody}"
dnsmasq --conf-file=/dev/null -u "$dnsmasq_user" --strict-order --bind-interfaces \
--pid-file="$DNSMASQ_PID" --listen-address 10.0.3.1 \
--dhcp-range 10.0.3.2,10.0.3.254 --dhcp-lease-max=253 --dhcp-no-override \
--except-interface=lo --interface=lxcbr0 --dhcp-leasefile="$DNSMASQ_LEASE" \
--dhcp-authoritative
install -m 0600 /dev/null /run/lxc/network_up
}

install_rules() {
sysctl -q -w net.ipv4.ip_forward=1 >/dev/null
if [[ -e "$RULE_MARKER" ]]; then
Expand Down Expand Up @@ -64,19 +89,42 @@ bridge_dns_healthy() {
grep -Fxq -- '--listen-address=10.0.3.1' <<<"$command_line" || return 1
fi
grep -Fxq -- '--interface=lxcbr0' <<<"$command_line" || return 1
grep -Fxq -- '--dhcp-range' <<<"$command_line" || return 1
grep -Fxq '10.0.3.2,10.0.3.254' <<<"$command_line" || return 1
grep -Fxq -- '--dhcp-lease-max=253' <<<"$command_line" || return 1
grep -Fxq -- '--dhcp-authoritative' <<<"$command_line" || return 1
grep -Fxq -- "--dhcp-leasefile=$DNSMASQ_LEASE" <<<"$command_line" || return 1
}

lease_state_writable() {
local probe
[[ -d "$DNSMASQ_STATE/misc" ]] || return 1
probe="$(mktemp "$DNSMASQ_STATE/misc/.1helm-write-check.XXXXXX")" || return 1
rm -f -- "$probe"
}

rules_healthy() {
[[ -e "$RULE_MARKER" ]] || return 1
nft list chain inet onehelm_lxc input 2>/dev/null \
| grep -Eq 'iifname "lxcbr0" udp dport \{ 53, 67 \} accept' || return 1
nft list chain inet onehelm_lxc input 2>/dev/null \
| grep -Eq 'iifname "lxcbr0" tcp dport \{ 53, 67 \} accept' || return 1
nft list chain inet onehelm_lxc forward 2>/dev/null \
| grep -Fq 'iifname "lxcbr0" accept' || return 1
nft list chain inet onehelm_lxc forward 2>/dev/null \
| grep -Eq 'oifname "lxcbr0" ct state (established,related|related,established) accept' || return 1
nft list chain ip onehelm_lxc postrouting 2>/dev/null \
| grep -Eq 'ip saddr 10\.0\.3\.0/24 ip daddr != 10\.0\.3\.0/24 masquerade' || return 1
}

network_healthy() {
lease_state_writable || return 1
bridge_dns_healthy || return 1
[[ -e "$RULE_MARKER" ]] || return 1
nft list table inet onehelm_lxc >/dev/null 2>&1 || return 1
nft list table ip onehelm_lxc >/dev/null 2>&1 || return 1
rules_healthy || return 1
}

ensure_rules() {
if [[ -e "$RULE_MARKER" ]] \
&& nft list table inet onehelm_lxc >/dev/null 2>&1 \
&& nft list table ip onehelm_lxc >/dev/null 2>&1; then
if rules_healthy; then
Comment on lines +106 to +127

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

Include IPv4 forwarding in the health contract.

install_rules() enables net.ipv4.ip_forward, but rules_healthy() never verifies it. If the sysctl is reset while the nftables marker and rules remain, ensure_rules() skips rebuilding and network_healthy() reports success even though guest forwarding and outbound NAT are broken. Add the sysctl check here and cover it in test/site.mjs.

Suggested fix
 rules_healthy() {
+  [[ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null)" == "1" ]] || return 1
   [[ -e "$RULE_MARKER" ]] || return 1
📝 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
rules_healthy() {
[[ -e "$RULE_MARKER" ]] || return 1
nft list chain inet onehelm_lxc input 2>/dev/null \
| grep -Eq 'iifname "lxcbr0" udp dport \{ 53, 67 \} accept' || return 1
nft list chain inet onehelm_lxc input 2>/dev/null \
| grep -Eq 'iifname "lxcbr0" tcp dport \{ 53, 67 \} accept' || return 1
nft list chain inet onehelm_lxc forward 2>/dev/null \
| grep -Fq 'iifname "lxcbr0" accept' || return 1
nft list chain inet onehelm_lxc forward 2>/dev/null \
| grep -Eq 'oifname "lxcbr0" ct state (established,related|related,established) accept' || return 1
nft list chain ip onehelm_lxc postrouting 2>/dev/null \
| grep -Eq 'ip saddr 10\.0\.3\.0/24 ip daddr != 10\.0\.3\.0/24 masquerade' || return 1
}
network_healthy() {
lease_state_writable || return 1
bridge_dns_healthy || return 1
[[ -e "$RULE_MARKER" ]] || return 1
nft list table inet onehelm_lxc >/dev/null 2>&1 || return 1
nft list table ip onehelm_lxc >/dev/null 2>&1 || return 1
rules_healthy || return 1
}
ensure_rules() {
if [[ -e "$RULE_MARKER" ]] \
&& nft list table inet onehelm_lxc >/dev/null 2>&1 \
&& nft list table ip onehelm_lxc >/dev/null 2>&1; then
if rules_healthy; then
rules_healthy() {
[[ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null)" == "1" ]] || return 1
[[ -e "$RULE_MARKER" ]] || return 1
nft list chain inet onehelm_lxc input 2>/dev/null \
| grep -Eq 'iifname "lxcbr0" udp dport \{ 53, 67 \} accept' || return 1
nft list chain inet onehelm_lxc input 2>/dev/null \
| grep -Eq 'iifname "lxcbr0" tcp dport \{ 53, 67 \} accept' || return 1
nft list chain inet onehelm_lxc forward 2>/dev/null \
| grep -Fq 'iifname "lxcbr0" accept' || return 1
nft list chain inet onehelm_lxc forward 2>/dev/null \
| grep -Eq 'oifname "lxcbr0" ct state (established,related|related,established) accept' || return 1
nft list chain ip onehelm_lxc postrouting 2>/dev/null \
| grep -Eq 'ip saddr 10\.0\.3\.0/24 ip daddr != 10\.0\.3\.0/24 masquerade' || return 1
}
🤖 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-lxc-net` around lines 106 - 127, Update rules_healthy() to
verify that IPv4 forwarding is enabled via the same net.ipv4.ip_forward setting
configured by install_rules(), returning unhealthy when it is not enabled. Add
coverage for this failure case in test/site.mjs, ensuring ensure_rules() and
network_healthy() do not report success when forwarding is disabled.

return
fi
install_rules
Expand All @@ -96,7 +144,7 @@ case "${1:-}" in
# its bridge loses its address. Stop only lxc-net's exact private bridge,
# then rebuild it instead of adopting the stale interface by name alone.
"$LXC_NET" stop force >/dev/null 2>&1 || true
"$LXC_NET" start
start_bridge_dns
install -m 0600 /dev/null "$MARKER"
ensure_rules
network_healthy || { echo "lxcbr0 bridge, DNS, DHCP, or NAT did not become healthy" >&2; exit 1; }
Expand Down
2 changes: 1 addition & 1 deletion scripts/1helm-lxc-runtime
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ ensure_network() {
}
remove_incomplete() {
local name="$1" current
[[ -e "$(config_path "$name")" || -e "$LXC_PATH/$name" ]] || return
[[ -e "$(config_path "$name")" || -e "$LXC_PATH/$name" ]] || return 0
if [[ ! -f "$(config_path "$name")" ]]; then
rm -rf -- "$LXC_PATH/$name"
return
Expand Down
4 changes: 3 additions & 1 deletion site/public/install-lxc-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RUNTIME_ROOT="$INSTALL_ROOT/runtime/lxc"
LXC_ROOT="/var/lib/1helm-lxc"
LXC_PATH="$LXC_ROOT/containers"
CACHE_BASE="/var/cache/1helm-lxc"
NETWORK_STATE="$LXC_ROOT/network"
HELPER_PATH="/usr/libexec/1helm-lxc-runtime"
NETWORK_HELPER_PATH="/usr/libexec/1helm-lxc-net"
CONFIG_PATH="/etc/1helm/lxc-unprivileged.conf"
Expand Down Expand Up @@ -104,6 +105,7 @@ python3 -c 'import ensurepip' >/dev/null 2>&1 || { echo "Python venv support is
# computer is provisioned.
install -d -o root -g root -m 0711 "$LXC_ROOT" "$LXC_PATH"
install -d -o root -g root -m 0700 "$CACHE_BASE"
install -d -o root -g root -m 0755 "$NETWORK_STATE" "$NETWORK_STATE/misc"

TEMP_ROOT="$(mktemp -d)"
trap 'rm -rf -- "$TEMP_ROOT"' EXIT
Expand Down Expand Up @@ -221,7 +223,7 @@ EOF
install -m 0644 /dev/stdin /etc/systemd/system/1helm-lxc-net.service <<EOF
[Unit]
Description=1Helm private LXC bridge
After=network-online.target lxc-net.service
After=network-online.target
Wants=network-online.target
Before=1helm.service

Expand Down
2 changes: 1 addition & 1 deletion src/server/channel-computers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const APPLE_RUNTIME_VERSION = "1.1.0";
export const APPLE_RUNTIME_PACKAGE = `container-${APPLE_RUNTIME_VERSION}-installer-signed.pkg`;
export const APPLE_RUNTIME_URL = `https://github.com/apple/container/releases/download/${APPLE_RUNTIME_VERSION}/${APPLE_RUNTIME_PACKAGE}`;
export const APPLE_RUNTIME_SHA256 = "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714";
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.25";
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.26";
const CONTAINER_CANDIDATES = [process.env.HELM_CONTAINER_CLI, "/usr/local/bin/container", "/opt/homebrew/bin/container", "container"].filter(Boolean) as string[];
const LXC_RUNTIME_VERSION = "1helm-lxc-runtime-v1";
const LXC_HELPER_CANDIDATES = [
Expand Down
2 changes: 1 addition & 1 deletion src/server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ export function migrate(): void {
const platformBackend = process.platform === "darwin" ? "apple" : process.platform === "win32" ? "wsl" : "lxc";
const configuredBackend = String(process.env.HELM_CHANNEL_COMPUTER_BACKEND || platformBackend);
const backend = ["apple", "lxc", "wsl", "native", "mock"].includes(configuredBackend) ? configuredBackend : platformBackend;
const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.25");
const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.26");
// Earlier Linux/Windows releases persisted the compatibility `native`
// seam into every channel row. A production host update must actually
// move those rows onto the platform isolation backend; changing the unit's
Expand Down
2 changes: 1 addition & 1 deletion test/channel-computers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ test("Apple channel-computer contract preserves isolation, files, wakes, archive
test("runtime digest and packaged image recipe stay pinned", async () => {
assert.equal(computers.APPLE_RUNTIME_SHA256, "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714");
assert.match(computers.APPLE_RUNTIME_URL, /\/1\.1\.0\/container-1\.1\.0-installer-signed\.pkg$/);
assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.25");
assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.26");
const packaging = await readFile(join(root, "scripts", "package-mac-dmg.cjs"), "utf8");
assert.match(packaging, /container\(\?:\$\|\\\/\)/, "release packaging includes container/ image assets");
const image = await readFile(join(root, "container", "Containerfile"), "utf8");
Expand Down
9 changes: 7 additions & 2 deletions test/site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ test("installer assets are explicit and syntax-valid", () => {
assert.match(lxcInstaller, /namespace_covers_range[\s\S]*65536 65535/, "the installer negotiates a full bare-metal or safe nested-host subordinate ID range");
assert.match(lxcInstaller, /1helm-update\\\.service[\s\S]*systemd-run[\s\S]*apply-linux-release\.sh/, "a v0.0.11 updater hands the complete verified release transaction outside its obsolete read-only mount namespace");
assert.match(lxcInstaller, /MainPID[\s\S]*\/proc\/\$pid\/cgroup[\s\S]*\/opt\/1helm\/update-host\.sh[\s\S]*kill -KILL/, "a failed handoff stops only the exact legacy updater main process before its obsolete EXIT rollback can run");
assert.match(lxcInstaller, /install -d[^\n]*"\$LXC_ROOT" "\$LXC_PATH"[\s\S]*install -d[^\n]*"\$CACHE_BASE"/, "the host creates every service ReadWritePaths root before starting 1Helm");
assert.match(lxcInstaller, /install -d[^\n]*"\$LXC_ROOT" "\$LXC_PATH"[\s\S]*install -d[^\n]*"\$CACHE_BASE"[\s\S]*install -d[^\n]*"\$NETWORK_STATE" "\$NETWORK_STATE\/misc"/, "the host creates every service ReadWritePaths root and the private DHCP lease directory before starting 1Helm");
assert.match(linuxUnits, /1helm-update\\\.service[\s\S]*systemd-run[\s\S]*HELM_HOST_APPLY_DELEGATED/, "a v0.0.11 updater delegates the verified unit migration outside its obsolete mount namespace");
assert.match(linuxUnits, /ReadWritePaths=[^\n]*\/usr\/libexec(?:\s|$)[^\n]*\/etc\/default(?:\s|$)[^\n]*\/etc\/systemd\/system(?:\s|$)[^\n]*\/etc\/sudoers\.d(?:\s|$)/, "future updater transactions can atomically replace and roll back only the required host-contract parent trees");
assert.doesNotMatch(linuxUnits, /ReadWritePaths=[^\n]*\/usr\/libexec\/1helm-lxc-runtime/, "the updater no longer mistakes a writable destination file for atomic parent-directory authority");
Expand All @@ -196,12 +196,17 @@ test("installer assets are explicit and syntax-valid", () => {
assert.match(lxcHelper, /cpu_count/, "LXC inspection counts noncontiguous delegated CPU lists correctly");
assert.match(lxcNetwork, /1helm-lxc-net-owned/, "the bridge wrapper stops only a bridge it started");
assert.match(lxcNetwork, /1helm-lxc-net-rules-owned[\s\S]*table ip onehelm_lxc[\s\S]*masquerade/, "an adopted bridge receives a separately owned, removable outbound NAT contract");
assert.match(lxcNetwork, /DNSMASQ_LEASE="\$DNSMASQ_STATE\/misc\/dnsmasq\.lxcbr0\.leases"[\s\S]*lease_state_writable[\s\S]*mktemp[\s\S]*bridge_dns_healthy/, "runtime health proves the exact private dnsmasq lease tree is writable from its current mount namespace");
assert.match(lxcNetwork, /start_bridge_dns[\s\S]*dnsmasq[\s\S]*--dhcp-leasefile="\$DNSMASQ_LEASE"/, "1Helm starts its private DHCP server directly instead of inheriting the distro helper's system-wide lease path");
assert.match(lxcNetwork, /--dhcp-range[\s\S]*10\.0\.3\.2,10\.0\.3\.254[\s\S]*--dhcp-lease-max=253[\s\S]*--dhcp-authoritative[\s\S]*--dhcp-leasefile=\$DNSMASQ_LEASE/, "runtime health verifies the exact DHCP and private lease-file process contract");
assert.match(lxcNetwork, /rules_healthy[\s\S]*nft list chain inet onehelm_lxc input[\s\S]*nft list chain inet onehelm_lxc forward[\s\S]*nft list chain ip onehelm_lxc postrouting[\s\S]*10\\\.0\\\.3\\\.0\/24[\s\S]*masquerade/, "runtime health verifies the exact DNS, DHCP, forwarding, and outbound NAT rules instead of accepting table names alone");
assert.match(lxcNetwork, /BRIDGE_CIDR="10\.0\.3\.1\/24"[\s\S]*bridge_dns_healthy[\s\S]*state UP[\s\S]*DNSMASQ_PID[\s\S]*--interface=lxcbr0[\s\S]*network_healthy/, "runtime health requires an up/addressed bridge and its exact dnsmasq process");
assert.match(lxcNetwork, /bridge_dns_healthy[\s\S]*ensure_rules[\s\S]*network_healthy/, "a healthy bridge can restore only its owned firewall rules without disrupting containers");
assert.match(lxcNetwork, /"\$LXC_NET" stop force[\s\S]*"\$LXC_NET" start[\s\S]*network_healthy/, "a dead private bridge/DNS stack is rebuilt and reverified instead of adopted by interface name");
assert.match(lxcNetwork, /"\$LXC_NET" stop force[\s\S]*start_bridge_dns[\s\S]*network_healthy/, "a dead private bridge/DNS stack is rebuilt with 1Helm-owned DHCP state and reverified instead of adopted by interface name");
assert.match(lxcHelper, /ready\)[\s\S]*"\$NETWORK_HELPER" start[\s\S]*"\$NETWORK_HELPER" check/, "runtime readiness repairs and then verifies the full network contract");
assert.match(lxcHelper, /inspect\)[\s\S]*current_owner[\s\S]*printf 'null/, "inspection exposes only marker-less interrupted creates as safely rebuildable partial machines");
assert.match(lxcHelper, /remove_incomplete[\s\S]*marker[\s\S]*lxc-destroy[\s\S]*rm -rf -- "\$LXC_PATH\/\$name"/, "create recovers only the exact validated marker-less partial container");
assert.match(lxcHelper, /remove_incomplete\(\)[\s\S]*\]\] \|\| return 0/, "a missing partial container is a successful no-op under the runtime's fail-closed shell mode");
assert.match(lxcInstaller, /systemctl enable --now 1helm-lxc-net\.service[\s\S]*"\$NETWORK_HELPER_PATH" start[\s\S]*"\$HELPER_PATH" ready/, "host installs and updates actively repair then verify the bridge contract without disrupting a healthy network");
assert.match(lxcHelper, /nameserver 10\.0\.3\.1[\s\S]*apt-get update/, "new guests have bridge DNS before package bootstrap");
assert.match(lxcConfig, /lxc\.apparmor\.profile = generated/);
Expand Down
Loading