├── .envrc ├── .github ├── CODE_OF_CONDUCT.md └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dev ├── flake.lock └── flake.nix ├── doc ├── flake.lock ├── flake.nix ├── index.md └── index.yaml ├── example ├── flake.lock └── flake.nix ├── flake.nix ├── justfile └── nix ├── flake-module.nix ├── lib.nix └── process-compose ├── cli.nix ├── default.nix ├── settings ├── command.nix ├── default.nix ├── environment.nix ├── probe.nix └── process.nix └── test.nix /.envrc: -------------------------------------------------------------------------------- 1 | watch_file dev/flake.nix 2 | use flake ./dev 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | This document, adapted from [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct/), provides community guidelines for a safe, respectful, productive, and collaborative place for any person who is willing to contribute to the associated project. It applies to all “collaborative space”, which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.). 2 | 3 | - Participants will be tolerant of opposing views. 4 | - Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. 5 | - When interpreting the words and actions of others, participants should always assume good intentions. 6 | - Behaviour which can be reasonably considered harassment will not be tolerated. 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-14] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: DeterminateSystems/nix-installer-action@main 16 | - name: Install omnix 17 | run: nix --accept-flake-config profile install "github:juspay/omnix" 18 | - run: om ci 19 | flake-parts-linkCheck: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: DeterminateSystems/nix-installer-action@main 24 | - run: nix build github:hercules-ci/flake.parts-website#checks.x86_64-linux.linkcheck --override-input process-compose-flake . 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.direnv 2 | 3 | /example/data.sqlite* 4 | /example/result 5 | 6 | /test/data.sqlite 7 | /test/result.txt 8 | 9 | /result 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for process-compose-flake 2 | 3 | ## Unreleased 4 | 5 | - New features 6 | - #81, #84: Support for specifying process-compose global CLI options 7 | - **Breaking changes**: 8 | - `preHook` and `postHook` are now inside `cli` module. 9 | - Old options `httpServer` and `tui` were removed; users should use the new `cli` module to set all process-compose cli global options. TUI can be disabled using `cli.environment.PC_DISABLE_TUI = true;` 10 | - ~~#18: Add `testScript` option for adding flake checks based on nixosTest library.~~ 11 | - #39: Allow `test` process to act as a test, which then gets run as part of flake checks. 12 | - #55: Add `lib` flake output - library of useful functions 13 | - #80: Add `evalModules`, to use process-compose-flake without flake-parts 14 | - New options 15 | - #52: Add `is_foreground` option 16 | - ~~#54: Add `apiServer` option to control REST API server~~ 17 | - ~~$60: Add `httpServer.{enable, port, uds}` options to control the HTTP server.~~ 18 | - #56: Add `preHook` and `postHook` for running commands before and after launching process-compose respectively. 19 | - #67: Add `ready_log_line` 20 | - #226: Add `availability.exit_on_skipped` 21 | - #77: Add `is_tty` 22 | - Notable changes 23 | - #58: Obviate IFD by switching to JSON config 24 | - Fixes 25 | - #19: Reintroduce the `shell` option so process-compose doesn't rely on user's global bash (which doesn't exist nixosTest runners). 26 | - #22: `command` option is no longer wrapped in `writeShellApplication`. 27 | - #20: Fix definiton of `probe.exec` 28 | - #53: Make process submodule a proper submodule (allowing use of `imports` etc) 29 | 30 | 31 | ## 0.1.0 (Jun 12, 2023) 32 | 33 | - Initial release 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Platonic.Systems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://nixos.zulipchat.com/#narrow/stream/414022-process-compose-flake) 2 | 3 | # process-compose-flake 4 | A [flake-parts](https://github.com/hercules-ci/flake-parts) module for [process-compose](https://github.com/F1bonacc1/process-compose). 5 | 6 | ## Documentation 7 | 8 | https://community.flake.parts/process-compose-flake 9 | 10 | ## Contributing 11 | 12 | Please run [`nix --accept-flake-config run github:juspay/omnix ci`](https://omnix.page/om/ci.html) on a NixOS machine to run the full suite of tests before pushing changes to the main branch. Our CI (Github Actions) cannot do this yet. 13 | 14 | ## Discussion 15 | 16 | - [Zulip](https://nixos.zulipchat.com/#narrow/stream/414022-process-compose-flake) 17 | 18 | ## Related projects 19 | 20 | - [`services-flake`](https://github.com/juspay/services-flake): NixOS-like services built on top of process-compose-flake. 21 | - [`proc-flake`](https://github.com/srid/proc-flake): A similar module that uses a `Procfile`-based runner. It is less feature-rich, but [at times more reliable](https://github.com/Platonic-Systems/process-compose-flake/issues/30) than process-compose. 22 | -------------------------------------------------------------------------------- /dev/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1685662779, 9 | "narHash": "sha256-cKDDciXGpMEjP1n6HlzKinN0H+oLmNpgeCTzYnsA2po=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "71fb97f0d875fd4de4994dfb849f2c75e17eb6c3", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1709613862, 24 | "narHash": "sha256-mH+c2gFEzEe49lhUWJ0ieIaMaJ1W85E6G1xLm8ege90=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "311a4be96d940a0c673e88bd5bc83ea4f005cc02", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixpkgs-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs-lib": { 38 | "locked": { 39 | "dir": "lib", 40 | "lastModified": 1685564631, 41 | "narHash": "sha256-8ywr3AkblY4++3lIVxmrWZFzac7+f32ZEhH/A8pNscI=", 42 | "owner": "NixOS", 43 | "repo": "nixpkgs", 44 | "rev": "4f53efe34b3a8877ac923b9350c874e3dcd5dc0a", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "dir": "lib", 49 | "owner": "NixOS", 50 | "ref": "nixos-unstable", 51 | "repo": "nixpkgs", 52 | "type": "github" 53 | } 54 | }, 55 | "nixpkgs_2": { 56 | "locked": { 57 | "lastModified": 1680945546, 58 | "narHash": "sha256-8FuaH5t/aVi/pR1XxnF0qi4WwMYC+YxlfdsA0V+TEuQ=", 59 | "owner": "nixos", 60 | "repo": "nixpkgs", 61 | "rev": "d9f759f2ea8d265d974a6e1259bd510ac5844c5d", 62 | "type": "github" 63 | }, 64 | "original": { 65 | "owner": "nixos", 66 | "ref": "nixos-unstable", 67 | "repo": "nixpkgs", 68 | "type": "github" 69 | } 70 | }, 71 | "process-compose-flake": { 72 | "locked": { 73 | "lastModified": 1712910508, 74 | "narHash": "sha256-mCF2EcBoQMXY2P9q3d0cuhmpDzeInLt4KcW1JzrcMqk=", 75 | "owner": "Platonic-Systems", 76 | "repo": "process-compose-flake", 77 | "rev": "e3292f80372cbc567ebda5deab5e451de5c5155c", 78 | "type": "github" 79 | }, 80 | "original": { 81 | "owner": "Platonic-Systems", 82 | "repo": "process-compose-flake", 83 | "type": "github" 84 | } 85 | }, 86 | "root": { 87 | "inputs": { 88 | "flake-parts": "flake-parts", 89 | "nixpkgs": "nixpkgs", 90 | "process-compose-flake": "process-compose-flake", 91 | "treefmt-nix": "treefmt-nix" 92 | } 93 | }, 94 | "treefmt-nix": { 95 | "inputs": { 96 | "nixpkgs": "nixpkgs_2" 97 | }, 98 | "locked": { 99 | "lastModified": 1688916551, 100 | "narHash": "sha256-YsGzTjtYwJ7j8TTIjo0cImycGAvUT1UXq2sCZ8Vu0Wc=", 101 | "owner": "numtide", 102 | "repo": "treefmt-nix", 103 | "rev": "3fadb6baac68068dc0196f35d3e092bf316d4409", 104 | "type": "github" 105 | }, 106 | "original": { 107 | "owner": "numtide", 108 | "repo": "treefmt-nix", 109 | "type": "github" 110 | } 111 | } 112 | }, 113 | "root": "root", 114 | "version": 7 115 | } 116 | -------------------------------------------------------------------------------- /dev/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 | flake-parts.url = "github:hercules-ci/flake-parts"; 5 | treefmt-nix.url = "github:numtide/treefmt-nix"; 6 | process-compose-flake.url = "github:Platonic-Systems/process-compose-flake"; 7 | }; 8 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }: 9 | flake-parts.lib.mkFlake { inherit inputs; } { 10 | systems = nixpkgs.lib.systems.flakeExposed; 11 | imports = [ 12 | inputs.treefmt-nix.flakeModule 13 | ]; 14 | perSystem = { pkgs, lib, config, ... }: { 15 | treefmt = { 16 | projectRoot = inputs.process-compose-flake; 17 | projectRootFile = "flake.nix"; 18 | programs = { 19 | nixpkgs-fmt.enable = true; 20 | }; 21 | }; 22 | devShells.default = pkgs.mkShell { 23 | packages = with pkgs; [ 24 | just 25 | nixd 26 | ]; 27 | inputsFrom = [ 28 | config.treefmt.build.devShell 29 | ]; 30 | shellHook = '' 31 | echo 32 | echo "🍎🍎 Run 'just ' to get started" 33 | just 34 | ''; 35 | }; 36 | }; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /doc/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "commonmark-simple": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1705078713, 7 | "narHash": "sha256-YgDHJG8M47ZXGLWu8o7MhXbIrgQ0Ai32Gr8nKvZGGw8=", 8 | "owner": "srid", 9 | "repo": "commonmark-simple", 10 | "rev": "fc106c94f781f6a35ef66900880edc08cbe3b034", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "srid", 15 | "repo": "commonmark-simple", 16 | "type": "github" 17 | } 18 | }, 19 | "commonmark-wikilink": { 20 | "flake": false, 21 | "locked": { 22 | "lastModified": 1705502834, 23 | "narHash": "sha256-79fzI4fPhCkfusDXctQwwmjIcWMrLfTvUtKBY32asuM=", 24 | "owner": "srid", 25 | "repo": "commonmark-wikilink", 26 | "rev": "f6d7bdf7f1fce09ba2a4259b0306b0eef24c0cf7", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "srid", 31 | "repo": "commonmark-wikilink", 32 | "type": "github" 33 | } 34 | }, 35 | "ema": { 36 | "inputs": { 37 | "flake-parts": [ 38 | "emanote", 39 | "flake-parts" 40 | ], 41 | "flake-root": [ 42 | "emanote", 43 | "flake-root" 44 | ], 45 | "haskell-flake": [ 46 | "emanote", 47 | "haskell-flake" 48 | ], 49 | "nixpkgs": [ 50 | "emanote", 51 | "nixpkgs" 52 | ], 53 | "treefmt-nix": [ 54 | "emanote", 55 | "treefmt-nix" 56 | ] 57 | }, 58 | "locked": { 59 | "lastModified": 1702334080, 60 | "narHash": "sha256-zrtzyLrSORxtocLMji5U9p4pDicMulOqgsuiB4LCu1o=", 61 | "owner": "srid", 62 | "repo": "ema", 63 | "rev": "33f4cf31ace7e612e78ad25f5fc45089745ab644", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "srid", 68 | "ref": "no-ws", 69 | "repo": "ema", 70 | "type": "github" 71 | } 72 | }, 73 | "emanote": { 74 | "inputs": { 75 | "commonmark-simple": "commonmark-simple", 76 | "commonmark-wikilink": "commonmark-wikilink", 77 | "ema": "ema", 78 | "emanote-template": "emanote-template", 79 | "flake-parts": "flake-parts", 80 | "flake-root": "flake-root", 81 | "haskell-flake": "haskell-flake", 82 | "heist-extra": "heist-extra", 83 | "nixpkgs": "nixpkgs", 84 | "systems": "systems", 85 | "treefmt-nix": "treefmt-nix", 86 | "unionmount": "unionmount" 87 | }, 88 | "locked": { 89 | "lastModified": 1705667811, 90 | "narHash": "sha256-ZH/m5e4l4v2HRcv45trQKl92PRG6e1lpRJ4+U4zewL8=", 91 | "owner": "srid", 92 | "repo": "emanote", 93 | "rev": "bf573e7653d183baa0fa68e2e172333f585b7a25", 94 | "type": "github" 95 | }, 96 | "original": { 97 | "owner": "srid", 98 | "repo": "emanote", 99 | "type": "github" 100 | } 101 | }, 102 | "emanote-template": { 103 | "flake": false, 104 | "locked": { 105 | "lastModified": 1703877265, 106 | "narHash": "sha256-2xdikzzHrIHr1s2pAJrBJU8mZP258Na3V4P4RWteDZM=", 107 | "owner": "srid", 108 | "repo": "emanote-template", 109 | "rev": "9d458b63c80162519ae55814e60f17cc9d3f95a3", 110 | "type": "github" 111 | }, 112 | "original": { 113 | "owner": "srid", 114 | "repo": "emanote-template", 115 | "type": "github" 116 | } 117 | }, 118 | "flake-parts": { 119 | "inputs": { 120 | "nixpkgs-lib": "nixpkgs-lib" 121 | }, 122 | "locked": { 123 | "lastModified": 1704982712, 124 | "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=", 125 | "owner": "hercules-ci", 126 | "repo": "flake-parts", 127 | "rev": "07f6395285469419cf9d078f59b5b49993198c00", 128 | "type": "github" 129 | }, 130 | "original": { 131 | "owner": "hercules-ci", 132 | "repo": "flake-parts", 133 | "type": "github" 134 | } 135 | }, 136 | "flake-root": { 137 | "locked": { 138 | "lastModified": 1692742795, 139 | "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=", 140 | "owner": "srid", 141 | "repo": "flake-root", 142 | "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937", 143 | "type": "github" 144 | }, 145 | "original": { 146 | "owner": "srid", 147 | "repo": "flake-root", 148 | "type": "github" 149 | } 150 | }, 151 | "haskell-flake": { 152 | "locked": { 153 | "lastModified": 1705067885, 154 | "narHash": "sha256-al2JqNIkXfLiVreqSJWly64Z6YVNphWBh4m3IxGIdYI=", 155 | "owner": "srid", 156 | "repo": "haskell-flake", 157 | "rev": "8a526aaf98cde6af6b2d1d368e9acb460ee34547", 158 | "type": "github" 159 | }, 160 | "original": { 161 | "owner": "srid", 162 | "repo": "haskell-flake", 163 | "type": "github" 164 | } 165 | }, 166 | "heist-extra": { 167 | "flake": false, 168 | "locked": { 169 | "lastModified": 1691619499, 170 | "narHash": "sha256-4e8v5t4FM99pdcPhohP3dAeGtsFnirbfYGpbr2+qWxI=", 171 | "owner": "srid", 172 | "repo": "heist-extra", 173 | "rev": "54ff970f733dd45b5509d1c4c298927b6241041b", 174 | "type": "github" 175 | }, 176 | "original": { 177 | "owner": "srid", 178 | "repo": "heist-extra", 179 | "type": "github" 180 | } 181 | }, 182 | "nixpkgs": { 183 | "locked": { 184 | "lastModified": 1704842529, 185 | "narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=", 186 | "owner": "nixos", 187 | "repo": "nixpkgs", 188 | "rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5", 189 | "type": "github" 190 | }, 191 | "original": { 192 | "owner": "nixos", 193 | "ref": "nixpkgs-unstable", 194 | "repo": "nixpkgs", 195 | "type": "github" 196 | } 197 | }, 198 | "nixpkgs-lib": { 199 | "locked": { 200 | "dir": "lib", 201 | "lastModified": 1703961334, 202 | "narHash": "sha256-M1mV/Cq+pgjk0rt6VxoyyD+O8cOUiai8t9Q6Yyq4noY=", 203 | "owner": "NixOS", 204 | "repo": "nixpkgs", 205 | "rev": "b0d36bd0a420ecee3bc916c91886caca87c894e9", 206 | "type": "github" 207 | }, 208 | "original": { 209 | "dir": "lib", 210 | "owner": "NixOS", 211 | "ref": "nixos-unstable", 212 | "repo": "nixpkgs", 213 | "type": "github" 214 | } 215 | }, 216 | "root": { 217 | "inputs": { 218 | "emanote": "emanote", 219 | "flake-parts": [ 220 | "emanote", 221 | "flake-parts" 222 | ], 223 | "nixpkgs": [ 224 | "emanote", 225 | "nixpkgs" 226 | ] 227 | } 228 | }, 229 | "systems": { 230 | "locked": { 231 | "lastModified": 1681028828, 232 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 233 | "owner": "nix-systems", 234 | "repo": "default", 235 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 236 | "type": "github" 237 | }, 238 | "original": { 239 | "owner": "nix-systems", 240 | "repo": "default", 241 | "type": "github" 242 | } 243 | }, 244 | "treefmt-nix": { 245 | "inputs": { 246 | "nixpkgs": [ 247 | "emanote", 248 | "nixpkgs" 249 | ] 250 | }, 251 | "locked": { 252 | "lastModified": 1693468138, 253 | "narHash": "sha256-DddblCahuTW8K0ncPOheTlG3igE8b15LJjafF1PWhOo=", 254 | "owner": "numtide", 255 | "repo": "treefmt-nix", 256 | "rev": "6930a5ba0a722385baf273885a03f561dcb1af67", 257 | "type": "github" 258 | }, 259 | "original": { 260 | "owner": "numtide", 261 | "repo": "treefmt-nix", 262 | "type": "github" 263 | } 264 | }, 265 | "unionmount": { 266 | "flake": false, 267 | "locked": { 268 | "lastModified": 1691619410, 269 | "narHash": "sha256-V9/OcGu9cy4kV9jta12A6w5BEj8awSEVYrXPpg8YckQ=", 270 | "owner": "srid", 271 | "repo": "unionmount", 272 | "rev": "ed73b627f88c8f021f41ba4b518ba41beff9df42", 273 | "type": "github" 274 | }, 275 | "original": { 276 | "owner": "srid", 277 | "repo": "unionmount", 278 | "type": "github" 279 | } 280 | } 281 | }, 282 | "root": "root", 283 | "version": 7 284 | } 285 | -------------------------------------------------------------------------------- /doc/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixConfig = { 3 | extra-substituters = "https://srid.cachix.org"; 4 | extra-trusted-public-keys = "srid.cachix.org-1:3clnql5gjbJNEvhA/WQp7nrZlBptwpXnUk6JAv8aB2M="; 5 | }; 6 | 7 | inputs = { 8 | emanote.url = "github:srid/emanote"; 9 | nixpkgs.follows = "emanote/nixpkgs"; 10 | flake-parts.follows = "emanote/flake-parts"; 11 | }; 12 | 13 | outputs = inputs@{ self, flake-parts, nixpkgs, ... }: 14 | flake-parts.lib.mkFlake { inherit inputs; } { 15 | systems = nixpkgs.lib.systems.flakeExposed; 16 | imports = [ inputs.emanote.flakeModule ]; 17 | perSystem = { self', pkgs, system, ... }: { 18 | emanote = { 19 | # By default, the 'emanote' flake input is used. 20 | # package = inputs.emanote.packages.${system}.default; 21 | sites."default" = { 22 | layers = [ ./. ]; 23 | layersString = [ "." ]; 24 | port = 8181; 25 | prettyUrls = true; 26 | }; 27 | }; 28 | devShells.default = pkgs.mkShell { 29 | buildInputs = [ 30 | pkgs.nixpkgs-fmt 31 | ]; 32 | }; 33 | formatter = pkgs.nixpkgs-fmt; 34 | }; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | short-title: process-compose-flake 3 | template: 4 | sidebar: 5 | collapsed: true 6 | emanote: 7 | folder-folgezettel: false 8 | --- 9 | 10 | # Process management using `process-compose-flake` 11 | 12 | [process-compose-flake](https://github.com/Platonic-Systems/process-compose-flake) is a [flake-parts](https://flake.parts/) module for [process-compose](https://github.com/F1bonacc1/process-compose). 13 | 14 | This `flake-parts` module allows you to declare one or more `process-compose` configurations using Nix attribute sets. It will generate corresponding `packages` that wrap the `process-compose` binary with the given configuration. 15 | 16 | This module is practical for local development e.g. if you have a lot of runtime dependencies that depend on each other. Stop executing these programs imperatively over and over again in a specific order, and stop the need to write complicated shell scripts to automate this. `process-compose` gives you a process dashboard for monitoring, inspecting logs for each process, and much more, all of this in a TUI. 17 | 18 | ## Quick Example 19 | 20 | See [`example/flake.nix`](https://github.com/Platonic-Systems/process-compose-flake/blob/main/example/flake.nix) for an example flake. This example shows a demo of [sqlite-web](https://github.com/coleifer/sqlite-web) using the sample [chinhook-database](https://github.com/lerocha/chinook-database). 21 | 22 | To run this example locally, 23 | 24 | ```bash 25 | mkdir example && cd example 26 | nix flake init -t github:Platonic-Systems/process-compose-flake 27 | nix run 28 | ``` 29 | 30 | This should open http://127.0.0.1:8213/ in your web browser. If not, navigate to the logs for the `sqlite-web` process and access the URL. Use `demo` as the password to access sqlite-web. The interface should look like this: 31 | 32 | ![](https://github.com/Platonic-Systems/process-compose-flake/assets/3998/254443fa-f3c2-4675-9ced-2a39ac23591d) 33 | 34 | 35 | ## Usage 36 | Let's say you want to have a `devShell` that makes a command `watch-server` available, that you can use to spin up your projects `backend-server`, `frontend-server`, and `proxy-server`. 37 | 38 | To achieve this using `process-compose-flake` you can simply add the following code to the `perSystem` function in your `flake-parts` flake. 39 | ```nix 40 | process-compose.watch-server = { 41 | settings.processes = { 42 | backend-server.command = "${self'.apps.backend-server.program} --port 9000"; 43 | frontend-server.command = "${self'.apps.frontend-server.program} --port 9001"; 44 | proxy-server.command = 45 | let 46 | proxyConfig = pkgs.writeTextFile { 47 | name = "proxy.conf"; 48 | text = '' 49 | ... 50 | ''; 51 | }; 52 | in 53 | "${self'.apps.proxy-server.program} -c ${proxyConfig} -p 8000"; 54 | }; 55 | }; 56 | ``` 57 | 58 | `process-compose-flake` will generate the `packages.${system}.watch-server` output for you. 59 | 60 | You can then spin up the processes by running `nix run .#watch-server`. 61 | 62 | The `package` output in turn can be used to make the `watch-server` command available in your `devShell`: 63 | 64 | ```nix 65 | devShells = { 66 | default = pkgs.mkShell { 67 | name = "my-shell"; 68 | nativeBuildInputs = [ 69 | self'.packages.watch-server 70 | ]; 71 | }; 72 | }; 73 | ``` 74 | 75 | You can enter your devShell by running `nix develop` and run `watch-server` to run your processes. 76 | 77 | ### preHook 78 | 79 | If you'd like to run certain commands before starting the processes, you can add them to `preHook`: 80 | 81 | ```nix 82 | process-compose.watch-server = { 83 | preHook = '' 84 | # Cleanup on EXIT, this runs irrespective of exit-code of process-compose 85 | trap "rm -rf ./data" EXIT 86 | export USER=foo 87 | ''; 88 | }; 89 | ``` 90 | 91 | ### postHook 92 | 93 | Or if you'd like to run certain commands upon successful execution of `process-compose`, i.e exits with `exit-code: 0`, then add them to `postHook`: 94 | 95 | ```nix 96 | process-compose.watch-server = { 97 | postHook = '' 98 | cat foo.txt 99 | ''; 100 | }; 101 | ``` 102 | 103 | ## Module API 104 | 105 | Our submodule mirrors the [process-compose YAML schema](https://github.com/F1bonacc1/process-compose/blob/main/process-compose.yaml). A few things to remember: 106 | 107 | - `process-compose..environment`: In the YAML config, a list of environment strings are specified. While this is supported, you can also specify the env vars as a Nix attrset 108 | - `process-compose..processes..command`: The command string does not have access to the process environment, so if your command becomes shellscript-like you probably want to wrap it in a `pkgs.writeShellApplication` (see [\#22](https://github.com/Platonic-Systems/process-compose-flake/issues/22)). 109 | - `process-compose..shell`: This is set to `pkgs.bash` by default, obviating reproducibility issues due to depending on globally available bash. 110 | 111 | ## See also 112 | 113 | - [process-compose docs](https://f1bonacc1.github.io/process-compose/launcher/) 114 | 115 | ## Related projects 116 | 117 | - [`services-flake`](https://community.flake.parts/services-flake): NixOS-like services built on top of process-compose-flake. Use this if you want to run popular services (like postgres). 118 | - [`proc-flake`](https://github.com/srid/proc-flake): A similar module that uses a `Procfile`-based runner. It is less feature-rich, but [at times more reliable](https://github.com/Platonic-Systems/process-compose-flake/issues/30) than process-compose. 119 | 120 | -------------------------------------------------------------------------------- /doc/index.yaml: -------------------------------------------------------------------------------- 1 | # For documentation and available settings, see 2 | # https://github.com/srid/emanote/blob/master/emanote/default/index.yaml 3 | 4 | page: 5 | siteTitle: process-compose-flake 6 | headHtml: | 7 | 8 | 9 | template: 10 | # You can add your own variables here, like editBaseUrl. 11 | # See after-note.tpl to see where editBaseUrl gets used. 12 | editBaseUrl: https://github.com/Platonic-Systems/process-compose-flake/edit/main/doc 13 | 14 | # Uncomment this to get neuron-style pages 15 | # name: /templates/layouts/note 16 | # layout: 17 | # note: 18 | # containerClass: container mx-auto max-w-screen-lg 19 | 20 | sidebar: 21 | collapsed: false 22 | 23 | # If you are hosting on GitLab Pages, you may want to remove this. 24 | urlStrategy: pretty 25 | -------------------------------------------------------------------------------- /example/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "chinookDb": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1716741054, 7 | "narHash": "sha256-YMma46vB72wJbIlWj0KOYUuV/e5Bqkmhylfy22XiAHs=", 8 | "owner": "lerocha", 9 | "repo": "chinook-database", 10 | "rev": "85eca67d22ae15b2767851cc45abc7f8764c517f", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "lerocha", 15 | "repo": "chinook-database", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-parts": { 20 | "inputs": { 21 | "nixpkgs-lib": "nixpkgs-lib" 22 | }, 23 | "locked": { 24 | "lastModified": 1726153070, 25 | "narHash": "sha256-HO4zgY0ekfwO5bX0QH/3kJ/h4KvUDFZg8YpkNwIbg1U=", 26 | "owner": "hercules-ci", 27 | "repo": "flake-parts", 28 | "rev": "bcef6817a8b2aa20a5a6dbb19b43e63c5bf8619a", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "hercules-ci", 33 | "repo": "flake-parts", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs": { 38 | "locked": { 39 | "lastModified": 1727524699, 40 | "narHash": "sha256-k6YxGj08voz9NvuKExojiGXAVd69M8COtqWSKr6sQS4=", 41 | "owner": "nixos", 42 | "repo": "nixpkgs", 43 | "rev": "b5b2fecd0cadd82ef107c9583018f381ae70f222", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "nixos", 48 | "ref": "nixpkgs-unstable", 49 | "repo": "nixpkgs", 50 | "type": "github" 51 | } 52 | }, 53 | "nixpkgs-lib": { 54 | "locked": { 55 | "lastModified": 1725233747, 56 | "narHash": "sha256-Ss8QWLXdr2JCBPcYChJhz4xJm+h/xjl4G0c0XlP6a74=", 57 | "type": "tarball", 58 | "url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz" 59 | }, 60 | "original": { 61 | "type": "tarball", 62 | "url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz" 63 | } 64 | }, 65 | "process-compose-flake": { 66 | "locked": { 67 | "lastModified": 1727711986, 68 | "narHash": "sha256-VYUqTcjhnFmmYz84z2YVXqZUg8JfAHuIdpEpkmqUQJ4=", 69 | "owner": "Platonic-Systems", 70 | "repo": "process-compose-flake", 71 | "rev": "d0824dab6ff49a06ed10e48177aabaef52469a0a", 72 | "type": "github" 73 | }, 74 | "original": { 75 | "owner": "Platonic-Systems", 76 | "repo": "process-compose-flake", 77 | "type": "github" 78 | } 79 | }, 80 | "root": { 81 | "inputs": { 82 | "chinookDb": "chinookDb", 83 | "flake-parts": "flake-parts", 84 | "nixpkgs": "nixpkgs", 85 | "process-compose-flake": "process-compose-flake", 86 | "systems": "systems" 87 | } 88 | }, 89 | "systems": { 90 | "locked": { 91 | "lastModified": 1681028828, 92 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 93 | "owner": "nix-systems", 94 | "repo": "default", 95 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 96 | "type": "github" 97 | }, 98 | "original": { 99 | "owner": "nix-systems", 100 | "repo": "default", 101 | "type": "github" 102 | } 103 | } 104 | }, 105 | "root": "root", 106 | "version": 7 107 | } 108 | -------------------------------------------------------------------------------- /example/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A demo of sqlite-web"; 3 | inputs = { 4 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 5 | flake-parts.url = "github:hercules-ci/flake-parts"; 6 | systems.url = "github:nix-systems/default"; 7 | process-compose-flake.url = "github:Platonic-Systems/process-compose-flake"; 8 | 9 | chinookDb.url = "github:lerocha/chinook-database"; 10 | chinookDb.flake = false; 11 | }; 12 | outputs = inputs: 13 | inputs.flake-parts.lib.mkFlake { inherit inputs; } { 14 | systems = import inputs.systems; 15 | imports = [ 16 | inputs.process-compose-flake.flakeModule 17 | ]; 18 | perSystem = { self', pkgs, lib, ... }: { 19 | # This adds a `self.packages.default` 20 | process-compose."default" = 21 | let 22 | port = 8213; 23 | dataFile = "data.sqlite"; 24 | in 25 | { 26 | cli = { 27 | # environment.PC_DISABLE_TUI = true; 28 | # Global options for `process-compose` 29 | options = { 30 | no-server = true; 31 | }; 32 | }; 33 | settings = { 34 | environment = { 35 | SQLITE_WEB_PASSWORD = "demo"; 36 | }; 37 | 38 | processes = { 39 | # Print a pony every 2 seconds, because why not. 40 | ponysay.command = '' 41 | while true; do 42 | ${lib.getExe pkgs.ponysay} "Enjoy our sqlite-web demo!" 43 | sleep 2 44 | done 45 | ''; 46 | 47 | # Create .sqlite database from chinook database. 48 | sqlite-init.command = '' 49 | echo "$(date): Importing Chinook database (${dataFile}) ..." 50 | ${lib.getExe pkgs.sqlite} "${dataFile}" < ${inputs.chinookDb}/ChinookDatabase/DataSources/Chinook_Sqlite.sql 51 | echo "$(date): Done." 52 | ''; 53 | 54 | # Run sqlite-web on the local chinook database. 55 | sqlite-web = { 56 | command = '' 57 | ${pkgs.sqlite-web}/bin/sqlite_web \ 58 | --password \ 59 | --port ${builtins.toString port} "${dataFile}" 60 | ''; 61 | # The 'depends_on' will have this process wait until the above one is completed. 62 | depends_on."sqlite-init".condition = "process_completed_successfully"; 63 | readiness_probe.http_get = { 64 | host = "localhost"; 65 | inherit port; 66 | }; 67 | }; 68 | 69 | # If a process is named 'test', it will be ignored. But a new 70 | # flake check will be created that runs it so as to test the 71 | # other processes. 72 | test = { 73 | command = pkgs.writeShellApplication { 74 | name = "sqlite-web-test"; 75 | runtimeInputs = [ pkgs.curl ]; 76 | text = '' 77 | curl -v http://localhost:${builtins.toString port}/ 78 | ''; 79 | }; 80 | depends_on."sqlite-web".condition = "process_healthy"; 81 | }; 82 | }; 83 | }; 84 | }; 85 | 86 | # nix run .#ponysay up to start the process 87 | packages.ponysay = (import inputs.process-compose-flake.lib { inherit pkgs; }).makeProcessCompose { 88 | modules = [{ 89 | settings = { 90 | processes = { 91 | ponysay.command = '' 92 | while true; do 93 | ${lib.getExe pkgs.ponysay} "Hi!" 94 | sleep 2 95 | done 96 | ''; 97 | }; 98 | }; 99 | }]; 100 | }; 101 | }; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | outputs = _: { 3 | flakeModule = ./nix/flake-module.nix; 4 | 5 | lib = ./nix/lib.nix; 6 | 7 | templates.default = { 8 | description = "Example flake using process-compose-flake"; 9 | path = builtins.path { path = ./example; filter = path: _: baseNameOf path == "flake.nix"; }; 10 | }; 11 | 12 | # https://github.com/srid/nixci 13 | nixci.default = let overrideInputs = { process-compose-flake = ./.; }; in { 14 | example = { 15 | inherit overrideInputs; 16 | dir = "example"; 17 | }; 18 | dev = { 19 | inherit overrideInputs; 20 | dir = "dev"; 21 | }; 22 | }; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # List all the just commands 2 | default: 3 | @just --list 4 | 5 | # Auto-format the project tree 6 | fmt: 7 | treefmt 8 | 9 | # Run doc server with hot-reload 10 | doc: 11 | cd ./doc && nix run 12 | 13 | # Build docs static website (this runs linkcheck automatically) 14 | doc-static: 15 | nix build ./doc 16 | 17 | # Run example, using current process-compose 18 | ex *ARGS: 19 | cd ./example && nix run --override-input process-compose-flake .. . -- {{ARGS}} 20 | 21 | # Run example's test 22 | ex-check: 23 | cd ./example && nix flake check -L --override-input process-compose-flake .. 24 | -------------------------------------------------------------------------------- /nix/flake-module.nix: -------------------------------------------------------------------------------- 1 | { lib, flake-parts-lib, ... }: 2 | let 3 | inherit (flake-parts-lib) 4 | mkPerSystemOption; 5 | inherit (lib) 6 | mkOption 7 | types; 8 | in 9 | { 10 | options.perSystem = mkPerSystemOption ({ config, pkgs, lib, ... }: 11 | { 12 | options.process-compose = mkOption { 13 | description = '' 14 | process-compose-flake: creates [process-compose](https://github.com/F1bonacc1/process-compose) 15 | executables from process-compose configurations written as Nix attribute sets. 16 | ''; 17 | type = types.attrsOf (types.submoduleWith { 18 | specialArgs = { inherit pkgs; }; 19 | modules = [ 20 | ./process-compose 21 | ]; 22 | }); 23 | }; 24 | 25 | config = { 26 | packages = lib.mapAttrs 27 | (_: cfg: cfg.outputs.package) 28 | config.process-compose; 29 | checks = 30 | let 31 | checks' = lib.mapAttrs 32 | (_: cfg: cfg.outputs.check) 33 | config.process-compose; 34 | in 35 | lib.filterAttrs (_: v: v != null) checks'; 36 | }; 37 | }); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /nix/lib.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib ? pkgs.lib, ... }: 2 | 3 | rec { 4 | # Lookup an environment in process-compose environment list 5 | # Returns null if environment doesn't otherwise. 6 | lookupEnv = name: envList: 7 | let 8 | env = parseEnvList envList; 9 | in 10 | lib.attrByPath [ name ] null env; 11 | 12 | # Parse "FOO=bar" into { FOO = "bar"; } 13 | parseEnv = s: 14 | let 15 | parts = lib.splitString "=" s; 16 | in 17 | if builtins.length parts == 2 then 18 | let 19 | k = builtins.head parts; 20 | v = builtins.head (builtins.tail parts); 21 | in 22 | { ${k} = v; } 23 | else 24 | null; 25 | 26 | # Parse `[ "FOO=bar" "BAZ=quux" ]` into `{ FOO = "bar"; BAZ = "quux"; }` 27 | parseEnvList = l: 28 | if l == null then { } 29 | else builtins.foldl' (x: y: x // y) { } (builtins.map parseEnv l); 30 | 31 | types = { 32 | command = import ./process-compose/setting/command.nix { inherit lib; }; 33 | }; 34 | 35 | # Run the process-compose-module stand-alone, without flake-parts 36 | # - modules: list of modules that set process-compose-flake options 37 | # - name: name of the module, you typically don't need to set this 38 | # Returns the full result of the module evaluation 39 | evalModules = { name ? "process-compose", modules }: (lib.evalModules { 40 | specialArgs = { 41 | inherit name pkgs; 42 | }; 43 | modules = [ 44 | ./process-compose 45 | ] ++ modules; 46 | }); 47 | 48 | # Same as evalModules, but returns the process-compose process directly 49 | makeProcessCompose = { name ? "process-compose", modules }: (evalModules { 50 | inherit name; 51 | modules = modules; 52 | }).config.outputs.package; 53 | } 54 | -------------------------------------------------------------------------------- /nix/process-compose/cli.nix: -------------------------------------------------------------------------------- 1 | { lib, config, options, ... }: 2 | let 3 | inherit (lib) types mkOption; 4 | in 5 | { 6 | options = { 7 | cli = { 8 | preHook = mkOption { 9 | type = types.lines; 10 | default = ""; 11 | description = "Shell commands to run before process-compose starts."; 12 | }; 13 | postHook = mkOption { 14 | type = types.lines; 15 | default = ""; 16 | description = "Shell commands to run after process-compose completes."; 17 | }; 18 | environment = mkOption { 19 | default = { }; 20 | description = "Environment variables to pass to process-compose binary."; 21 | type = types.submodule { 22 | options = { 23 | PC_DISABLE_TUI = mkOption { 24 | type = types.nullOr types.bool; 25 | default = null; 26 | description = "Disable the TUI (Text User Interface) of process-compose"; 27 | }; 28 | }; 29 | }; 30 | }; 31 | options = mkOption { 32 | description = "CLI options to pass to process-compose binary"; 33 | default = { }; 34 | type = types.submodule { 35 | options = { 36 | log-file = mkOption { 37 | type = types.nullOr types.str; 38 | default = null; 39 | description = "Pass --log-file to process-compose"; 40 | }; 41 | no-server = mkOption { 42 | type = types.bool; 43 | default = false; 44 | description = "Pass --no-server to process-compose"; 45 | }; 46 | ordered-shutdown = mkOption { 47 | type = types.bool; 48 | default = false; 49 | description = "Pass --ordered-shutdown to process-compose"; 50 | }; 51 | port = mkOption { 52 | type = types.nullOr types.int; 53 | default = null; 54 | description = "Pass --port to process-compose"; 55 | }; 56 | read-only = mkOption { 57 | type = types.bool; 58 | default = false; 59 | description = "Pass --read-only to process-compose"; 60 | }; 61 | unix-socket = mkOption { 62 | type = types.nullOr types.str; 63 | default = null; 64 | description = "Pass --unix-socket to process-compose"; 65 | }; 66 | use-uds = mkOption { 67 | type = types.bool; 68 | default = false; 69 | description = "Pass --use-uds to process-compose"; 70 | }; 71 | }; 72 | }; 73 | }; 74 | 75 | # The final CLI arguments we will pass to process-compose binary. 76 | outputs = { 77 | # TODO: We should refactor this to generically iterate on options and produce the CLI automatically using naming conventions and types. 78 | options = lib.mkOption { 79 | type = types.str; 80 | readOnly = true; 81 | description = "The final CLI arguments we will pass to process-compose binary."; 82 | default = let o = config.cli.options; in lib.escapeShellArgs ( 83 | (lib.optionals (o.log-file != null) [ "--log-file" o.log-file ]) 84 | ++ (lib.optionals o.no-server [ "--no-server" ]) 85 | ++ (lib.optionals o.ordered-shutdown [ "--ordered-shutdown" ]) 86 | ++ (lib.optionals (o.port != null) [ "--port" "${builtins.toString o.port}" ]) 87 | ++ (lib.optionals o.read-only [ "--read-only" ]) 88 | ++ (lib.optionals (o.unix-socket != null) [ "--unix-socket" o.unix-socket ]) 89 | ++ (lib.optionals o.use-uds [ "--use-uds" ]) 90 | ); 91 | }; 92 | 93 | environment = lib.mkOption { 94 | type = types.str; 95 | description = "Shell script prefix setting environment variables"; 96 | readOnly = true; 97 | default = 98 | lib.concatStringsSep " " (lib.mapAttrsToList 99 | (name: value: 100 | if value == null then "" else "${name}=${builtins.toJSON value}") 101 | config.cli.environment); 102 | }; 103 | }; 104 | }; 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /nix/process-compose/default.nix: -------------------------------------------------------------------------------- 1 | { name, config, pkgs, lib, ... }: 2 | 3 | let 4 | inherit (lib) types mkOption; 5 | in 6 | { 7 | imports = [ 8 | ./cli.nix 9 | ./settings 10 | ./test.nix 11 | ]; 12 | 13 | options = { 14 | package = mkOption { 15 | type = types.package; 16 | default = pkgs.process-compose; 17 | defaultText = lib.literalExpression "pkgs.process-compose"; 18 | description = '' 19 | The process-compose package to bundle up in the command package and flake app. 20 | ''; 21 | }; 22 | outputs.package = mkOption { 23 | type = types.package; 24 | description = '' 25 | The final package that will run 'process-compose up' for this configuration. 26 | ''; 27 | }; 28 | outputs.testPackage = mkOption { 29 | type = types.nullOr types.package; 30 | description = '' 31 | Like `outputs.package` but includes the "test" process 32 | 33 | Set to null if there is no "test" process. 34 | ''; 35 | }; 36 | }; 37 | 38 | config.outputs = 39 | let 40 | mkProcessComposeWrapper = { name, configFile }: 41 | pkgs.writeShellApplication { 42 | inherit name; 43 | runtimeInputs = [ config.package ]; 44 | text = '' 45 | ${config.cli.preHook} 46 | 47 | # IMPORTANT: We **must** use environment variables for everything but non-global options, otherwise the use of sub-command specific CLI options will prevent the user from passing their own subcommands reliably. 48 | ${config.cli.outputs.environment} PC_CONFIG_FILES=${configFile} process-compose ${config.cli.outputs.options} "$@" 49 | 50 | ${config.cli.postHook} 51 | ''; 52 | }; 53 | in 54 | { 55 | package = 56 | mkProcessComposeWrapper 57 | { 58 | inherit name; 59 | configFile = config.outputs.settingsFile; 60 | }; 61 | testPackage = 62 | if (builtins.hasAttr "test" config.settings.processes) then 63 | mkProcessComposeWrapper 64 | { 65 | name = "${name}-test"; 66 | configFile = config.outputs.settingsTestFile; 67 | } 68 | else null; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /nix/process-compose/settings/command.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | 3 | args: 4 | lib.mkOption (args // { 5 | type = lib.types.either lib.types.package lib.types.str; 6 | apply = pkg: 7 | if builtins.isString pkg 8 | then pkg 9 | else lib.getExe pkg; 10 | }) 11 | -------------------------------------------------------------------------------- /nix/process-compose/settings/default.nix: -------------------------------------------------------------------------------- 1 | { name, config, pkgs, lib, ... }: 2 | let 3 | inherit (lib) types mkOption literalExpression; 4 | in 5 | { 6 | options = { 7 | settings = mkOption { 8 | default = { }; 9 | type = types.submoduleWith { 10 | modules = [{ 11 | options = { 12 | processes = mkOption { 13 | type = types.attrsOf (types.submoduleWith { modules = [ ./process.nix ]; }); 14 | default = { }; 15 | description = '' 16 | A map of process names to their configuration. 17 | ''; 18 | }; 19 | 20 | environment = import ./environment.nix { inherit lib; }; 21 | 22 | log_length = mkOption { 23 | type = types.nullOr types.ints.unsigned; 24 | default = null; 25 | example = 3000; 26 | description = '' 27 | Log length to display in TUI mode. 28 | ''; 29 | }; 30 | log_level = mkOption { 31 | type = types.nullOr (types.enum [ 32 | "trace" 33 | "debug" 34 | "info" 35 | "warn" 36 | "error" 37 | "fatal" 38 | "panic" 39 | ]); 40 | default = null; 41 | example = "info"; 42 | description = '' 43 | Level of logs to output. 44 | ''; 45 | }; 46 | log_location = mkOption { 47 | type = types.nullOr types.str; 48 | default = null; 49 | example = "./pc.log"; 50 | description = '' 51 | File to write the logs to. 52 | ''; 53 | }; 54 | 55 | shell = { 56 | shell_argument = mkOption { 57 | type = types.str; 58 | default = "-c"; 59 | example = "-c"; 60 | description = '' 61 | Arguments to pass to the shell given by `shell_command`. 62 | ''; 63 | }; 64 | shell_command = mkOption { 65 | type = types.str; 66 | description = '' 67 | The shell to use to run the process `command`s. 68 | 69 | For reproducibility across systems, by default this uses 70 | `pkgs.bash`. 71 | ''; 72 | default = lib.getExe pkgs.bash; 73 | defaultText = "lib.getExe pkgs.bash"; 74 | }; 75 | }; 76 | 77 | version = mkOption { 78 | type = types.nullOr types.str; 79 | default = null; 80 | example = "0.5"; 81 | description = '' 82 | Version of the process-compose configuration. 83 | ''; 84 | }; 85 | }; 86 | }]; 87 | }; 88 | example = 89 | # packages.${system}.watch-server becomes available 90 | # execute `nix run .#watch-server` or incude packages.${system}.watch-server 91 | # as a nativeBuildInput to your devShell 92 | literalExpression '' 93 | { 94 | watch-server = { 95 | processes = { 96 | backend = "''${pkgs.simple-http-server}"; 97 | frontend = "''${pkgs.simple-http-server}"; 98 | }; 99 | }; 100 | }; 101 | ''; 102 | description = '' 103 | For each attribute `x = process-compose config` a flake app and package `x` is added to the flake. 104 | Which runs process-compose with the declared config. 105 | ''; 106 | }; 107 | outputs.settingsFile = mkOption { 108 | type = types.attrsOf types.raw; 109 | internal = true; 110 | description = '' 111 | The settings file that will be used to run the process-compose flake. 112 | ''; 113 | }; 114 | 115 | outputs.settingsTestFile = mkOption { 116 | type = types.attrsOf types.raw; 117 | internal = true; 118 | }; 119 | }; 120 | config.outputs = 121 | let 122 | removeNullAndEmptyAttrs = attrs: 123 | let 124 | f = lib.filterAttrsRecursive (key: value: value != null && value != { }); 125 | # filterAttrsRecursive doesn't delete the *resulting* empty attrs, so we must 126 | # evaluate it again and to get rid of it. 127 | in 128 | lib.pipe attrs [ f f ]; 129 | toPCJson = name: attrs: 130 | pkgs.writeTextFile { 131 | name = "process-compose-${name}.json"; 132 | text = builtins.toJSON attrs; 133 | }; 134 | in 135 | { 136 | settingsFile = toPCJson name (removeNullAndEmptyAttrs config.settings); 137 | settingsTestFile = toPCJson "${name}-test" (removeNullAndEmptyAttrs 138 | (lib.updateManyAttrsByPath [ 139 | { 140 | path = [ "processes" "test" ]; 141 | update = old: old // { 142 | disabled = false; 143 | availability = { 144 | exit_on_end = true; 145 | exit_on_skipped = true; 146 | }; 147 | }; 148 | } 149 | ] 150 | config.settings)); 151 | }; 152 | } 153 | -------------------------------------------------------------------------------- /nix/process-compose/settings/environment.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) types; 4 | inherit (types) nullOr either listOf str attrsOf; 5 | in 6 | lib.mkOption { 7 | type = 8 | nullOr 9 | (either (listOf str) (attrsOf str)); 10 | default = null; 11 | example = { ABC = "2221"; PRINT_ERR = "111"; }; 12 | description = '' 13 | Attrset of environment variables. 14 | 15 | List of strings is also allowed. 16 | ''; 17 | apply = attrs: 18 | if builtins.isAttrs attrs then 19 | lib.mapAttrsToList (name: value: "${name}=${value}") attrs 20 | else 21 | attrs; 22 | } 23 | -------------------------------------------------------------------------------- /nix/process-compose/settings/probe.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | 3 | let 4 | inherit (lib) types mkOption; 5 | in 6 | { 7 | options = { 8 | failure_threshold = mkOption { 9 | type = types.ints.unsigned; 10 | default = 3; 11 | example = 3; 12 | description = '' 13 | Number of times to fail before giving up on restarting the process. 14 | ''; 15 | }; 16 | http_get = mkOption { 17 | description = '' 18 | URL to determine the health of the process. 19 | ''; 20 | type = types.nullOr (types.submodule { 21 | options = { 22 | host = mkOption { 23 | type = types.str; 24 | example = "google.com"; 25 | description = '' 26 | The host address which `process-compose` uses to probe the process. 27 | ''; 28 | }; 29 | scheme = mkOption { 30 | type = types.str; 31 | default = "http"; 32 | example = "http"; 33 | description = '' 34 | The protocol used to probe the process listening on `host`. 35 | ''; 36 | }; 37 | path = mkOption { 38 | type = types.str; 39 | default = "/"; 40 | example = "/"; 41 | description = '' 42 | The path to the healtcheck endpoint. 43 | ''; 44 | }; 45 | port = mkOption { 46 | type = types.port; 47 | example = "8080"; 48 | description = '' 49 | Which port to probe the process on. 50 | ''; 51 | }; 52 | }; 53 | }); 54 | default = null; 55 | }; 56 | exec = mkOption { 57 | type = types.nullOr (types.submodule { 58 | options.command = mkOption { 59 | type = types.str; 60 | example = "ps -ef | grep -v grep | grep my-proccess"; 61 | description = '' 62 | The command to execute in order to check the health of the process. 63 | ''; 64 | }; 65 | }); 66 | default = null; 67 | description = '' 68 | Execution settings. 69 | ''; 70 | }; 71 | initial_delay_seconds = mkOption { 72 | type = types.ints.unsigned; 73 | default = 0; 74 | example = 0; 75 | description = '' 76 | Wait for `initial_delay_seconds` before starting the probe/healthcheck. 77 | ''; 78 | }; 79 | period_seconds = mkOption { 80 | type = types.ints.unsigned; 81 | default = 10; 82 | example = 10; 83 | description = '' 84 | Check the health every `period_seconds`. 85 | ''; 86 | }; 87 | success_threshold = mkOption { 88 | type = types.ints.unsigned; 89 | default = 1; 90 | example = 1; 91 | description = '' 92 | Number of successful checks before marking the process `Ready`. 93 | ''; 94 | }; 95 | timeout_seconds = mkOption { 96 | type = types.ints.unsigned; 97 | default = 3; 98 | example = 3; 99 | description = '' 100 | How long to wait for a given probe request. 101 | ''; 102 | }; 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /nix/process-compose/settings/process.nix: -------------------------------------------------------------------------------- 1 | { name, lib, ... }: 2 | 3 | let 4 | inherit (lib) types mkOption; 5 | probeType = types.submoduleWith { 6 | specialArgs = { inherit lib; }; 7 | modules = [ ./probe.nix ]; 8 | }; 9 | in 10 | { 11 | options = { 12 | command = import ./command.nix { inherit lib; } { 13 | description = '' 14 | The command or script or package that runs this process 15 | 16 | If a package is given, its executable is used as the command. Otherwise, 17 | the command string is wrapped in a `pkgs.writeShellApplication` which 18 | uses ShellCheck and runs the command in bash. 19 | ''; 20 | }; 21 | 22 | depends_on = mkOption { 23 | description = "Process dependency relationships"; 24 | type = types.nullOr (types.attrsOf (types.submodule { 25 | options = { 26 | condition = mkOption { 27 | type = types.enum [ 28 | "process_completed" 29 | "process_completed_successfully" 30 | "process_healthy" 31 | "process_log_ready" 32 | "process_started" 33 | ]; 34 | example = "process_healthy"; 35 | description = '' 36 | The condition the parent process must be in before starting the current one. 37 | ''; 38 | }; 39 | }; 40 | })); 41 | default = null; 42 | }; 43 | 44 | availability = { 45 | restart = mkOption { 46 | type = types.nullOr (types.enum [ 47 | "always" 48 | "on_failure" 49 | "exit_on_failure" 50 | "no" 51 | ]); 52 | default = null; 53 | example = "on_failure"; 54 | description = '' 55 | When to restart the process. 56 | ''; 57 | }; 58 | exit_on_end = mkOption { 59 | type = types.nullOr types.bool; 60 | default = null; 61 | example = true; 62 | description = '' 63 | Whether to gracefully stop all the processes upon the exit of the current process. 64 | ''; 65 | }; 66 | # Added to process-compose in https://github.com/F1bonacc1/process-compose/pull/226 67 | exit_on_skipped = mkOption { 68 | type = types.nullOr types.bool; 69 | default = null; 70 | example = true; 71 | description = '' 72 | Whether to gracefully stop all the processes upon the process being skipped. 73 | ''; 74 | }; 75 | backoff_seconds = mkOption { 76 | type = types.nullOr types.ints.unsigned; 77 | default = null; 78 | example = 2; 79 | description = '' 80 | Restart will wait `process.availability.backoff_seconds` seconds between `stop` and `start` of the process. If not configured the default value is 1s. 81 | ''; 82 | }; 83 | max_restarts = mkOption { 84 | type = types.nullOr types.ints.unsigned; 85 | default = null; 86 | example = 0; 87 | description = '' 88 | Max. number of times to restart. 89 | 90 | Tip: It might be sometimes useful to `exit_on_end` with `restart: on_failure` and `max_restarts` in case you want the process to recover from failure and only cause termination on success. 91 | ''; 92 | }; 93 | }; 94 | 95 | shutdown = { 96 | command = mkOption { 97 | type = types.nullOr types.str; 98 | default = null; 99 | example = "sleep 2 && pkill -f 'test_loop.bash my-proccess'"; 100 | description = '' 101 | The command to run while process-compose is trying to gracefully shutdown the current process. 102 | 103 | Note: The `shutdown.command` is executed with all the Environment Variables of the primary process 104 | ''; 105 | }; 106 | signal = mkOption { 107 | type = types.nullOr types.ints.unsigned; 108 | default = null; 109 | example = 15; 110 | description = '' 111 | If `shutdown.command` is not defined, exit the process with this signal. Defaults to `15` (SIGTERM) 112 | ''; 113 | }; 114 | timeout_seconds = mkOption { 115 | type = types.nullOr types.ints.unsigned; 116 | default = null; 117 | example = 10; 118 | description = '' 119 | Wait for `timeout_seconds` for its completion (if not defined wait for 10 seconds). Upon timeout, `SIGKILL` is sent to the process. 120 | ''; 121 | }; 122 | }; 123 | 124 | working_dir = mkOption { 125 | type = types.nullOr types.str; 126 | default = null; 127 | example = "/tmp"; 128 | description = '' 129 | The directory to run the process in. 130 | ''; 131 | }; 132 | readiness_probe = mkOption { 133 | type = types.nullOr probeType; 134 | default = null; 135 | description = '' 136 | The settings used to check if the process is ready to accept connections. 137 | ''; 138 | }; 139 | liveness_probe = mkOption { 140 | type = types.nullOr probeType; 141 | default = null; 142 | description = '' 143 | The settings used to check if the process is alive. 144 | ''; 145 | }; 146 | ready_log_line = mkOption { 147 | type = types.nullOr types.str; 148 | default = null; 149 | example = "process is ready"; 150 | description = '' 151 | A string to search for in the output of the command that indicates 152 | the process is ready. String will be part of a regex '.*{ready_log_line}.*'. 153 | This should be used for long running processes that do not have a 154 | readily accessible check for http or similar other checks. 155 | ''; 156 | }; 157 | 158 | namespace = mkOption { 159 | type = types.str; 160 | default = "default"; 161 | description = '' 162 | Used to group processes together. 163 | ''; 164 | }; 165 | 166 | environment = import ./environment.nix { inherit lib; }; 167 | log_location = mkOption { 168 | type = types.nullOr types.str; 169 | default = null; 170 | example = "./pc.my-proccess.log"; 171 | description = '' 172 | Log location of the `process-compose` process. 173 | ''; 174 | }; 175 | disable_ansi_colors = mkOption { 176 | type = types.nullOr types.bool; 177 | default = null; 178 | example = true; 179 | description = '' 180 | Whether to disable colors in the logs. 181 | ''; 182 | }; 183 | is_daemon = mkOption { 184 | type = types.nullOr types.bool; 185 | default = null; 186 | example = true; 187 | description = '' 188 | - For processes that start services / daemons in the background, please use the `is_daemon` flag set to `true`. 189 | - In case a process is daemon it will be considered running until stopped. 190 | - Daemon processes can only be stopped with the `$PROCESSNAME.shutdown.command` as in the example above. 191 | ''; 192 | }; 193 | is_foreground = mkOption { 194 | type = types.nullOr types.bool; 195 | default = null; 196 | example = true; 197 | description = '' 198 | Foreground processes are useful for cases when a full `tty` access is required (e.g. `vim`, `top`, `gdb -tui`) 199 | 200 | - Foreground process have to be started manually (`F7`). They can be started multiple times. 201 | - They are available in TUI mode only. 202 | - To return to TUI, exit the foreground process. 203 | - In TUI mode, a local process will be started. 204 | ''; 205 | }; 206 | is_tty = mkOption { 207 | type = types.nullOr types.bool; 208 | default = null; 209 | example = true; 210 | description = '' 211 | Simulate TTY mode for this process 212 | ''; 213 | }; 214 | disabled = mkOption { 215 | type = types.nullOr types.bool; 216 | default = if name == "test" then true else null; 217 | example = true; 218 | description = '' 219 | Whether the process is disabled. Useful when a process is required to be started only in a given scenario, like while running in CI. 220 | 221 | Even if disabled, the process is still listed in the TUI and the REST client, and can be started manually when needed. 222 | ''; 223 | }; 224 | 225 | }; 226 | } 227 | -------------------------------------------------------------------------------- /nix/process-compose/test.nix: -------------------------------------------------------------------------------- 1 | { name, config, pkgs, lib, ... }: 2 | let 3 | inherit (lib) types mkOption; 4 | in 5 | { 6 | options = { 7 | outputs.check = mkOption { 8 | description = '' 9 | Run the `process-compose` package with `test` process Enabled. 10 | 11 | Note: This is meant to be run in CI. 12 | ''; 13 | type = types.nullOr types.package; 14 | default = 15 | if (config.outputs.testPackage != null) then 16 | pkgs.runCommand "${name}-test" { } '' 17 | # Set pipefail option for safer bash 18 | set -euo pipefail 19 | export HOME=$TMP 20 | cd $HOME 21 | # Run with tui disabled because /dev/tty is disabled in the simulated shell 22 | ${lib.getExe config.outputs.testPackage} -t=false 23 | # `runCommand` will fail if $out isn't created 24 | touch $out 25 | '' 26 | else null; 27 | }; 28 | }; 29 | } 30 | --------------------------------------------------------------------------------