ov/skills/host-deploy/SKILL.md
MUST be invoked before any work involving: `ov deploy add host` / `ov deploy del host`, applying layer recipes to the local filesystem, the host-target install ledger, ReverseOp teardown, the host-specific `--with-services`/`--allow-repo-changes`/`--allow-root-tasks` gates, sudo batching, or the `~/.config/overthink/installed/` directory.
npx skillsauth add overthinkos/overthink-plugins host-deployInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ov deploy add host <ref> applies an image or layer's install recipe directly to the invoking user's host filesystem instead of baking it into a container image. The same InstallPlan IR that drives ov image build (via OCITarget) and container deploys (via ContainerDeployTarget) is consumed by HostDeployTarget, which translates each IR step into shell commands, podman run <builder> invocations for compile-needing work, and systemd unit writes.
Use cases:
--add-layer.Host deploys are singletons per machine: there is exactly one host deploy at any time. Container deploys coexist freely (my-dev, postgres-staging, etc.). See /ov:deploy for the command family overview and /ov-dev:install-plan for the IR.
| Action | Command |
|---|---|
| Apply layer to host | ov deploy add host <ref> |
| Apply image to host | ov deploy add host fedora-coder |
| Overlay extra layers | ov deploy add host fedora-coder --add-layer <ref> --add-layer <ref> |
| Dry-run | ov deploy add host <ref> --dry-run [--format=json] |
| Tear down | ov deploy del host |
| Tear down, keep repo changes | ov deploy del host --keep-repo-changes |
| Re-run layer tests post-deploy | ov deploy add host <ref> --verify |
Host deploys touch global system state; the gates make intent explicit. All gates are off by default. --yes enables all three plus skips sudo preflight.
| Flag | Guards |
|---|---|
| --with-services | Writing systemd units + drop-ins; systemctl enable --now; systemctl daemon-reload |
| --allow-repo-changes | Repo config mutations: structured rpm:/deb:/pac: repos:, rpm: copr:, rpm: modules:, and root cmd: tasks with dnf copr/dnf config-manager addrepo/pacman-key/add-apt-repository/release-rpm install |
| --allow-root-tasks | Arbitrary cmd: user: root task bodies (opaque shell). Structured package installs and file placements are not gated (inspectable). |
| --skip-incompatible | Skip layers without a host-matching format section instead of failing the whole deploy |
| --builder-image <ref> | Override the compile-builder image (default matches host distro family) |
| --yes / -y | Implies all three gates + skips sudo preflight |
All gates are strictly host-target; ov deploy add my-dev fedora-coder --with-services silently ignores the flag on the container target.
HostDeployTarget.Emit (ov/deploy_target_host.go) walks the IR, groups contiguous same-(Scope, Venue) steps into batches, and emits one shell block per batch:
| (Scope, Venue) | Execution form |
|---|---|
| ScopeSystem + VenueHostNative | sudo bash <<'OV_ROOT' … OV_ROOT |
| ScopeUser + VenueHostNative | bash <<'OV_USER' … OV_USER as invoking user |
| any + VenueContainerBuilder | podman run <builder> bash -s < script |
| any + VenueSkip | No-op with reason logged |
Per-layer flow:
vars: exported in each shell block as export K=V.BUILD_ARCH=$(uname -m) and CTX=<absolute-layer-dir> exported (CTX replaces /ctx/ in cmd bodies — 3 occurrences in the current layer set).set -e at the top of every block./ov-dev:host-infra).At session start (once), sudo -v refreshes the sudo timestamp so subsequent sudo bash blocks don't re-prompt within the 5-minute cache window. --yes skips the preflight.
~/.config/overthink/installed/Every host deploy writes records under the invoking user's config dir. The ledger is the source of truth for what got installed; ov deploy del host reads it to reverse precisely what was done.
~/.config/overthink/installed/
├── .lock # flock — serializes concurrent ov deploy sessions
├── deploys/
│ └── <deploy-id>.json # per-deploy: image, target, layers, add_layers
└── layers/
├── ripgrep.json # per-layer: version, deployed_by, steps, reverse_ops
└── pre-commit.json
Per-layer records carry a deployed_by: [<deploy-id>, …] set. When a deploy is torn down, its ID is removed from each layer's set; only when the set empties does the layer's actual reversal (package remove, env.d file delete, service disable, etc.) run. This is the refcount mechanism that makes overlapping deploys safe.
Every step's Reverse() method emits a list of ReverseOp values recorded in the layer ledger. ov deploy del host executes them in LIFO order. A full catalogue:
| Kind | Effect |
|---|---|
| package-remove | sudo dnf remove -y <pkgs> / apt-get purge -y / pacman -Rs --noconfirm |
| pixi-env-remove | rm -rf $HOME/.pixi/envs/<name> |
| cargo-uninstall | cargo uninstall <binary> |
| npm-uninstall-g | npm uninstall -g <package> |
| rm-file-system | sudo rm -f <path> |
| rm-file-user | rm -f <path> (no sudo) |
| rm-dir-recursive | rm -rf <path> |
| service-disable | systemctl [--user] disable --now <unit> (skipped with --keep-services) |
| service-remove | Delete the unit file (skipped with --keep-services) |
| remove-dropin | Delete a drop-in .conf and, if empty, the parent .d dir |
| restore-enabled | Re-enable a packaged unit that was enabled before ov touched it |
| remove-managed-block | Strip the shell-profile managed block (session-level, not per-op) |
| remove-envd-file | Delete ~/.config/overthink/env.d/<layer>.env |
| remove-repo-file | sudo rm -f /etc/yum.repos.d/<file>.repo (refcount-aware; skipped with --keep-repo-changes) |
| copr-disable | sudo dnf -y copr disable <repo> (skipped with --keep-repo-changes) |
See /ov-dev:host-infra for the executor implementations (reverse_ops.go).
For VenueContainerBuilder steps (pixi, npm, cargo, aur), HostDeployTarget delegates to the existing multi-stage builder image via podman run:
podman run --rm \
--user "$(id -u):$(id -g)" \
-v "$HOME/.pixi":"$HOME/.pixi":rw \
-v "$HOME/.cargo":"$HOME/.cargo":rw \
-v "$HOME/.npm-global":"$HOME/.npm-global":rw \
-v "$HOME/.cache/ov":"$HOME/.cache/ov":rw \
-v "$LAYER_SRC":/work:ro \
-e HOME="$HOME" \
-e PIXI_CACHE_DIR="$HOME/.cache/ov/pixi" \
-e NPM_CONFIG_PREFIX="$HOME/.npm-global" \
-e CARGO_HOME="$HOME/.cargo" \
-w /work \
<builder-image> bash -s < <stage-script>
Properties:
$HOME/.ssh exposure.HOME=$HOME — path-baking in shebangs resolves correctly (container and host see the same absolute paths for the mounted subdirs).$HOME/.cache/ov.aur: layers are Arch-only. On an Arch host, archlinux-builder produces .pkg.tar.zst in /tmp/aur-pkgs/ (bind-mounted to a host staging dir), then sudo pacman -U installs them. On non-Arch hosts, ov deploy add host refuses with a clean error — format incompatibility (.pkg.tar.zst can't be consumed by dnf or apt).
Glibc preflight: the builder image's glibc must be ≤ the host's glibc for compiled artifacts to run. Mismatch triggers a refusal.
Each layer's env: + path_append: materialize as a file at ~/.config/overthink/env.d/<layer>.env. A managed block in the user's shell init sources all env.d files:
# overthink:begin (managed by ov; do not edit inside this block)
for f in "$HOME/.config/overthink/env.d"/*.env; do [ -r "$f" ] && . "$f"; done
# overthink:end
Target file per shell:
~/.profile (login) + ~/.bashrc (interactive)~/.zshenv~/.config/fish/conf.d/overthink.fishLogin shell detected from $SHELL / getent passwd $USER. See /ov-dev:host-infra for the managed-block fencing protocol.
# Install ripgrep on the host via dnf/pacman/apt
ov deploy add host ripgrep
# Deploy a whole image's layer set to the host
ov deploy add host fedora-coder --with-services --yes
# Overlay a private layer onto a shared base image
ov deploy add host fedora-coder --add-layer ./private-overlay.yml --yes
# Remote-ref overlay
ov deploy add host fedora-coder \
--add-layer github.com/team-acme/configs/layers/sshkeys \
--with-services
# Dry-run — preview the InstallPlan as JSON
ov deploy add host fedora-coder --dry-run --format=json
# Tear down, keep repo-config changes (don't remove rpmfusion.repo)
ov deploy del host --keep-repo-changes
# Tear down the whole host deploy
ov deploy del host --yes
~/.config/overthink/installed/.lock is taken via flock(2) for the full duration of each ov deploy add host / ov deploy del host session. A second concurrent invocation blocks on the lock until the first completes — prevents partial-state races.
/ov:deploy — Parent command family; deploy.yml schema; container target/ov:layer — Unified services: schema (use_packaged + custom entries) that host deploys render via systemd/ov:build — Build-mode Containerfile generation; three-phase template split (prepare/install/cleanup × container/host)/ov:update — --pull equivalent on ov deploy add/ov:test — --verify re-runs layer tests: against the host post-deploy/ov-dev:install-plan — InstallPlan IR that HostDeployTarget consumes; 8 step kinds; DeployTarget interface/ov-dev:host-infra — Implementation: hostdistro.go (distro alias table), install_ledger.go (flock protocol + JSON shape), builder_run.go (podman argv), shell_profile.go (managed blocks), reverse_ops.go (15 executors), service_render.go (ServiceEntry → unit text), deploy_ref.go (4 ref forms)Canonical layer examples illustrating host-deploy behaviors:
/ov-layers:ripgrep — Pure rpm/deb/pac system-package layer/ov-layers:uv — Download-task layer placing a single binary at /usr/local/bin/ov-layers:pre-commit — Multi-builder layer (pixi + npm + cargo tasks)/ov-layers:ollama — Custom services: entry rendered to systemd unit/ov-layers:postgresql — use_packaged: entry with drop-in overrides/ov-layers:rpmfusion — Repo-config-mutating layer (requires --allow-repo-changes)/ov-layers:sshd — Layer using both use_packaged: (distro-shipped sshd.service) and custom service (sshd-wrapper)MUST be invoked when the task involves ov deploy add host, ov deploy del host, the ~/.config/overthink/installed/ ledger, any of the host-target gates, or ReverseOp behaviour. Invoke this skill BEFORE reading Go source or launching Explore agents.
development
Claude Code multi-agent support in Overthink — sub-agents, dynamic workflows, and agent teams, and how each drives the existing `ov eval` disposable beds to test and verify. MUST be invoked before authoring or invoking an ov sub-agent / dynamic workflow / agent team, wiring agent-lifecycle hooks, or asking "which primitive should drive the R10 beds?".
tools
Mounts a virtiofs share tagged `workspace` at /workspace inside a VM guest via a systemd .mount unit. Use when a kind:vm entity shares a host directory into the guest and you need it auto-mounted (and re-mounted at every boot).
development
MUST be invoked before any work involving: the `kind: android` schema kind, a `target: android` deploy, the `apk:` layer package format (installing Android apps declaratively), AndroidDeployTarget, an in-pod emulator OR a remote/physical adb-endpoint device, or nested `pod → android` deployment. The first-class Android device + app surface that sits above `ov eval adb`/`appium`.
tools
Use when committing, branching, pushing, merging, tagging, creating PRs, or approving/merging PRs with gh — the feat/-branch, R10-gated, never-force-push landing workflow across the main repo + the plugins submodule + image/<distro> submodules. Covers sync-to-upstream, branch/worktree pruning, the fork+PR path for contributors without write access, and cross-repo @github landing order.