headplane/docs/Nix.md
Igor Ramazanov 03a7e51384
feat: add a nix flake (#132)
* feat: wip `nix`

Add initial code to be used when working with `nix` and `NixOS`.

* a Nix flake
* building a package
* a NixOS module

* feat: build `hp_agent` with `nix`

* feat: add `hp_agent` as a flake output

* feat: nix: start `headplane` after `headscale`

* feat: do not rely on `git` for versioning

It causes lots of pain when building with `nix` for a local `flake.nix`.

Not sure if it's a good general solution:
* now it requires a manual step of updating `./version` on each release.
* we're losing commit hash abbreviation, like `0.5.3-5-gbe5a291`

I guess, this can be fixed by installing git-pre-commit-hook, but even
then it'd be wrong, because the output of `git describe --tags --always`
won't be the same before and after commit.

* feat: include `hp_agent` to the NixOS module

* fix: version error message

* fix: use relative path imports in `nix`

* fix: NixOS module: generate `/etc/headplane/config.yaml` from `services.headplane.settings`

* fix: NixOS module: allow passing `bool` in `services.headplane.settings.*`

* fix: NixOS module: fix `/etc/headplane/config.yaml` generation

* docs: add Nix/NixOS docs

* feat: nix: read version from `package.json`

* fix: nix: fix `agent` env vars configuration

* feat: nix: add `services.headplane.agent.debug` option

* fix: delete unnecessary `version` file

* fix: nix: remove unnecessary `sed` substitutions

A left over from previous versions.
See: https://github.com/tale/headplane/issues/95#issue-2807487849

* feat: nix: do not hardcode `headplane-agent` configuration environment variables

To make the module more flexible and to reduce the dependency on the
config API.

* docs: improve `Nix` documentation

* Reflect recent changes.
* Link `Nix` in README

* feat: nix: setup Nix CI

* feat: nix: CI without depending on flakehub.com

* chore: begin bundling all deps into the server

* fix: loosen headscale config validation

* fix: navigate back to machines page on node deletion

* fix: slice off trailing slash if present on headscale url

* feat: switch to a central singleton handler

This also adds support for Headscale TLS installations

* fix: shamefully-hoist dependencies

* fix: handle localized number fields

* fix: revert dependency bundling for now

* chore: cleanup and remove from readme for now

---------

Co-authored-by: Aarnav Tale <aarnavtale@icloud.com>
2025-03-18 00:31:37 -04:00

4.6 KiB

Nix

flake.nix provided:

$ nix flake show . --all-systems
git+file:///home/igor/personal/headplane?ref=refs/heads/nix&rev=2d78a95a0648a3778e114fb246ea436e96475d62
├───devShell
│   ├───aarch64-darwin: development environment 'headplane'
│   ├───x86_64-darwin: development environment 'headplane'
│   └───x86_64-linux: development environment 'headplane'
├───formatter
│   ├───aarch64-darwin: package 'alejandra-3.1.0'
│   ├───x86_64-darwin: package 'alejandra-3.1.0'
│   └───x86_64-linux: package 'alejandra-3.1.0'
├───nixosModules
│   └───headplane: NixOS module
├───overlays
│   └───default: Nixpkgs overlay
└───packages
    ├───aarch64-darwin
    │   ├───headplane: package 'headplane-0.5.3-SNAPSHOT'
    │   └───headplane-agent: package 'hp_agent-0.5.3-SNAPSHOT'
    ├───x86_64-darwin
    │   ├───headplane: package 'headplane-0.5.3-SNAPSHOT'
    │   └───headplane-agent: package 'hp_agent-0.5.3-SNAPSHOT'
    └───x86_64-linux
        ├───headplane: package 'headplane-0.5.3-SNAPSHOT'
        └───headplane-agent: package 'hp_agent-0.5.3-SNAPSHOT'

NixOS module options

Defined as services.headplane.*, check the ./nix/ directory for details.

Usage

  1. Add the github:tale/headplane flake input.
  2. Import a default overlay to add pkgs.headplane and pkgs.headplane-agent.
  3. Import NixOS module for services.headplane.*.
# Your flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    headplane = {
      url = "github:igor-ramazanov/headplane/nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = {
    nixpkgs,
    headplane,
    ...
  }: {
    nixosConfigurations.MY_MACHINE = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        # provides `services.headplane.*` NixOS options.
        headplane.nixosModules.headplane
        {
          # provides `pkgs.headplane` and `pkgs.headplane-agent`.
          nixpkgs.overlays = [ headplane.overlays.default ];
        }
        {
          {config, pkgs, ...}:
          let
            format = pkgs.formats.yaml {};

            # A workaround generate a valid Headscale config accepted by Headplane when `config_strict == true`.
            settings = lib.recursiveUpdate config.services.headscale.settings {
              acme_email = "/dev/null";
              tls_cert_path = "/dev/null";
              tls_key_path = "/dev/null";
              policy.path = "/dev/null";
              oidc.client_secret_path = "/dev/null";
            };

            headscaleConfig = format.generate "headscale.yml" settings;
          in {
            services.headplane = {
              enable = true;
              agent = {
                # As an example only.
                # Headplane Agent hasn't yet been ready at the moment of writing the doc.
                enable = true;
                settings = {
                  HEADPLANE_AGENT_DEBUG = true;
                  HEADPLANE_AGENT_HOSTNAME = "localhost";
                  HEADPLANE_AGENT_TS_SERVER = "https://example.com";
                  HEADPLANE_AGENT_TS_AUTHKEY = "xxxxxxxxxxxxxx";
                  HEADPLANE_AGENT_HP_SERVER = "https://example.com/admin/dns";
                  HEADPLANE_AGENT_HP_AUTHKEY = "xxxxxxxxxxxxxx";
                };
              };
              settings = {
                server = {
                  host = "127.0.0.1";
                  port = 3000;
                  cookie_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                  cookie_secure = true;
                };
                headscale = {
                  url = "https://example.com";
                  config_path = "${headscaleConfig}";
                  config_strict = true;
                };
                integration.proc.enabled = true;
                oidc = {
                  issuer = "https://oidc.example.com";
                  client_id = "headplane";
                  client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                  disable_api_key_login = true;
                  # Might needed when integrating with Authelia.
                  token_endpoint_auth_method = "client_secret_basic";
                  headscale_api_key = "xxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                  redirect_uri = "https://oidc.example.com/admin/oidc/callback";
                };
              };
            };
          }
        }
      ];
    };
  };
}