Aker.Gate (Aker v0.1.0)

Copy Markdown View Source

Whether this install may run. The gate checks the stored licence at boot and every hour, holds :licensed or {:locked, reason} where every reader finds it without a call, and renews the licence in its term's last week.

The host starts one gate in its supervision tree, with the options below, and decides what a lock blocks: it reads status/1 or locked?/1 on each page or job, and the gate calls :on_lock and :on_unlock when the install locks or unlocks. A gate name no check has run under reads {:locked, :not_checked}, so a host that forgets to start the gate runs locked, not free.

Every function takes the gate's options, so a host can build them from runtime config at each call. The process only runs the boot check and the hourly tick.

load/3, activate/3 and refresh/2 trust the host to have checked who may call them; the actor passes to the store untouched. renew/1 stores as :system.

A running install stays licensed through a failure of its own machine (a store it cannot read, a missing machine id, a slow chip): only the licence itself locks a running install. A booting install stays locked until the check passes.

Summary

Functions

Activates with code as actor: sends this install's request to the licensing server and loads the licence that comes back. A code that is not 1 to 64 base32 or base64url characters makes no call.

Runs the boot check now and answers the status it holds.

This install's key and machine id hash, or the reason they are missing. Makes the install key when there is none.

The licence in force, or nil when none is stored or the store cannot be read.

Checks bytes as this install's licence and stores it as actor. The install unlocks when it passes, and :on_unlock runs when it was locked.

Whether the install is locked.

The options every function takes

The hourly check: the boot check again, which also moves the latest time seen. A running install it finds locked by the licence itself calls :on_lock; a locked install it finds licensed calls :on_unlock.

Asks for this term's licence with the current seats, as actor, and loads it.

Asks for the next term's licence and loads it, as :system.

The activation request to send the licensing server, as the file's bytes.

Forgets the status under name, which then reads {:locked, :not_checked}.

Starts the gate: the boot check now, then a tick each :check_every.

The install's licence status under name.

One tick: renews when the licence's term ends within :renew_within, whatever the lock state, so a paid renewal unlocks an install whose term ended, then runs recheck/1. One renewal call per tick, with no retry: the next tick is the retry.

Types

status()

@type status() :: :licensed | {:locked, atom()}

Functions

activate(opts, code, actor)

@spec activate(keyword(), String.t(), term()) :: {:ok, map()} | {:error, term()}

Activates with code as actor: sends this install's request to the licensing server and loads the licence that comes back. A code that is not 1 to 64 base32 or base64url characters makes no call.

check(opts)

@spec check(keyword()) :: status()

Runs the boot check now and answers the status it holds.

context(opts)

@spec context(keyword()) ::
  {:ok, %{install_key: {module(), keyword()}, machine_id_hash: String.t()}}
  | {:error, atom()}

This install's key and machine id hash, or the reason they are missing. Makes the install key when there is none.

current(opts)

@spec current(keyword()) :: map() | nil

The licence in force, or nil when none is stored or the store cannot be read.

load(opts, bytes, actor)

@spec load(keyword(), binary(), term()) :: {:ok, map()} | {:error, term()}

Checks bytes as this install's licence and stores it as actor. The install unlocks when it passes, and :on_unlock runs when it was locked.

locked?(name)

@spec locked?(atom()) :: boolean()

Whether the install is locked.

options()

@spec options() :: NimbleOptions.t()

The options every function takes:

  • :name (atom/0) - Required. The gate's name; its status is kept under it.

  • :product (String.t/0) - Required. The product this app is.

  • :product_version (String.t/0) - Required. The app's version.

  • :root_keys (list of String.t/0) - Required. The trusted Ed25519 root public keys, raw (Aker.PublicKeys.decode/1).

  • :licence_dir - The folder that holds the install key.

  • :machine_id_path - The host's machine id file (default /etc/machine-id).

  • :require_mount (boolean/0) - Refuse with :machine_id_not_mounted unless :machine_id_path is given, for a container whose own /etc/machine-id every copy shares. The default value is false.

  • :store (tuple of atom/0, keyword/0 values) - Required. {module, opts} of an Aker.Store.

  • :server (keyword/0) - Aker.Client's server options. The default value is [].

  • :on_lock (function of arity 0) - Called when a running install locks.

  • :on_unlock (function of arity 0) - Called when a locked install unlocks.

  • :check_every (pos_integer/0) - Milliseconds between ticks. The default value is 3600000.

  • :renew_within (pos_integer/0) - Seconds before the term ends when each tick renews. The default value is 604800.

  • :tpm_device (String.t/0) - The TPM chip's device. The default value is "/dev/tpmrm0".

  • :tpm_options (keyword/0) - Aker.InstallKey.Tpm options. The default value is [].

  • :settings (keyword/0) - The setting names a log line tells the operator to set, for :licence_dir, :machine_id_path and :server_url (default each option's own name). The default value is [].

  • :now (struct of type DateTime) - The time to check against, for tests only: a host that sets it freezes the gate's clock.

recheck(opts)

@spec recheck(keyword()) :: status()

The hourly check: the boot check again, which also moves the latest time seen. A running install it finds locked by the licence itself calls :on_lock; a locked install it finds licensed calls :on_unlock.

refresh(opts, actor)

@spec refresh(keyword(), term()) :: {:ok, map()} | {:error, term()}

Asks for this term's licence with the current seats, as actor, and loads it.

renew(opts)

@spec renew(keyword()) :: {:ok, map()} | {:error, term()}

Asks for the next term's licence and loads it, as :system.

request(opts)

@spec request(keyword()) :: {:ok, binary()} | {:error, atom()}

The activation request to send the licensing server, as the file's bytes.

reset(name)

@spec reset(atom()) :: :ok

Forgets the status under name, which then reads {:locked, :not_checked}.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts the gate: the boot check now, then a tick each :check_every.

status(name)

@spec status(atom()) :: status()

The install's licence status under name.

tick(opts)

@spec tick(keyword()) :: status()

One tick: renews when the licence's term ends within :renew_within, whatever the lock state, so a paid renewal unlocks an install whose term ended, then runs recheck/1. One renewal call per tick, with no retry: the next tick is the retry.