{ description = "Spread — dev shell: Go, air live-reload, and a local Postgres"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; outputs = { self, nixpkgs }: let systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forAll = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); in { packages = forAll (pkgs: { default = pkgs.buildGoModule { pname = "spread"; version = "0.1.0"; src = ./.; vendorHash = "sha256-YaOsSrH0pG+H+9ntjcyRjUgA6l0wgcg7PksrerMfyGg="; subPackages = [ "." ]; ldflags = [ "-s" "-w" ]; }; }); # Import into a NixOS host and set services.spread.{domain,acmeEmail}. nixosModules.default = { config, lib, pkgs, ... }: let cfg = config.services.spread; in { options.services.spread = { enable = lib.mkEnableOption "Spread BOM re-quoting app"; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.system}.default; description = "The spread package to run."; }; domain = lib.mkOption { type = lib.types.str; example = "spread.example.com"; description = "Public hostname served over HTTPS."; }; acmeEmail = lib.mkOption { type = lib.types.str; description = "Contact email for Let's Encrypt."; }; port = lib.mkOption { type = lib.types.port; default = 8137; }; mailBackend = lib.mkOption { type = lib.types.enum [ "log" "smtp" "sendgrid" ]; default = "log"; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Secrets file (SMTP_*, SENDGRID_API_KEY, MAIL_FROM); kept out of the Nix store."; }; }; config = lib.mkIf cfg.enable { users.users.spread = { isSystemUser = true; group = "spread"; }; users.groups.spread = { }; services.postgresql = { enable = true; ensureDatabases = [ "spread" ]; ensureUsers = [{ name = "spread"; ensureDBOwnership = true; }]; }; systemd.services.spread = { description = "Spread app"; after = [ "network.target" "postgresql.service" ]; wants = [ "postgresql.service" ]; wantedBy = [ "multi-user.target" ]; environment = { PORT = toString cfg.port; DATABASE_URL = "postgresql:///spread?host=/run/postgresql"; SPREAD_BASE_URL = "https://${cfg.domain}"; SPREAD_SECURE_COOKIES = "1"; MAIL_BACKEND = cfg.mailBackend; }; serviceConfig = { ExecStart = "${cfg.package}/bin/spread"; User = "spread"; Group = "spread"; Restart = "on-failure"; NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; } // lib.optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; }; }; security.acme = { acceptTerms = true; defaults.email = cfg.acmeEmail; }; services.nginx = { enable = true; recommendedProxySettings = true; recommendedTlsSettings = true; virtualHosts.${cfg.domain} = { enableACME = true; forceSSL = true; locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; }; }; networking.firewall.allowedTCPPorts = [ 80 443 ]; }; }; devShells = forAll (pkgs: { default = pkgs.mkShell { packages = [ pkgs.go pkgs.air pkgs.postgresql_17 ]; # A throwaway Postgres lives under the project dir so `air` can run the # full app (accounts) without touching any real database. shellHook = '' # Keep the dev Postgres OUT of the project tree — a unix socket in # $PWD breaks `nix build`/`nix develop` (Nix can't copy a socket). export SPREAD_STATE="''${XDG_STATE_HOME:-$HOME/.local/state}/spread" export PGDATA="$SPREAD_STATE/pgdata" export PGHOST="$SPREAD_STATE/sock" export PGUSER=postgres export DATABASE_URL="postgres://postgres@/spread?host=$PGHOST" # Dev-forgiving: serve the public demo even if Postgres isn't running. export SPREAD_DB_OPTIONAL=1 # Invite / password-reset emails print to the server log in dev. export MAIL_BACKEND=log export SPREAD_BASE_URL="http://localhost:8137" pg-start() { mkdir -p "$PGHOST" [ -d "$PGDATA" ] || initdb -D "$PGDATA" -U postgres --auth=trust >/dev/null pg_ctl -D "$PGDATA" -o "-k $PGHOST -c listen_addresses=" -w start createdb spread 2>/dev/null && echo "created database 'spread'" || true } pg-stop() { pg_ctl -D "$PGDATA" stop -m fast; } spread-admin() { SPREAD_ADMIN_PASSWORD="''${2:-changeme123}" go run . init-admin "''${1:-admin@spread.test}" } echo "spread dev shell" echo " air — run with live reload (rebuilds on .go/.html/.js/.sql change)" echo " pg-start — start the local dev Postgres (needed for /login /admin /app)" echo " pg-stop — stop it" echo " spread-admin — create/reset the admin user" ''; }; }); }; }