defmodule Tailscale do
@moduledoc """
Elixir bindings for the Tailscale Rust client.
"""
@typedoc """
An IPv4 address.
`tailscale` is capable of interpreting either the `inet` format or a `String`.
"""
@type ip4_addr() :: :inet.ip4_address() | String.t()
@typedoc """
An IPv6 address.
`tailscale` is capable of interpreting either the `inet` format or a `String`.
"""
@type ip6_addr() :: :inet.ip6_address() | String.t()
@typedoc """
An IP address (v4 or v6).
`tailscale` is capable of interpreting either the `inet` format or a `String`.
"""
@type ip_addr() :: ip4_addr() | ip6_addr()
@typedoc """
Handle to a tailscale "device", i.e. a unique tailnet-connected identity with a network address.
See the note in `connect/2` about nomenclature for more details.
"""
@opaque t() :: Tailscale.Native.device()
@spec connect(String.t(), String.t() | nil) :: {:ok, t()}
@doc """
Open a connection to tailscale, creating a device connected to a tailnet.
## Parameters
- `config_path`: the path of the config/state file to load. This contains the node's cryptographic
keys and therefore defines the identity of this device. It will be created if it doesn't exist.
- `auth_key`: the auth key to be used to authorize this device. You only need to supply this if
the device state in `config_path` has not been authorized.
## Nomenclature (devices, peers, nodes, etc.)
In our parlance, anything that shows up on console.tailscale.com
and gets a tailnet IP is known canonically as a "device", though these are also variously been
referred to as "nodes" or "peers". Conventionally, each of these would be a device running
`tailscaled`, but with the advent of `tsnet` and now `tailscale-rs` and its derivative
cross-language clients, a single computer can have many Tailscale connections simultaneously,
possibly to many different tailnets. As an attempt to capture the whole ontology of "things that
have a persistent identity and tailnet IP", we try to refer to them uniformly by the umbrella term
"device".
"""
def connect(config_path, auth_key \\ nil) do
Tailscale.Native.connect(config_path, auth_key)
end
@spec ipv4_addr(t()) :: {:ok, :inet.ip4_address()} | {:error, any()}
@doc """
Get the current IPv4 address of this Tailscale node.
Blocks until the address is available.
"""
def ipv4_addr(dev), do: Tailscale.Native.ipv4_addr(dev)
@spec ipv6_addr(t()) :: {:ok, :inet.ip6_address()} | {:error, any()}
@doc """
Get the current IPv6 address of this Tailscale node.
Blocks until the address is available.
Note that this address is in `:inet` format (16-bit segments), which may be difficult to read.
See `:inet.ntoa` to format to a string.
"""
def ipv6_addr(dev), do: Tailscale.Native.ipv6_addr(dev)
@spec peer_by_name(t(), String.t()) :: {:ok, Tailscale.NodeInfo.t() | nil} | {:error, any()}
@doc """
Look up a peer by name.
Returns `{:ok, nil}` if there was no such peer. `:error` if the lookup encountered an error.
"""
def peer_by_name(dev, name), do: Tailscale.Native.peer_by_name(dev, name)
end