1
0
Fork
You've already forked sower
0
Deployment and lifecycle management for Nix
  • Elixir 78.7%
  • Nix 10.4%
  • Rust 7.3%
  • HTML 2.7%
  • JavaScript 0.4%
  • Other 0.5%
2026年07月05日 21:07:07 -04:00
.forgejo/workflows ci: run mix format check inside nix 2026年07月05日 21:07:07 -04:00
.services refactor: rename SowerAgent to Garden 2026年03月22日 23:10:52 -04:00
apps cli: make the rust cli the sower front door 2026年07月04日 16:57:19 -04:00
bin cli: make the rust cli the sower front door 2026年07月04日 16:57:19 -04:00
config fix(nix): provide tailwind/esbuild binaries via path config 2026年06月12日 00:44:16 -04:00
docs garden: drive the admin socket from the sower CLI 2026年06月11日 00:17:33 -04:00
nix ci: run mix format check inside nix 2026年07月05日 21:07:07 -04:00
src cli: make the rust cli the sower front door 2026年07月04日 16:57:19 -04:00
tests cli: make the rust cli the sower front door 2026年07月04日 16:57:19 -04:00
.envrc cli: make the rust cli the sower front door 2026年07月04日 16:57:19 -04:00
.formatter.exs elixir: move to umbrella 2025年06月22日 16:14:11 -04:00
.gitignore chore: ignore dexter sqlite files 2026年04月18日 09:57:36 -04:00
.golangci.yml client: address staticcheck 2025年05月02日 23:34:02 -04:00
.iex-garden.exs feat: replace erlexec with Rexec, a lightweight Rust port wrapper 2026年03月25日 09:05:29 -04:00
.iex-server.exs feat: replace erlexec with Rexec, a lightweight Rust port wrapper 2026年03月25日 09:05:29 -04:00
.iex.exs feat: replace erlexec with Rexec, a lightweight Rust port wrapper 2026年03月25日 09:05:29 -04:00
.mcp.json dev: add systemd-analyze wrapper, update rules 2026年03月18日 00:01:00 -04:00
AGENTS.md agents: change commit guidance 2026年07月04日 16:57:18 -04:00
build.rs feat(cli): add sower seed subcommand backed by progenitor client (sow-192) 2026年05月20日 22:33:13 -04:00
Cargo.lock release: version 0.11.0-dev 2026年06月19日 13:08:36 -04:00
Cargo.toml release: version 0.11.0-dev 2026年06月19日 13:08:36 -04:00
CLAUDE.md chore: retire Go CLI and remove all Go from the repo 2026年05月20日 22:33:18 -04:00
dev-client-example.json breaking(policy)!: remove legacy subscription fields 2026年05月20日 22:33:02 -04:00
dev-server-example.json server: put s3 access key in a file 2026年02月27日 17:35:50 -05:00
flake.lock flake.lock: Update 2026年06月19日 15:50:29 -04:00
flake.nix cli: make the rust cli the sower front door 2026年07月04日 16:57:19 -04:00
justfile ci: audit elixir deps 2026年05月22日 00:09:30 -04:00
LICENSE.txt license agpl 3.0 2024年03月29日 11:09:44 -04:00
mix.exs server: drop faker, fork typedstruct 2026年06月19日 13:39:43 -04:00
mix.lock server: drop faker, fork typedstruct 2026年06月19日 13:39:43 -04:00
openapi.json release: version 0.10.0-rc0 2026年05月21日 23:29:21 -04:00
README.md feat(policy): treat absent window.days as every day 2026年05月20日 22:33:00 -04:00
VERSION release: version 0.11.0-dev 2026年06月19日 13:08:36 -04:00

Sower

Sower is a deployment and lifecycle management tool for Nix based configurations, including NixOS and Home-Manager.

With sower we sow the seeds of our systems.

  • A seed is an extra bundle of metadata for an artifact path, e.g. a Nix store path.
  • Seed metadata includes a set of tags, with git and user-provided tags.
  • A garden defines seeds they want to subscribe to.
  • Seeds are submitted to a server to be used for deployments.

WARNING This project is experimental and is not recommended for production installation. One of the goals is never break deployments, but it is not guaranteed yet. I'm only using this in a homelab with approximately a dozen gardens. This means the risk to me of breaking deployments is moderately low.

I'd love for others to get value out of what I'm building here. Please reach out if you're a user, I want to chat. :)

Installation

Read the NixOS modules for the server and the garden. There is an example in nix/tests/e2e.nix

  1. An example server config exists in nix/tests/e2e.nix
  2. An example garden config is below.

Good luck, everyone's counting on you.

Components

  • Server including Phoenix LiveView web interface.
  • Always-on end-system daemon (Garden) with bi-directional communication to the Server over real-time WebSocket connection.
  • Activator used by the Garden for running limited, specific actions as root, over a systemd initiated socket.
  • CLI for submitting seeds including a full code to submitted builder.

Gardens

Gardens are an always on client, which have full control over what the seeds from the server can or will do. Through the garden's configuration, you can determine what the garden should subscribe to and what should happen when working with seeds for deployment.

Subscriptions

Subscriptions are the main controls for how systems are deployed. They include:

  • A set of seed tag matching rules
  • Schedule in cron format for pull-based deployments
  • A policy controlling which deployment actions are permitted, and when

Policy

Each subscription carries a policy map of named rules. Each rule permits a set of actions (stage, activate, restart) for a set of triggers (manual, scheduled, realtime, poll_on_connect), optionally constrained to a time window. Multiple rules are OR-ed; the highest-disruption permitted action wins. See docs/spec-deployment-policy.org for the full specification.

Example garden config

{
 age.secrets.sower-api-token = {
 file = cfg.access_token_secret;
 owner = "sower-garden";
 };
 services.sower = {
 activator = {
 package = inputs.sower-next.packages.${pkgs.stdenv.hostPlatform.system}.activator;
 allowedGroups = [ "users" ];
 };
 garden = {
 enable = true;
 accessTokenFile = config.age.secrets.sower-api-token.path;
 package = inputs.sower-next.packages.${pkgs.stdenv.hostPlatform.system}.garden;
 settings = {
 access_token_file = config.age.secrets.sower-api-token.path;
 endpoint = "http://localhost:7150";
 subscriptions = {
 ${config.networking.hostName} = {
 seed_name = config.networking.hostName;
 seed_type = "nixos";
 rules = [ "git_branch=main" ];
 # https://hexdocs.pm/crontab/cron_notation.html
 schedule = "0 3 * * *";
 timezone = "America/New_York";
 policy = {
 # Allow manual activations anytime.
 manual = {
 actions = [ "activate" ];
 triggers = [ "manual" ];
 };
 # Scheduled / poll-on-connect deploys may stage, activate, and
 # reboot — but only inside the maintenance window.
 maintenance = {
 actions = [ "stage" "activate" "restart" ];
 triggers = [ "scheduled" "poll_on_connect" ];
 window = {
 time_start = "02:00";
 time_end = "04:00";
 };
 };
 };
 };
 };
 };
 };
 };
 users.users.adam.extraGroups = [ "sower-activator" ];
};

Disclaimer

This project uses coding agents for assisting and producing code.