Skip to content

feat(dist): Nix flake + NixOS module #18

@pachev

Description

@pachev

Summary

Expose Mast as a Nix flake with a NixOS module so a Nix user can add Mast to their system config and have a working install. Targets x86_64-linux and aarch64-linux.

Why

  • Maintainer wants `nix run` / NixOS module support for personal use.
  • Nix users tend to file high-quality issues and stay on the latest version, so the support cost is low relative to the signal.
  • Pairs naturally with the LXC track: same release binary, different packaging.

Approach

Build: `beamPackages.mixRelease` + `mixFodDeps` (FOD).

  • One `sha256` for the whole deps tree. Recomputes on `mix.lock` bump, which is rare. Simplest to maintain.
  • Skip `mix2nix`/`deps_nix` until rebuild times actually hurt. They give per-dep caching but add a regeneration step on every lock change.
  • Asset build: `buildNpmPackage` (or Tailwind/esbuild standalone if we stay JS-free) feeding `mixRelease`'s `preInstall` with `mix assets.deploy && mix release`.

Pin: `nixpkgs-unstable`.

  • Elixir 1.19 + OTP 28 are very new. Stable nixpkgs (25.11) still ships 1.18 / OTP 27.
  • Document the unstable pin in the README so users aren't surprised.
  • Alternative: `zoedsoupe/elixir-overlay` for a more controlled BEAM version pin if unstable drift becomes annoying.

Flake outputs

```nix
{
packages.${system}.default = mast;
apps.${system}.default = { type = "app"; program = "${mast}/bin/mast"; };
nixosModules.default = ./nix/module.nix;
devShells.${system}.default = ...; # mix, postgres, elixir, erlang
}
```

Multi-arch via `flake-utils.lib.eachDefaultSystem` for x86_64-linux + aarch64-linux. Skip Darwin in the package output; dev shell can be Darwin-friendly.

NixOS module surface

```nix
services.mast = {
enable = mkEnableOption "Mast fleet dashboard";
host = mkOption { type = str; default = "localhost"; };
port = mkOption { type = port; default = 4000; };
environmentFile = mkOption {
type = path;
description = "Path to file containing SECRET_KEY_BASE, DATABASE_URL, etc.";
};
database.createLocally = mkOption { type = bool; default = true; };
selfManage = mkOption {
type = bool;
default = false;
description = "Auto-enroll this host as Mast's first managed server. Off by default for Nix; the LXC template sets this true.";
};
};
```

Module behavior:

  • Emits `systemd.services.mast` with `ExecStart = "${cfg.package}/bin/mast start"`, `User = "mast"`, `DynamicUser = true`, `EnvironmentFile = cfg.environmentFile`, `After = [ "postgresql.service" ]`.
  • When `database.createLocally`: `services.postgresql.enable = true`, `ensureDatabases = [ "mast" ]`, `ensureUsers = [ { name = "mast"; ensureDBOwnership = true; } ]`.
  • When `selfManage`: post-activation script that does the same `Mast.Bootstrap.self_enroll!` call as the LXC bootstrap unit (so the logic stays in Elixir, not duplicated in Nix).
  • Migrations: `preStart` runs `bin/mast eval 'Mast.Release.migrate()'`.

nix run

Useful for:

  • `nix run .#mast -- remote` — IEx into a running release (when one's running).
  • `nix run .#mast -- eval '...'` — one-shot eval, e.g. for migrations on a manually-managed install.

Not the deploy story. Long-running service = use the NixOS module. Say this in the README.

Scope

In:

  • `flake.nix` with `mixRelease` build, multi-arch outputs, dev shell, NixOS module.
  • `nix/module.nix` with the surface above.
  • `mix.lock` hash recomputation note in `docs/install/nix.md`.
  • CI step: `nix build .#default` on push.

Out:

  • Submitting to nixpkgs proper (stay flake-only for v1; revisit once the maintainer wants the broader audience).
  • Darwin package output.
  • `deps_nix` / `mix2nix` migration.

Open questions

  • `Mast.Release` module: does it exist yet for handling `migrate`/`rollback`/`seed` from a release binary? If not, add it as part of this work (it's standard Phoenix-in-release boilerplate and will also serve the LXC track).
  • Secret handling: do we recommend `agenix` / `sops-nix` in docs, or stay neutral and just point at `environmentFile`? Neutral is fine for v1.
  • Default `database.createLocally`: `true` matches the "just works" feel and aligns with the LXC default. Operators who run external Postgres set it false and provide `DATABASE_URL` themselves.

Related

  • Companion issue: Proxmox LXC template (shares the `Mast.Bootstrap` module and `Mast.Release` migration helpers).
  • ADR 0005 unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    distributionPackaging and install paths (LXC, Nix, Docker, etc.)enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions