Remote bootstrap and plan artifacts

Copy Markdown View Source

HostKit's remote flow is designed for machines that may not have Elixir, Mix, or application runtimes installed yet.

The control machine runs HostKit. The target machine only needs SSH and a supported package manager.

Declare the host

use HostKit.DSL

project :prod do
  host :server do
    hostname "203.0.113.10"
    user "root"
    sudo true

    ssh password: secret_env("HOSTKIT_SSH_PASSWORD"),
        silently_accept_hosts: true
  end

  service :bootstrap do
    package :ca_certificates
    package :curl

    mise path: "/usr/local/bin/mise", system_data_dir: "/usr/local/share/mise" do
      tool :erlang, "29.0.2"
      tool :elixir, "1.20.1"
    end
  end
end

Identity-file auth is preferred when available:

ssh identity_file: Path.expand("~/.ssh/id_ed25519"),
    silently_accept_hosts: true

Package resolution and locks

HostKit resolves semantic package names to target package names using Repology. Write a package lock when planning for repeatable applies:

mix host_kit.plan --host server \
  --write-package-lock host_kit.package.lock \
  infra/config.exs

Apply with the lock:

mix host_kit.apply --host server \
  --package-lock host_kit.package.lock \
  --confirm \
  infra/config.exs

Plan artifacts

For production, prefer a two-step artifact flow:

HOSTKIT_SSH_PASSWORD='...' mix host_kit.plan --host server \
  --package-lock host_kit.package.lock \
  --out host_kit.plan.json \
  infra/config.exs

Review host_kit.plan.json, then apply the reviewed artifact:

HOSTKIT_SSH_PASSWORD='...' mix host_kit.apply --host server \
  --plan host_kit.plan.json \
  --confirm \
  infra/config.exs

Artifacts include target metadata such as package manager/repository. apply --plan validates that metadata before applying package changes.

Secret safety

secret_env/1 serializes as a reference:

{
  "$type": "struct",
  "module": "Elixir.HostKit.Secret",
  "fields": {
    "source": {
      "$type": "tuple",
      "items": [
        {"$type": "atom", "value": "env"},
        "HOSTKIT_SSH_PASSWORD"
      ]
    }
  }
}

The resolved secret value is not stored in the artifact.

Linux integration with Incus

Create an Incus-backed target:

HOSTKIT_INCUS_SUDO=true HOSTKIT_SSH_PUBLIC_KEY=$HOME/.ssh/id_ed25519.pub \
  scripts/incus_integration_vm.sh ensure

Run the remote CLI integration against it:

HOSTKIT_INTEGRATION_TOOL=incus HOSTKIT_INCUS_SUDO=true \
  mix test test/integration/cli_remote_test.exs --include integration

Use HOSTKIT_INCUS_TYPE=vm for an Incus VM instead of the default container.

Real remote validation

Copy examples/integration_hosts.example.exs, set the hostname/auth settings, then run:

HOSTKIT_SSH_PASSWORD='...' \
HOSTKIT_INTEGRATION_TOOL=remote \
HOSTKIT_INTEGRATION_CONFIG=examples/integration_hosts.example.exs \
mix test test/integration/cli_remote_test.exs --include integration