Incus (required for triggering deployments)
The default deploy config targets Incus containers on localhost via
core/setups/tf-incus-hosts.nix. Incus is a NixOS system service; add the
following to your NixOS configuration and run nixos-rebuild switch:
virtualisation.incus = {
enable = true;
preseed = {
config."core.https_address" = ":8443";
networks = [
{
name = "incusbr0";
type = "bridge";
config = {
"ipv4.address" = "10.0.100.1/20";
"ipv4.dhcp.ranges" = "10.0.100.2-10.0.100.254";
"ipv4.nat" = "true";
"ipv6.address" = "none";
};
}
];
storage_pools = [
{
name = "default";
driver = "dir";
}
];
profiles = [
{
name = "default";
devices = {
eth0 = {
type = "nic";
name = "eth0";
network = "incusbr0";
};
root = {
type = "disk";
path = "/";
pool = "default";
};
};
}
];
};
};
# Incus maps container uids/gids into a host sub-range; without these
# /etc/subuid + /etc/subgid entries `incusd forkstart` fails with
# `newuidmap: cannot open /etc/subuid`. Matches core/components/hosting-provider/incus.nix.
users.users.root.subUidRanges = [ { startUid = 1000000; count = 1000000000; } ];
users.users.root.subGidRanges = [ { startGid = 1000000; count = 1000000000; } ];
networking.firewall.allowedTCPPorts = [ 8443 ];
networking.nftables.enable = true;
After activation, issue a trust token for the Terraform provider:
incus config trust add --name terraform
Which Incus host the API targets is a deployment-method setting, not an env
var: it defaults to localhost, a HOSTING_CONFIG host leaf pins it, and an
operator submission overrides it at deploy time as TF_VAR_host. If Incus is
not available, set DEPLOY_CONFIG_FILE to a custom config targeting reachable
machines instead.
Provisioning the deployer's incus credentials
Even with virtualisation.incus enabled, a deploy fails unless a base image
has been uploaded under the alias the deploy looks up -- data "incus_image" "base" then errors with Image alias not found -- and the deploy gets
three TF_VAR_* variables: TF_VAR_token, TF_VAR_image_fingerprint and
TF_VAR_incus_config_dir.
For the local-dev case (incus on the same host as the api), run the
dev-tf-incus-bootstrap program of core once. It holds the incus +
OpenTofu recipe: it mints two trust tokens (deployer-image,
deployer-hosts), uploads the generic NixOS base images via
dev-tf-incus-image, and writes the TF_VAR_* env file to OUT_TF_ENV. It
reads the incus socket, so run it as root or as a member of the
incus-admin group.
On a colocated node, the fediversity-api service reads the file when
services.fediversity-api.environmentFiles names it, for example
[ "-/var/lib/fediversity-api/tf-env" ]:
doas env OUT_TF_ENV=/var/lib/fediversity-api/tf-env INCUS_CONFIG_DIR=/etc/incus-client \
nix run .#dev-tf-incus-bootstrap
doas systemctl restart fediversity-api.service
On a developer workstation, write the file to the XDG state path, which
api-http-stack sources on start:
OUT_TF_ENV="${XDG_STATE_HOME:-$HOME/.local/state}/fediversity/tf-env" \
INCUS_CONFIG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/fediversity/incus-config" \
nix run .#dev-tf-incus-bootstrap
dev-tf-incus-bootstrap is a local-dev convenience (api and windmill on
one host). The machine-agnostic REMOTE path -- deploy runners and incus on
separate machines -- is the documented flow in notes/bootstrap.md: the
base-image upload and the -tf-incus-all deploy read the incus host from
TF_VAR_host (the deploy-runtime override the hosts graph already uses,
issue #723) plus TF_VAR_incus_config_dir / TF_VAR_token, so the same
commands serve local dev (host defaults to localhost) and a remote
provider (TF_VAR_host=<ip>). The image's tofu state is backend-agnostic (a
keyed local file by default), so a remote upload needs no state backend and
a re-upload replaces the image in place.
For a multi-node provider, the runner nodes get their TF_VAR_token from
the incus host through OpenBao rather than a local file (the #1013
follow-up, gap #38). The incus host's NixOS module
(core/components/hosting-provider/incus.nix) mints a one-time trust token
pair per deploy-runner node (windmill worker, api) at activation --
authenticated to the central OpenBao by its TPM-attested SVID -- and
publishes each to its incus-deploy vars KV instance under a per-runner
subpath. Each runner's openbao agent reads back ITS OWN token and hands it
to the tf provider, which self-generates its client.crt/client.key
LOCALLY and self-fetches the servercert, so only the one-time token travels.
This is what lets windmill and api run on nodes separate from the incus
host.