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, at: "203.0.113.10" do
    ssh do
      user "root"
      password secret_env("HOSTKIT_SSH_PASSWORD")
      accept_hosts true
    end
  end

  bootstrap do
    package :ca_certificates
    package :curl

    mise do
      tool :erlang, "29.0.2"
      tool :elixir, "1.20.1"
    end
  end
end

Identity-file auth is preferred when available:

ssh do
  user "root"
  identity_file Path.expand("~/.ssh/id_ed25519")
  accept_hosts true
end

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.

The same demo target is represented declaratively with the generic instance DSL in examples/livebook_demo_instance.exs. scripts/livebook_demo_vm.sh is now a thin wrapper around HostKit's Incus instance backend, preserving the convenient ensure, status, and destroy commands while keeping lifecycle behavior in HostKit.

Real remote validation

Copy examples/integration_hosts.example.exs, set the host/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