defmodule HostKit.LivebookDemoVM do @moduledoc false alias HostKit.{Host, Instance, Project} def main(["--" | args]), do: main(args) def main([command]) when command in ["ensure", "destroy", "status"] do apply(__MODULE__, String.to_existing_atom(command), []) end def main(_args) do IO.puts(:stderr, usage()) System.halt(2) end def ensure do project = project() with {:ok, plan} <- HostKit.plan(project), {:ok, _results} <- HostKit.apply(plan, Keyword.merge(incus_opts(), confirm: true)) do print_ready() else {:error, reason} -> fail("could not ensure Livebook demo target", reason) end end def destroy do instance = instance() case HostKit.Instance.Backend.delete(instance, incus_opts()) do :ok -> :ok {:error, reason} -> fail("could not destroy Livebook demo target", reason) end end def status do instance = instance() case HostKit.Instance.Backend.read(instance, incus_opts()) do {:ok, %Instance{}} -> IO.puts("#{name()} is present") {:ok, nil} -> IO.puts("#{name()} is absent") {:error, reason} -> fail("could not read Livebook demo target", reason) end end defp project do Project.new(:livebook_demo) |> Project.add_instance(instance()) end defp instance do host = %Host{ name: :guest, hostname: "127.0.0.1", user: "root", sudo: false, meta: %{ ssh: [ user: "root", password: password(), port: ssh_port(), silently_accept_hosts: true ] } } Instance.new(name(), backend: :incus, image: image(), kind: kind(), lifecycle: :ephemeral) |> Instance.add_port(:ssh, host: ssh_port(), guest: 22) |> Instance.add_port(:caddy_demo, host: public_port(), guest: public_port()) |> Instance.add_port(:phoenix_demo, host: phoenix_public_port(), guest: phoenix_public_port()) |> Instance.add_host(host) end defp incus_opts do [ incus: System.get_env("INCUS", "incus"), incus_sudo: env_true?("HOSTKIT_INCUS_SUDO") ] end defp print_ready do IO.puts(""" Livebook target ready: Server: 127.0.0.1 SSH user: root SSH password: #{password()} SSH port: #{ssh_port()} Caddy port: #{public_port()} Phoenix port: #{phoenix_public_port()} """) end defp usage do """ Usage: scripts/livebook_demo_vm.sh COMMAND Commands: ensure Create/start a local Incus demo target with SSH password auth destroy Delete the demo target status Show target status Environment: HOSTKIT_LIVEBOOK_DEMO_VM instance name (default: hostkit-livebook-demo) HOSTKIT_LIVEBOOK_DEMO_IMAGE Incus image (default: images:ubuntu/24.04) HOSTKIT_LIVEBOOK_DEMO_TYPE container or vm (default: container) HOSTKIT_LIVEBOOK_DEMO_SSH_PORT host SSH port (default: 2222) HOSTKIT_LIVEBOOK_DEMO_PUBLIC_PORT Caddy public demo port (default: 18080) HOSTKIT_LIVEBOOK_PHOENIX_PORT Phoenix public demo port (default: 18081) HOSTKIT_LIVEBOOK_DEMO_PASSWORD root password (default: hostkit-demo) HOSTKIT_INCUS_SUDO run incus through sudo: true/false (default: false) INCUS incus executable (default: incus) """ end defp fail(message, reason) do IO.puts(:stderr, "#{message}: #{inspect(reason)}") System.halt(1) end defp name, do: env("HOSTKIT_LIVEBOOK_DEMO_VM", "hostkit-livebook-demo") |> String.to_atom() defp image, do: env("HOSTKIT_LIVEBOOK_DEMO_IMAGE", "images:ubuntu/24.04") defp password, do: env("HOSTKIT_LIVEBOOK_DEMO_PASSWORD", "hostkit-demo") defp ssh_port, do: env_integer("HOSTKIT_LIVEBOOK_DEMO_SSH_PORT", 2222) defp public_port, do: env_integer("HOSTKIT_LIVEBOOK_DEMO_PUBLIC_PORT", 18_080) defp phoenix_public_port, do: env_integer("HOSTKIT_LIVEBOOK_PHOENIX_PORT", 18_081) defp kind do case env("HOSTKIT_LIVEBOOK_DEMO_TYPE", "container") do "vm" -> :vm _other -> :container end end defp env(name, default), do: System.get_env(name, default) defp env_integer(name, default) do case System.get_env(name) do nil -> default value -> String.to_integer(value) end end defp env_true?(name), do: System.get_env(name) in ["1", "true", "TRUE", "yes"] end HostKit.LivebookDemoVM.main(System.argv())