Skip to content

feat(cluster): worker-as-LXC architecture (Linux v1) - #316

Merged
jaylfc merged 20 commits into
masterfrom
feat/worker-as-lxc
May 5, 2026
Merged

feat(cluster): worker-as-LXC architecture (Linux v1)#316
jaylfc merged 20 commits into
masterfrom
feat/worker-as-lxc

Conversation

@jaylfc

@jaylfc jaylfc commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the flat worker model (bare host → incus → agent containers) with a nested model: bare host runs one privileged LXC named taos-worker with nested incus inside; agent containers live in there. Linux only in v1; macOS (Apple Containerization) and Windows (WSL2) are deferred to follow-up specs.

This is the prerequisite for the agent-migration spec drafted at docs/superpowers/specs/2026-05-05-agent-migration-design.md — that spec is paused until this lands.

Why

Three concrete wins:

  1. Disk bounded. A single incus storage create taos-worker-pool btrfs size=N caps everything taOS can ever consume on the host. Host never runs out of disk because of taOS.
  2. Worker is replaceable. Nuke + reinstall the worker LXC without touching the host. Agent state survives because it lives on shared cluster storage (per migration spec), not in the worker.
  3. Cross-platform path. "Worker = isolated container that holds agent containers" maps naturally to Apple Containerization VMs on macOS and WSL2 on Windows. This PR is the Linux instance of that abstraction.

What's in the PR

Backend (T1-T4, T9-T10):

  • WorkerInfo + WorkerRegister + HeartbeatBody gain capacity fields: host_lan_ip, storage_cap_bytes, storage_used_bytes, bytes_deduped_total, worker_lxc_image_version. Heartbeat reports the three byte counters.
  • tinyagentos/cluster/worker_capacity.py reads btrfs pool size + bees dedup totals (best-effort, returns zeros on any error).
  • One-worker-per-host invariant via host_lan_ip collision check (legacy flat-mode workers without the field skip the check). Privilege/nesting verification at incus-enroll flags degraded workers.
  • tinyagentos/cluster/convert_to_lxc.py for flat-to-LXC migration (drain, delete, redeploy).
  • tinyagentos/cli/worker.py exposes taos-worker-ctl convert-to-lxc | dedup enable\|disable | resize-storage.

install-worker.sh two-phase rewrite (T5-T8):

  • Phase detection via TAOS_INSIDE_WORKER env var.
  • Flat-mode refusal — refuses to install on hosts with existing taos-agent-* containers; user runs taos-worker-ctl convert-to-lxc to migrate first.
  • Phase 1 (host): kernel feature check → host package install → incus admin init --minimal → btrfs storage pool (incus storage create taos-worker-pool btrfs size=$cap) → launch privileged+nesting taos-worker LXC → nftables port-forward :8443incus exec re-exec into LXC.
  • Phase 2 (inside worker LXC): install nested incus + best-effort bees → incus admin init --minimal → register with controller including host_lan_ip + worker_lxc_image_version.

Tests:

  • 53 unit tests passing (extended cluster + new CLI parser tests).
  • 4 KVM integration tests written, gated by TAOS_INTEGRATION=1. Cover: fresh worker-LXC install, convert flat-to-LXC, disk-quota enforcement, dedup byte tracking.

End-to-end verification

Verified against fresh Ubuntu 24.04 KVM VMs on the Fedora dev box throughout T5-T8 implementation. The 6 fix commits (8069064, 95c6113, 4db70af, 741f658, e506b0e, 9ac924d) are real bugs the iterative testing surfaced — incus storage create source= failing on ext4 hosts, IP-discovery race against DHCP, incus list -c name column expansion, stdin pollution from curl-pipe-bash, function-definition order at parse time, and bees not in Ubuntu Noble repos.

End-to-end test passing on KVM (controller + worker host VMs):

  • Worker LXC running with security.privileged=true and security.nesting=true
  • nft list ruleset shows :8443 dnat to worker LXC IP
  • Nested incus reachable via incus exec taos-worker -- incus list
  • Worker registered with controller, including host_lan_ip and worker_lxc_image_version fields populated
  • Idempotent on re-run

Test Plan

  • Backend: pytest tests/cluster/ tests/cli/ → 53/53 passing
  • Integration tests collect cleanly when TAOS_INTEGRATION not set (4 skipped)
  • bash -n scripts/install-worker.sh clean
  • shellcheck: only INFO-level (no errors)
  • End-to-end on fresh Ubuntu 24.04 KVM: Phase 1 + Phase 2 + controller registration ✓
  • Idempotent on re-run ✓
  • Run tests/integration/ with TAOS_INTEGRATION=1 against the Fedora KVM box (manual; convert-to-LXC and disk-quota paths still need the real-cluster validation)

Out of scope (deferred)

  • macOS / Windows worker abstractions (separate specs)
  • Multi-worker per host
  • Worker LXC migration between bare hosts
  • Wiring taos-worker-ctl into a unified taos worker ... dispatcher (currently a standalone console script)
  • Pre-built taOS worker image on GitHub Releases
  • bees → custom dedup tool

Spec

docs/superpowers/specs/2026-05-05-worker-as-lxc-design.md (gitignored; in your local repo).

jaylfc added 17 commits May 5, 2026 20:43
Adds host_lan_ip collision check (409 on duplicate) in register_worker,
find_worker_by_host_lan_ip to ClusterManager, degraded/degraded_reason
fields to WorkerInfo, and privilege+nesting verification in incus_enroll.
Adds PHASE variable (host/inside) driven by TAOS_INSIDE_WORKER env var,
and refuse_flat_mode_install() which blocks re-runs on hosts that already
have flat-mode taos-agent-* LXC containers.
…, worker LXC)

Adds check_kernel_features (cgroup v2, btrfs, nftables), worker_disk_cap
with TAOS_WORKER_DISK_CAP override, create_btrfs_loopback for incus storage,
launch_worker_lxc with privileged+nesting config, and phase1_host_prep
which wires them together with per-distro package install.
Adds setup_port_forward (nft dnat rule host:8443 -> worker LXC:8443,
persisted to /etc/nftables.conf), reexec_into_worker_lxc stub, and the
phase 1 entry-point block that runs on bare Linux hosts, calls
phase1_host_prep + setup_port_forward, then exits (T8 will replace the
exit 0 with the re-exec call).
… loopback

incus storage create with source= on a pre-formatted image fails on ext4
hosts (incus 6.x checks that the source path resides on btrfs). Switch to
the size= parameter so incus manages the loopback image itself, which works
on any host filesystem.
The LXC exec-reachability check in launch_worker_lxc passes before the
container's DHCP address is assigned. Add a 15-iteration / 30-second
retry loop so setup_port_forward waits for the IP rather than dying
immediately.
incus storage list does not support -c flag; use plain --format=csv
and awk to extract the name column. Also fix incus list -c name (which
expands to multiple columns) to -c n for container name-only output.
- Add phase2_inside_lxc(): apt-installs incus+bees, inits nested incus,
  writes bees.service unit (TAOS_NO_DEDUP opt-out), detects host LAN IP
  via default route gateway, then calls install_and_enroll_incus().
- Wire phase 1 entry-point to call reexec_into_worker_lxc() instead of
  the T7 placeholder log+exit; add phase 2 (inside) entry-point block.
- Add worker registration POST to /api/cluster/workers inside
  install_and_enroll_incus() (step 7) with host_lan_ip and
  worker_lxc_image_version fields before /incus-enroll (step 8).
- Make sg_incus wrapper root-aware: skip incus-admin group ceremony when
  running as root inside the privileged worker LXC.
… init

When run via 'curl | bash', stdin is the curl pipe. incus launch and
incus admin init read from stdin for YAML config; they slurp the rest
of the script, causing 'yaml: unmarshal errors' failures. Fix by
redirecting stdin to /dev/null on all incus commands that may read it.
…04 repos)

bees (btrfs dedup daemon) is not packaged in Ubuntu 24.04 Noble.
Install with apt-get and skip the bees.service setup gracefully if not
available, rather than failing the entire Phase 2 run.
…ntry-points

phase2_inside_lxc() calls install_and_enroll_incus(), but that function
was defined after the phase if-blocks, so it was not yet registered when
the inside-phase exit path ran. Move the definition up (before the phase
entry-point blocks) so it is always available. Remove the duplicate
definition that remains in the flat-install section.
Adds tinyagentos/cli/worker.py with three subcommands (convert-to-lxc,
dedup, resize-storage), registers taos-worker-ctl console script in
pyproject.toml, and covers all subcommand parsers with 11 unit tests.
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@jaylfc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 45 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5be2a694-e6e4-4b76-9df2-c4fce10aa734

📥 Commits

Reviewing files that changed from the base of the PR and between 034ba61 and d034286.

📒 Files selected for processing (22)
  • pyproject.toml
  • scripts/install-worker.sh
  • tests/cli/__init__.py
  • tests/cli/test_worker_cli.py
  • tests/cluster/test_convert_to_lxc.py
  • tests/cluster/test_local_worker_enroll.py
  • tests/cluster/test_worker_capacity.py
  • tests/cluster/test_worker_invariants.py
  • tests/cluster/test_worker_registration.py
  • tests/integration/__init__.py
  • tests/integration/conftest.py
  • tests/integration/test_convert_flat_to_lxc.py
  • tests/integration/test_disk_quota_enforcement.py
  • tests/integration/test_worker_lxc_install.py
  • tinyagentos/cli/__init__.py
  • tinyagentos/cli/worker.py
  • tinyagentos/cluster/convert_to_lxc.py
  • tinyagentos/cluster/manager.py
  • tinyagentos/cluster/worker_capacity.py
  • tinyagentos/cluster/worker_protocol.py
  • tinyagentos/routes/cluster.py
  • tinyagentos/worker/agent.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/worker-as-lxc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread scripts/install-worker.sh
export TAOS_INSIDE_WORKER=1
export TAOS_CONTROLLER_URL='${CONTROLLER_URL}'
export TAOS_WORKER_NAME='${WORKER_NAME}'
curl -sL '${REPO}/raw/${BRANCH}/scripts/install-worker.sh' | bash -s -- '${CONTROLLER_URL}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Downloading and executing script from remote URL without integrity verification poses a security risk. Consider adding GPG verification or checksum validation to prevent MITM attacks or tampered scripts.

Comment thread tinyagentos/cli/worker.py Outdated
subprocess.run(["sudo", "incus", "stop", "taos-worker"], check=True)

print(f"Resizing {pool_img} to {new_size}...")
subprocess.run(["sudo", "truncate", "-s", new_size, pool_img], check=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Truncating the storage pool image file as root without validation of the new size could lead to data loss if the size is incorrect or smaller than current usage. Add checks to ensure the new size is larger than current used space.

Comment thread scripts/install-worker.sh Outdated
die "unsupported package manager"
fi
sudo systemctl enable --now incus
sudo incus admin init --minimal < /dev/null 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Suppressing errors from 'incus admin init' with '|| true' may hide failures that could cause issues in later steps. Consider handling specific error codes or logging warnings instead.

Comment thread scripts/install-worker.sh
if command -v apt >/dev/null 2>&1; then
sudo apt update -y
sudo apt install -y incus nftables curl btrfs-progs
elif command -v dnf >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Persisting nftables ruleset by overwriting /etc/nftables.conf directly may not be atomic if the command fails midway. Consider writing to a temporary file first and moving it.

@kilo-code-bot

kilo-code-bot Bot commented May 5, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
scripts/install-worker.sh 222 Downloading script without integrity check (security risk)
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
tests/integration/conftest.py 18 SSH with plain password (security risk in tests)
Files Reviewed (22 files)
  • pyproject.toml
  • scripts/install-worker.sh
  • tests/cli/__init__.py
  • tests/cli/test_worker_cli.py
  • tests/cluster/test_convert_to_lxc.py
  • tests/cluster/test_local_worker_enroll.py
  • tests/cluster/test_worker_capacity.py
  • tests/cluster/test_worker_invariants.py
  • tests/cluster/test_worker_registration.py
  • tests/integration/__init__.py
  • tests/integration/conftest.py
  • tests/integration/test_convert_flat_to_lxc.py
  • tests/integration/test_disk_quota_enforcement.py
  • tests/integration/test_worker_lxc_install.py
  • tinyagentos/cli/__init__.py
  • tinyagentos/cli/worker.py
  • tinyagentos/cluster/convert_to_lxc.py
  • tinyagentos/cluster/manager.py
  • tinyagentos/cluster/worker_capacity.py
  • tinyagentos/cluster/worker_protocol.py
  • tinyagentos/routes/cluster.py
  • tinyagentos/worker/agent.py

Fix these issues in Kilo Cloud


Reviewed by grok-code-fast-1:optimized:free · 266,212 tokens

jaylfc added 3 commits May 5, 2026 23:32
…convert + quota

T11: test_worker_lxc_install — fresh ubuntu VM, install-worker.sh, assert LXC
config (privileged+nesting), nftables :8443, nested incus, controller registration.
T12: test_convert_flat_to_lxc — legacy flat-mode install, stub agent deploy, memory
dir hash before/after taos-worker-ctl convert-to-lxc, assert data survives.
T13: test_disk_quota_enforcement — 1 GiB quota fill + ENOSPC assertion, agent
isolation check, bees dedup bytes_deduped_total assertion (auto-skips if bees absent).
All tests guarded by @pytest.mark.skipif(not INTEGRATION, ...) — collect cleanly
in CI without TAOS_INTEGRATION=1.
CI runs pytest from the repo root but 'tests' is not an installable
package — it has no __init__.py at the top level — so absolute imports
like 'from tests.integration.conftest import ...' fail with
ModuleNotFoundError. Switch to relative imports ('from .conftest import
...') which work both in CI and locally regardless of PYTHONPATH.
@jaylfc
jaylfc force-pushed the feat/worker-as-lxc branch from 4129bf6 to d034286 Compare May 5, 2026 22:33
@jaylfc
jaylfc merged commit 4f5f91c into master May 5, 2026
8 checks passed
@jaylfc
jaylfc deleted the feat/worker-as-lxc branch June 5, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant