Summary

nixpush is a NixOS notification-dispatch mechanism that separates what sends an alert from where the alert goes. Modules and scripts call nixpush send --channel alerts with a message, and one NixOS-level channel table decides which provider delivers it.

The core is stateless by default: exactly one synchronous delivery attempt per send, reported through a three-class exit code, with no daemon and no queue. Two per-channel opt-ins exist without changing that default: a crash-safe on-disk spool (durable = true) and a fallback destination for unseal failures or provider hard rejections.

What it is

  • A NixOS option schema (nixosModules.core) defining named channels bound to pluggable providers, rendered to /etc/nixpush/channels.json.
  • A synchronous nixpush send CLI (package nixpush) performing one delivery attempt per invocation.
  • A first-party ntfy provider (nixosModules.ntfy-provider, package nixpush-provider-ntfy) implemented with curl and jq.
  • A Nix-level mkSendCommand helper exposed both as flake lib and as config.nixpush.lib.mkSendCommand.
  • Declared checks (checks/assertions.nix, checks/behavior.nix) and supporting material (docs/faq.md, docs/rationale.md, CONTRIBUTING.md provider contract).

It is not a notification service itself, nor a queue, scheduler, or dashboard. Providers are small executables owned outside the core.

How it works

A channel names a provider and its parameters; a provider is any executable satisfying the stdin, environment, and exit-code contract documented in CONTRIBUTING.md. The referenced revision’s README states the contract as JSON-in and exit-code-out, so community providers can be written in any language.

Evaluation renders the channel table to JSON on the target host. At runtime nixpush send reads that file, resolves the named channel (or nixpush.defaultChannel), executes the provider once, and exits with one of three classes. Nothing listens, retries, or persists unless the channel sets durable = true, which adds the file-backed spool, or sets a fallback destination used when a secret fails to unseal or the primary provider hard-rejects.

The flake exposes nixosModules.default as core plus the ntfy provider for single-provider consumers; multi-provider setups import core and add provider modules explicitly.

How to configure it

Import the module bundle for the wanted provider set, then declare channels under the nixpush.* option surface observed in modules/default.nix:

  • nixpush.channels.<name> — provider binding plus per-channel parameters.
  • nixpush.providers.<name> — provider registration surface.
  • nixpush.defaultChannel — channel used when send omits --channel.
  • nixpush.spoolDir — spool location for durable channels.

The ntfy provider adds its own surface (see modules/providers/ntfy.nix), including server, topic, and token-file settings. Callers then use nixpush send --channel <name> or the mkSendCommand helper; repointing channels.<name>.provider changes delivery without touching callers. Review docs/faq.md and docs/rationale.md for the numbered design decisions referenced from code comments.

Tutorial

This example uses invented values against the real option interface from the referenced revision. The interface shape below was evaluated with the actual nixosModules.default bundle (core plus ntfy provider) at the pinned revision using synthetic values only; no message was delivered.

# Synthetic example: invented server, topic, and channel choices.
{
  imports = [ inputs.nixpush.nixosModules.default ];
 
  nixpush = {
    enable = true;
    defaultChannel = "alerts";
    channels.alerts.provider = "ntfy";
    ntfy = {
      enable = true;
      serverUrl = "https://ntfy.example";
      topic = "demo-alerts";
    };
  };
}

Importing the bundle alone registers nothing: delivery only exists because ntfy.enable is set and the channel names the registered "ntfy" provider. Sending from a script uses the same channel name regardless of backend:

# Synthetic example: public placeholder only.
nixpush send --channel alerts "demo message"

Treat the exit code as the delivery verdict: the referenced revision documents three outcome classes rather than a boolean.

Evidence and limits

This page is bound to public revision fe83da882b39aa8099441e4435a69ab451e6d0b0. That tree contains the flake outputs, modules/default.nix, modules/providers/ntfy.nix, pkgs/nixpush.nix, pkgs/nixpush-provider-ntfy.nix, lib/default.nix, lib/nixpush.sh, checks/assertions.nix, checks/behavior.nix, and the docs/ plus CONTRIBUTING.md material discussed above.

CNIX has not independently established a successful evaluation, build, delivery, or runtime result for that revision. The tutorial interface above was evaluated against the real modules at that revision with synthetic values: the channel binding, default channel, and ntfy settings pass type checking and the default-channel assertion holds. No message was delivered. Durability behavior under crash conditions and fallback semantics are described from source structure, not from reproduced runs.

nixpush addresses notification dispatch only. Host-memory policy, remote access, compositor configuration, shell environment, file sharing, and tiny -VM server policy are covered by sibling Corbet Nix projects, each with its own revision-addressed concept. Its relationship to other Corbet Nix projects is currently a shared design direction, not evidence of runtime integration.